package org.archstudio.xarchadt.core; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.Serializable; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; import javax.xml.xpath.XPathException; import org.archstudio.sysutils.SystemUtils; import org.archstudio.xadl3.xadlcore_3_0.Xadlcore_3_0Package; import org.archstudio.xarchadt.IXArchADT; import org.archstudio.xarchadt.IXArchADTFeature; import org.archstudio.xarchadt.IXArchADTFeature.FeatureType; import org.archstudio.xarchadt.IXArchADTFeature.ValueType; import org.archstudio.xarchadt.IXArchADTFileListener; import org.archstudio.xarchadt.IXArchADTModelListener; import org.archstudio.xarchadt.IXArchADTPackageMetadata; import org.archstudio.xarchadt.IXArchADTSubstitutionHint; import org.archstudio.xarchadt.IXArchADTTypeMetadata; import org.archstudio.xarchadt.ObjRef; import org.archstudio.xarchadt.XArchADTFileEvent; import org.archstudio.xarchadt.XArchADTFileEvent.EventType; import org.archstudio.xarchadt.XArchADTModelEvent; import org.archstudio.xarchadt.core.internal.BasicXArchADTFeature; import org.archstudio.xarchadt.core.internal.BasicXArchADTPackageMetadata; import org.archstudio.xarchadt.core.internal.BasicXArchADTTypeMetadata; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IExtensionRegistry; import org.eclipse.core.runtime.Platform; import org.eclipse.e4.emf.xpath.EcoreXPathContextFactory; import org.eclipse.e4.emf.xpath.XPathContextFactory; import org.eclipse.emf.common.notify.Notification; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.common.util.Enumerator; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.common.util.WrappedException; import org.eclipse.emf.ecore.EAttribute; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EClassifier; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EPackage; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecore.EcorePackage; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.ecore.resource.impl.ResourceImpl; import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; import org.eclipse.emf.ecore.util.EContentAdapter; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.emf.ecore.util.ExtendedMetaData; import org.eclipse.emf.ecore.util.FeatureMap; import org.eclipse.emf.ecore.xmi.XMLResource; import org.eclipse.emf.ecore.xmi.impl.ElementHandlerImpl; import org.eclipse.emf.ecore.xmi.impl.GenericXMLResourceFactoryImpl; import org.eclipse.emf.ecore.xmi.impl.XMLParserPoolImpl; import org.eclipse.jdt.annotation.Nullable; import org.xml.sax.SAXException; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.Collections2; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.MapMaker; import com.google.common.collect.Maps; public class XArchADTImpl implements IXArchADT { protected final List<IXArchADTModelListener> modelListeners = Lists.newCopyOnWriteArrayList(); protected final List<IXArchADTFileListener> fileListeners = Lists.newCopyOnWriteArrayList(); protected final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); protected final Lock rLock = rwLock.readLock(); protected final Lock wLock = rwLock.writeLock(); // All the resources here indexed by URI private final ResourceSet resourceSet; private static final Map<Object, Object> LOAD_OPTIONS_MAP = new HashMap<Object, Object>(); static { // This allows root elements in other schema LOAD_OPTIONS_MAP.put(XMLResource.OPTION_EXTENDED_META_DATA, true); LOAD_OPTIONS_MAP.put(XMLResource.OPTION_PROCESS_DANGLING_HREF, XMLResource.OPTION_PROCESS_DANGLING_HREF_DISCARD); // optimizations from http://www.eclipse.org/modeling/emf/docs/performance/EMFPerformanceTips.html LOAD_OPTIONS_MAP.put(XMLResource.OPTION_DEFER_ATTACHMENT, true); LOAD_OPTIONS_MAP.put(XMLResource.OPTION_DEFER_IDREF_RESOLUTION, true); LOAD_OPTIONS_MAP.put(XMLResource.OPTION_USE_PARSER_POOL, new XMLParserPoolImpl()); LOAD_OPTIONS_MAP.put(XMLResource.OPTION_USE_XML_NAME_TO_FEATURE_MAP, new HashMap<Object, Object>()); } private static final ElementHandlerImpl elementHandlerImpl = new ElementHandlerImpl(false); private static final Map<Object, Object> SAVE_OPTIONS_MAP = new HashMap<Object, Object>(); static { // This voodoo supports substitution groups properly. SAVE_OPTIONS_MAP.put(XMLResource.OPTION_ELEMENT_HANDLER, elementHandlerImpl); } private static final String EMF_EXTENSION_POINT_ID = "org.eclipse.emf.ecore.generated_package"; public XArchADTImpl() { resourceSet = new ResourceSetImpl(); resourceSet.eAdapters().add(new ContentAdapter()); resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap() .put("xml", new GenericXMLResourceFactoryImpl()); resourceSet.getResourceFactoryRegistry().getContentTypeToFactoryMap() .put("xml", new GenericXMLResourceFactoryImpl()); registerAllSchemaPackages(); } /** * This method causes the EPackage.eINSTANCE variable of each EMF-generated bundle (plugin) in Eclipse to be * touched. This causes the package to spontaneously register itself with the EPackage Registry, which is how we * find out what EPackages are available on the system. This is an EMF-ism that isn't easily avoided. If you are * running outside of ArchStudio then these bundles will not be available. What you have to do then is just read the * eINSTANCE variable for all the EPackages you want to have available to you. */ private void registerAllSchemaPackages() { if (Platform.isRunning()) { IExtensionRegistry reg = Platform.getExtensionRegistry(); // The Extension Registry can be null if we're running outside of Eclipse for (IConfigurationElement configurationElement : reg.getConfigurationElementsFor(EMF_EXTENSION_POINT_ID)) { String packageClassName = configurationElement.getAttribute("class"); if (packageClassName != null) { String bundleName = configurationElement.getDeclaringExtension().getContributor().getName(); try { Class<?> packageClass = Platform.getBundle(bundleName).loadClass(packageClassName); Field instanceField = packageClass.getDeclaredField("eINSTANCE"); /* EPackage ePackage = (EPackage) */instanceField.get(packageClass); } catch (ClassNotFoundException cnfe) { } catch (NoSuchFieldException nsfe) { } catch (IllegalAccessException iae) { } } } } } // Map ObjRef <-> EObjects private final Map<ObjRef, EObject> objRefToEObject = new MapMaker().weakKeys().makeMap(); private final Map<EObject, ObjRef> eObjectToObjRef = new MapMaker().softValues().makeMap(); private final ReentrantReadWriteLock objRefToEObjectLockRWLock = new ReentrantReadWriteLock(); private final Lock objRefToEObjectLockRLock = objRefToEObjectLockRWLock.readLock(); private final Lock objRefToEObjectLockWLock = objRefToEObjectLockRWLock.writeLock(); @Nullable protected ObjRef putNullable(@Nullable EObject eObject) { return eObject != null ? put(eObject) : null; } protected ObjRef put(EObject eObject) { objRefToEObjectLockRLock.lock(); try { ObjRef objRef = eObjectToObjRef.get(eObject); if (objRef == null) { objRefToEObjectLockRLock.unlock(); try { objRefToEObjectLockWLock.lock(); try { objRef = new ObjRef(); eObjectToObjRef.put(eObject, objRef); objRefToEObject.put(objRef, eObject); } finally { objRefToEObjectLockWLock.unlock(); } } finally { objRefToEObjectLockRLock.lock(); } } return objRef; } finally { objRefToEObjectLockRLock.unlock(); } } protected List<ObjRef> putAll(Iterable<? extends EObject> eObjects) { List<ObjRef> objRefs = Lists.newArrayList(); for (EObject eObject : eObjects) { objRefs.add(put(eObject)); } return objRefs; } protected EObject get(ObjRef objRef) { objRefToEObjectLockRLock.lock(); try { EObject eObject = objRefToEObject.get(objRef); if (eObject == null) { throw new IllegalArgumentException("No such object: " + objRef); } return eObject; } finally { objRefToEObjectLockRLock.unlock(); } } protected List<EObject> getAll(Iterable<ObjRef> objRefs) { List<EObject> eObjects = Lists.newArrayList(); for (ObjRef objRef : objRefs) { eObjects.add(get(objRef)); } return eObjects; } @Nullable protected Serializable check(@Nullable Object object) { if (object instanceof EObject) { return put((EObject) object); } return (Serializable) object; } @Nullable protected Object uncheck(@Nullable Serializable serializable) { if (serializable instanceof ObjRef) { return get((ObjRef) serializable); } return serializable; } // Dynamically maps feature names (either uppercase or lowercase) of an EClass to EStructuralFeatures. private static final LoadingCache<EClass, Map<String, EStructuralFeature>> caselessFeatureCache = CacheBuilder .newBuilder().build(new CacheLoader<EClass, Map<String, EStructuralFeature>>() { Map<String, EStructuralFeature> structuralFeatures = Maps.newHashMap(); Map<Integer, EStructuralFeature> featureIDs = Maps.newHashMap(); @Override public Map<String, EStructuralFeature> load(@Nullable EClass eClass) throws Exception { structuralFeatures = Maps.newHashMap(); featureIDs = Maps.newHashMap(); apply0(eClass); return structuralFeatures; } private void apply0(@Nullable EClass eClass) { if (eClass == null) { throw new NullPointerException(); } for (EStructuralFeature eStructuralFeature : eClass.getEStructuralFeatures()) { EStructuralFeature conflictingEStructuralFeature = featureIDs.put( eStructuralFeature.getFeatureID(), eStructuralFeature); if (conflictingEStructuralFeature != null) { throw new RuntimeException("Unexpected structural feature"); } String name = eStructuralFeature.getName(); structuralFeatures.put(SystemUtils.capFirst(name), eStructuralFeature); structuralFeatures.put(SystemUtils.uncapFirst(name), eStructuralFeature); } for (EClass eSuperClass : eClass.getESuperTypes()) { apply0(eSuperClass); } } }); private static final EStructuralFeature getEFeature(EClass eClass, String featureName, boolean many) { EStructuralFeature eFeature = caselessFeatureCache.getUnchecked(eClass).get(featureName); if (eFeature == null) { throw new IllegalArgumentException(SystemUtils.message(// "EClass '$0' does not contain EFeature '$1' in EPackage '$2'.",// eClass.getName(), featureName, eClass.getEPackage().getNsURI())); } if (eFeature.isMany() && !many) { throw new IllegalArgumentException(SystemUtils.message(// "EFeature '$0' is many in EClass '$1:$2'.",// featureName, eClass.getEPackage().getNsURI(), eClass.getName())); } if (!eFeature.isMany() && many) { throw new IllegalArgumentException(SystemUtils.message(// "EFeature '$0' is single in EClass '$1:$2'.",// featureName, eClass.getEPackage().getNsURI(), eClass.getName())); } return eFeature; } protected static final EStructuralFeature getEFeature(EObject eObject, String featureName, boolean many) { return getEFeature(eObject.eClass(), featureName, many); } @SuppressWarnings({ "unchecked" }) private static final EList<Object> getEList(EObject eObject, String featureName) { try { return (EList<Object>) eObject.eGet(getEFeature(eObject.eClass(), featureName, true)); } catch (ClassCastException e) { throw new RuntimeException(SystemUtils.message(// "EObject of type '$0:$1' does not have an EList for feature '$2'", // eObject.eClass().getEPackage().getNsURI(), eObject.eClass().getName(), featureName), e); } } @Override public boolean isValidObjRef(ObjRef objRef) { try { return get(objRef) != null; } catch (Exception e) { return false; } } @Override public void set(ObjRef baseObjRef, String typeOfThing, @Nullable Serializable value) { wLock.lock(); try { EObject baseEObject = get(baseObjRef); EStructuralFeature typeFeature = getEFeature(baseEObject, typeOfThing, false); if (typeFeature instanceof EReference) { if (((EReference) typeFeature).isContainment()) { Object oldValue = baseEObject.eGet(typeFeature); if (oldValue instanceof EObject) { EcoreUtil.delete((EObject) oldValue); } } } baseEObject.eSet(typeFeature, uncheck(value)); } finally { wLock.unlock(); } } @Override public @Nullable Serializable get(ObjRef baseObjRef, String typeOfThing) { rLock.lock(); try { return get(baseObjRef, typeOfThing, true); } finally { rLock.unlock(); } } public @Nullable Serializable get(ObjRef baseObjRef, String typeOfThing, boolean resolve) { rLock.lock(); try { EObject baseEObject = get(baseObjRef); return check(baseEObject.eGet(getEFeature(baseEObject, typeOfThing, false), resolve)); } finally { rLock.unlock(); } } @Override public @Nullable Serializable resolve(ObjRef objRef) { rLock.lock(); try { EObject baseEObject = get(objRef); return check(EcoreUtil.resolve(baseEObject, baseEObject.eResource())); } finally { rLock.unlock(); } } @Override public void clear(ObjRef baseObjRef, String typeOfThing) { wLock.lock(); try { EObject baseEObject = get(baseObjRef); EStructuralFeature typeFeature = getEFeature(baseEObject, typeOfThing, false); if (typeFeature instanceof EReference) { if (((EReference) typeFeature).isContainment()) { Object oldValue = baseEObject.eGet(typeFeature); if (oldValue instanceof EObject) { EcoreUtil.delete((EObject) oldValue); return; } } } baseEObject.eUnset(typeFeature); } finally { wLock.unlock(); } } @Override public void add(ObjRef baseObjRef, String typeOfThing, Serializable thingToAdd) { wLock.lock(); try { getEList(get(baseObjRef), typeOfThing).add(uncheck(thingToAdd)); } finally { wLock.unlock(); } } @Override public void add(ObjRef baseObjRef, String typeOfThing, Collection<? extends Serializable> thingsToAdd) { wLock.lock(); try { for (Serializable thingToAdd : thingsToAdd) { getEList(get(baseObjRef), typeOfThing).add(uncheck(thingToAdd)); } } finally { wLock.unlock(); } } @Override public List<Serializable> getAll(ObjRef baseObjRef, String typeOfThing) { rLock.lock(); try { EList<Object> list = getEList(get(baseObjRef), typeOfThing); List<Serializable> result = Lists.newArrayListWithCapacity(list.size()); for (Object object : list) { result.add(check(object)); } return result; } finally { rLock.unlock(); } } @Override public void remove(ObjRef baseObjRef, String typeOfThing, Serializable thingToRemove) { wLock.lock(); try { Object unchecked = uncheck(thingToRemove); EObject baseEObject = get(baseObjRef); EStructuralFeature typeFeature = getEFeature(baseEObject, typeOfThing, true); if (typeFeature instanceof EReference) { if (((EReference) typeFeature).isContainment()) { if (unchecked instanceof EObject) { EcoreUtil.delete((EObject) unchecked); return; } } } getEList(get(baseObjRef), typeOfThing).remove(unchecked); } finally { wLock.unlock(); } } @Override public void remove(ObjRef baseObjRef, String typeOfThing, Collection<? extends Serializable> thingsToRemove) { wLock.lock(); try { for (Serializable thingToRemove : thingsToRemove) { remove(baseObjRef, typeOfThing, thingToRemove); } } finally { wLock.unlock(); } } @Override @Nullable public ObjRef getByID(ObjRef documentRef, String id) { rLock.lock(); try { return putNullable(get(documentRef).eResource().getEObject(id)); } finally { rLock.unlock(); } } @Override @Nullable public ObjRef getByID(String id) { rLock.lock(); try { for (Resource r : resourceSet.getResources()) { EObject objectByID = r.getEObject(id); if (objectByID != null) { return put(objectByID); } } return null; } finally { rLock.unlock(); } } @Override @Nullable public ObjRef getParent(ObjRef targetObjRef) { rLock.lock(); try { return putNullable(get(targetObjRef).eContainer()); } finally { rLock.unlock(); } } @Override public boolean hasAncestor(ObjRef childObjRef, ObjRef ancestorObjRef) { rLock.lock(); try { return EcoreUtil.isAncestor(get(ancestorObjRef), get(childObjRef)); } finally { rLock.unlock(); } } @Override public List<ObjRef> getAllAncestors(ObjRef targetObjRef) { rLock.lock(); try { EObject eObject = get(targetObjRef); List<ObjRef> ancestorObjRefs = Lists.newArrayList(); while (eObject != null) { ancestorObjRefs.add(put(eObject)); eObject = eObject.eContainer(); } return ancestorObjRefs; } finally { rLock.unlock(); } } @Override public List<ObjRef> getLineage(ObjRef targetObjRef) { rLock.lock(); try { return Lists.reverse(getAllAncestors(targetObjRef)); } finally { rLock.unlock(); } } @Override public boolean isAttached(ObjRef targetObjRef) { rLock.lock(); try { EObject eObject = get(targetObjRef); Resource resource = eObject.eResource(); if (resource != null) { return EcoreUtil.isAncestor(resource, eObject); } return false; } finally { rLock.unlock(); } } private static IXArchADTFeature.ValueType getValueType(EStructuralFeature feature) { Class<?> c = feature.getEType().getInstanceClass(); if (c == null) { return ValueType.OBJECT; } else if (Enumerator.class.isAssignableFrom(c)) { return ValueType.ENUMERATION; } else if (String.class.isAssignableFrom(c)) { return ValueType.STRING; } else { return ValueType.OBJECT; } } private static final LoadingCache<String, EPackage> ePackageCache = CacheBuilder.newBuilder().build( new CacheLoader<String, EPackage>() { @Override public EPackage load(@Nullable String nsURI) throws Exception { EPackage ePackage = EPackage.Registry.INSTANCE.getEPackage(nsURI); if (ePackage != null) { return ePackage; } throw new NullPointerException(SystemUtils.message("No EPackage with nsURI: $0", nsURI)); } }); private static final LoadingCache<EClass, Map<String, IXArchADTFeature>> featureMetadataCache = CacheBuilder .newBuilder().build(new CacheLoader<EClass, Map<String, IXArchADTFeature>>() { @Override public Map<String, IXArchADTFeature> load(@Nullable EClass eClass) throws Exception { if (eClass == null) { throw new NullPointerException(); } List<IXArchADTFeature> features = Lists.newArrayList(); features.addAll(Collections2.transform(eClass.getEAllAttributes(), new Function<EAttribute, IXArchADTFeature>() { @Override public IXArchADTFeature apply(@Nullable EAttribute eFeature) { if (eFeature == null) { throw new NullPointerException(); } return new BasicXArchADTFeature(eFeature.getName(), eFeature.getEType() .getEPackage().getNsURI(), eFeature.getEType().getName(), FeatureType.ATTRIBUTE, getValueType(eFeature), false); }; })); features.addAll(Collections2.transform(eClass.getEAllReferences(), new Function<EReference, IXArchADTFeature>() { @Override public IXArchADTFeature apply(@Nullable EReference eFeature) { if (eFeature == null) { throw new NullPointerException(); } return new BasicXArchADTFeature(eFeature.getName(), eFeature.getEType() .getEPackage().getNsURI(), eFeature.getEType().getName(), eFeature.isMany() ? FeatureType.ELEMENT_MULTIPLE : FeatureType.ELEMENT_SINGLE, getValueType(eFeature), !eFeature .isContainment()); }; })); // EClass allows multiple features by the same name, see #16 Map<String, IXArchADTFeature> featureMap = Maps.newHashMap(); for (IXArchADTFeature f : features) { featureMap.put(f.getName(), f); } return featureMap; } }); /** * This is a map that generates type metadata for an EClass on demand, caching it after it's generated. */ private static final LoadingCache<EClass, IXArchADTTypeMetadata> typeMetadataCache = CacheBuilder.newBuilder() .build(new CacheLoader<EClass, IXArchADTTypeMetadata>() { @Override public IXArchADTTypeMetadata load(@Nullable EClass eClass) throws Exception { if (eClass == null) { throw new NullPointerException(); } return new BasicXArchADTTypeMetadata(eClass.getEPackage().getNsURI(), eClass.getName(), featureMetadataCache.get(eClass), eClass.isAbstract()); } }); /** * This is a map that generates package metadata for an EPackage on demand, caching it after it's generated. */ private static final LoadingCache<EPackage, IXArchADTPackageMetadata> packageMetatadataCache = CacheBuilder .newBuilder().build(new CacheLoader<EPackage, IXArchADTPackageMetadata>() { @Override public IXArchADTPackageMetadata load(@Nullable EPackage ePackage) throws Exception { if (ePackage == null) { throw new NullPointerException(); } return new BasicXArchADTPackageMetadata(ePackage.getNsURI(), Iterables.transform( Iterables.filter(ePackage.getEClassifiers(), EClass.class), new Function<EClass, IXArchADTTypeMetadata>() { @Override public IXArchADTTypeMetadata apply(@Nullable EClass eClass) { return typeMetadataCache.getUnchecked(eClass); }; })); } }); private static final EClass getEClass(EPackage ePackage, String typeName) { EClassifier eClassifier = ePackage.getEClassifier(typeName); if (eClassifier == null) { eClassifier = ePackage.getEClassifier(SystemUtils.capFirst(typeName)); } if (eClassifier == null) { throw new IllegalArgumentException(SystemUtils.message( "EPackage '$0' does not contain an EClass named '$1'", ePackage, typeName)); } if (eClassifier instanceof EClass) { return (EClass) eClassifier; } throw new IllegalArgumentException("Classifier was not an instance of EClass"); } @Override public IXArchADTPackageMetadata getPackageMetadata(String nsURI) { rLock.lock(); try { return packageMetatadataCache.getUnchecked(ePackageCache.getUnchecked(nsURI)); } finally { rLock.unlock(); } } @Override public IXArchADTTypeMetadata getTypeMetadata(String nsURI, String typeName) { rLock.lock(); try { return typeMetadataCache.getUnchecked(getEClass(ePackageCache.getUnchecked(nsURI), typeName)); } finally { rLock.unlock(); } } @Override public IXArchADTTypeMetadata getTypeMetadata(ObjRef objRef) { rLock.lock(); try { return typeMetadataCache.getUnchecked(get(objRef).eClass()); } finally { rLock.unlock(); } } @Override public List<IXArchADTPackageMetadata> getAvailablePackageMetadata() { rLock.lock(); try { return Lists.newArrayList(Collections2.transform(EPackage.Registry.INSTANCE.keySet(), new Function<String, IXArchADTPackageMetadata>() { @Override public IXArchADTPackageMetadata apply(@Nullable String nsURI) { return packageMetatadataCache.getUnchecked(ePackageCache.getUnchecked(nsURI)); } })); } finally { rLock.unlock(); } } @Override public boolean isAssignable(String sourceNsURI, String sourceTypeName, String targetNsURI, String targetTypeName) { rLock.lock(); try { EClass eSourceClass = getEClass(ePackageCache.getUnchecked(sourceNsURI), sourceTypeName); EClass eTargetClass = getEClass(ePackageCache.getUnchecked(targetNsURI), targetTypeName); if (eSourceClass.equals(EcorePackage.Literals.EOBJECT)) { // This is a special case - all EWhatevers are inherently assignable // to EObject. This comes up when a schema has an 'any' element. return true; } return eSourceClass.isSuperTypeOf(eTargetClass); } finally { rLock.unlock(); } } @Override public boolean isInstanceOf(@Nullable Serializable object, String sourceNsURI, String sourceTypeName) { rLock.lock(); try { if (object == null) { return false; } if (!(object instanceof ObjRef)) { return false; } EObject baseEObject = get((ObjRef) object); return getEClass(ePackageCache.getUnchecked(sourceNsURI), sourceTypeName).isSuperTypeOf( baseEObject.eClass()); } finally { rLock.unlock(); } } @Override public List<URI> getOpenURIs() { rLock.lock(); try { List<URI> uriList = new ArrayList<URI>(); for (Resource r : resourceSet.getResources()) { uriList.add(r.getURI()); } return uriList; } finally { rLock.unlock(); } } @Override @Nullable public ObjRef getDocumentRootRef(URI uri) { rLock.lock(); try { Resource r = resourceSet.getResource(uri, false); return r != null && r.getContents().size() > 0 ? putNullable(r.getContents().get(0)) : null; } finally { rLock.unlock(); } } @Override @Nullable public ObjRef getDocumentRootRef(ObjRef objRef) { rLock.lock(); try { Resource eResource = get(objRef).eResource(); return eResource != null ? putNullable(eResource.getContents().get(0)) : null; } finally { rLock.unlock(); } } @Override public void close(URI uri) { wLock.lock(); try { Resource r = resourceSet.getResource(uri, false); setResourceFinishedLoading(r, false); r.unload(); fireXArchADTFileEvent(new XArchADTFileEvent(EventType.XARCH_CLOSED_EVENT, uri)); } finally { wLock.unlock(); } } @Override public ObjRef create(String nsURI, String typeOfThing) { wLock.lock(); try { EPackage ePackage = ePackageCache.getUnchecked(nsURI); EClass eClass = getEClass(ePackage, typeOfThing); EObject eObject = ePackage.getEFactoryInstance().create(eClass); return put(eObject); } finally { wLock.unlock(); } } @Override public ObjRef createDocument(URI uri) { wLock.lock(); try { return createDocument(uri, Xadlcore_3_0Package.eINSTANCE.getNsURI()); } finally { wLock.unlock(); } } @Override public ObjRef createDocument(URI uri, String nsURI) { wLock.lock(); try { Resource r = resourceSet.getResource(uri, false); if (r != null && r.getContents().size() >= 1) { return put(r.getContents().get(0)); } r = resourceSet.createResource(uri, "xml"); if (r instanceof ResourceImpl) { ((ResourceImpl) r).setIntrinsicIDToEObjectMap(new HashMap<String, EObject>()); } ObjRef documentRootRef = create(nsURI, "DocumentRoot"); EObject documentRootObject = get(documentRootRef); r.getContents().add(documentRootObject); setResourceFinishedLoading(r, true); fireXArchADTFileEvent(new XArchADTFileEvent(EventType.XARCH_CREATED_EVENT, uri, documentRootRef)); return documentRootRef; } finally { wLock.unlock(); } } @Override public ObjRef load(URI uri) throws SAXException, IOException { wLock.lock(); try { for (Resource r : resourceSet.getResources()) { if (r.getURI().equals(uri)) { // Possibly already open if (r.isLoaded() && r.getContents().size() >= 1) { // Already open return put(r.getContents().get(0)); } } } Resource newResource = null; try { newResource = resourceSet.getResource(uri, false); if (newResource == null) { newResource = resourceSet.createResource(uri); if (newResource instanceof ResourceImpl) { ((ResourceImpl) newResource).setIntrinsicIDToEObjectMap(new HashMap<String, EObject>()); } } if (!newResource.isLoaded()) { newResource.load(LOAD_OPTIONS_MAP); } } catch (WrappedException e) { // This catches problems with the model itself, such as unresolved dependencies e.printStackTrace(); // We still want to open the model (and eventually correct these problems) newResource = resourceSet.getResource(uri, false); } setResourceFinishedLoading(newResource, true); ObjRef rootElementRef = put(newResource.getContents().get(0)); fireXArchADTFileEvent(new XArchADTFileEvent(EventType.XARCH_OPENED_EVENT, uri, rootElementRef)); return rootElementRef; } finally { wLock.unlock(); } } @Override public ObjRef load(URI uri, byte[] content) throws SAXException, IOException { wLock.lock(); try { Resource r = resourceSet.createResource(uri); if (r instanceof ResourceImpl) { ((ResourceImpl) r).setIntrinsicIDToEObjectMap(new HashMap<String, EObject>()); } r.load(new ByteArrayInputStream(content), LOAD_OPTIONS_MAP); setResourceFinishedLoading(r, true); ObjRef rootElementRef = put(r.getContents().get(0)); fireXArchADTFileEvent(new XArchADTFileEvent(EventType.XARCH_OPENED_EVENT, uri, rootElementRef)); return rootElementRef; } finally { wLock.unlock(); } } @Override public void renameXArch(String oldURI, String newURI) { wLock.lock(); try { // TODO Auto-generated method stub throw new UnsupportedOperationException(); } finally { wLock.unlock(); } } @Override @Nullable public URI getURI(ObjRef ref) { rLock.lock(); try { EObject obj = get(ref); if (obj.eResource() == null) { return null; } return obj.eResource().getURI(); } finally { rLock.unlock(); } } @Override public void save(URI uri) throws IOException { rLock.lock(); try { Resource r = resourceSet.getResource(uri, false); r.save(SAVE_OPTIONS_MAP); fireXArchADTFileEvent(new XArchADTFileEvent(EventType.XARCH_SAVED_EVENT, uri, getDocumentRootRef(uri))); } finally { rLock.unlock(); } } @Override public byte[] serialize(URI uri) { rLock.lock(); try { Resource r = resourceSet.getResource(uri, false); try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); r.save(baos, SAVE_OPTIONS_MAP); return baos.toByteArray(); } catch (IOException ioe) { throw new RuntimeException("This shouldn't happen", ioe); } } finally { rLock.unlock(); } } @Override @Nullable public String getTagName(ObjRef objRef) { rLock.lock(); try { EObject eObject = get(objRef); EStructuralFeature sf = elementHandlerImpl.getRoot(ExtendedMetaData.INSTANCE, eObject.eClass()); if (sf != null) { return sf.getName(); } else if (eObject.eContainer() != null) { return eObject.eContainingFeature().getName(); } return null; } finally { rLock.unlock(); } } @Override @Nullable public String getContainingFeatureName(ObjRef ref) { rLock.lock(); try { EObject eobject = get(ref); EStructuralFeature containingFeature = eobject.eContainingFeature(); if (containingFeature == null) { return null; } return containingFeature.getName(); } finally { rLock.unlock(); } } @Override public String getTagsOnlyPathString(ObjRef targetObjRef) { rLock.lock(); try { List<String> tags = new ArrayList<String>(); EObject eObject = get(targetObjRef); while (eObject != null) { EStructuralFeature sf = elementHandlerImpl.getRoot(ExtendedMetaData.INSTANCE, eObject.eClass()); if (sf != null) { tags.add(sf.getName()); } else if (eObject.eContainer() != null) { tags.add(eObject.eContainingFeature().getName()); } else { break; } eObject = eObject.eContainer(); } return Joiner.on("/").join(Lists.reverse(tags)); } finally { rLock.unlock(); } } @Override @Nullable public ObjRef resolveHref(ObjRef documentRef, String href) { rLock.lock(); try {// TODO Do this right (e.g., open the target URL if necessary) if (href.startsWith("#")) { String id = href.substring(1); return getByID(documentRef, id); } throw new IllegalArgumentException("resolveHref only supports local hrefs right now."); } finally { rLock.unlock(); } } protected void fireXArchADTModelEvent(XArchADTModelEvent evt) { for (IXArchADTModelListener l : modelListeners) { l.handleXArchADTModelEvent(evt); } } public void addXArchADTModelListener(IXArchADTModelListener l) { wLock.lock(); try { modelListeners.add(l); } finally { wLock.unlock(); } } public void removeXArchADTModelListener(IXArchADTModelListener l) { wLock.lock(); try { modelListeners.remove(l); } finally { wLock.unlock(); } } protected synchronized void fireXArchADTFileEvent(XArchADTFileEvent evt) { for (IXArchADTFileListener l : fileListeners) { l.handleXArchADTFileEvent(evt); } } public void addXArchADTFileListener(IXArchADTFileListener l) { wLock.lock(); try { fileListeners.add(l); } finally { wLock.unlock(); } } public void removeXArchADTFileListener(IXArchADTFileListener l) { wLock.lock(); try { fileListeners.remove(l); } finally { wLock.unlock(); } } // This stuff is necessary to suppress the event flurry when a document is loaded. private final Set<Resource> resourcesFinishedLoading = new CopyOnWriteArraySet<Resource>(); private void setResourceFinishedLoading(Resource r, boolean isFinishedLoading) { if (isFinishedLoading) { resourcesFinishedLoading.add(r); } else { resourcesFinishedLoading.remove(r); } } class ContentAdapter extends EContentAdapter { @Override public void notifyChanged(@Nullable Notification notification) { if (notification == null) { throw new NullPointerException(); } super.notifyChanged(notification); if (notification.isTouch()) { return; } Object notifier = notification.getNotifier(); if (!(notifier instanceof EObject)) { return; } if (!resourcesFinishedLoading.contains(((EObject) notifier).eResource())) { return; } String featureName; Object feature = notification.getFeature(); if (feature instanceof EStructuralFeature) { featureName = ((EStructuralFeature) feature).getName(); } else { return; } ObjRef srcRef = put((EObject) notifier); List<ObjRef> srcAncestors = getAllAncestors(srcRef); String srcPath = getTagsOnlyPathString(srcRef); Object oldValue = notification.getOldValue(); Object newValue = notification.getNewValue(); String oldNewValueTagName = null; if (oldValue instanceof EObject) { oldNewValueTagName = getTagName(put((EObject) oldValue)); } else if (newValue instanceof EObject) { oldNewValueTagName = getTagName(put((EObject) newValue)); } if (oldNewValueTagName == null) { oldNewValueTagName = featureName; } String oldValuePath = null; if (oldValue instanceof EObject) { oldValue = put((EObject) oldValue); oldValuePath = srcPath + (srcPath.length() > 0 ? "/" : "") + oldNewValueTagName; } String newValuePath = null; if (newValue instanceof FeatureMap.Entry) { newValue = ((FeatureMap.Entry) newValue).getValue(); } if (newValue instanceof EObject) { newValue = put((EObject) newValue); newValuePath = srcPath + (srcPath.length() > 0 ? "/" : "") + oldNewValueTagName; } XArchADTModelEvent.EventType evtType; switch (notification.getEventType()) { case Notification.ADD: evtType = XArchADTModelEvent.EventType.ADD; break; case Notification.REMOVE: evtType = XArchADTModelEvent.EventType.REMOVE; break; case Notification.SET: if (notification.getNewValue() != null) { evtType = XArchADTModelEvent.EventType.SET; break; } case Notification.UNSET: evtType = XArchADTModelEvent.EventType.CLEAR; break; default: return; } fireXArchADTModelEvent(new XArchADTModelEvent(evtType, srcRef, srcAncestors, srcPath, featureName, oldValue, oldValuePath, newValue, newValuePath)); } } protected List<IXArchADTSubstitutionHint> allSubstitutionHints = null; @Override public List<IXArchADTSubstitutionHint> getAllSubstitutionHints() { rLock.lock(); try { if (allSubstitutionHints == null) { List<EPackage> allEPackages = Lists.newArrayList(); for (String packageNsURI : Lists.newArrayList(EPackage.Registry.INSTANCE.keySet())) { allEPackages.add(EPackage.Registry.INSTANCE.getEPackage(packageNsURI)); } allSubstitutionHints = Lists.newArrayList(); allSubstitutionHints.addAll(ExtensionHintUtils.parseExtensionHints(allEPackages)); allSubstitutionHints.addAll(SubstitutionHintUtils.parseSubstitutionHints(allEPackages)); } return allSubstitutionHints; } finally { rLock.unlock(); } } @Override public List<IXArchADTSubstitutionHint> getSubstitutionHintsForSource(String sourceNsURI, String sourceTypeName) { rLock.lock(); try { List<IXArchADTSubstitutionHint> matchingHints = new ArrayList<IXArchADTSubstitutionHint>(); for (IXArchADTSubstitutionHint hint : getAllSubstitutionHints()) { if (sourceNsURI.equals(hint.getSourceNsURI()) && sourceTypeName.equals(hint.getSourceTypeName())) { matchingHints.add(hint); } } return matchingHints; } finally { rLock.unlock(); } } @Override public List<IXArchADTSubstitutionHint> getSubstitutionHintsForTarget(String targetNsURI, String targetTypeName) { rLock.lock(); try { List<IXArchADTSubstitutionHint> matchingHints = new ArrayList<IXArchADTSubstitutionHint>(); for (IXArchADTSubstitutionHint hint : getAllSubstitutionHints()) { if (targetNsURI.equals(hint.getTargetNsURI()) && targetTypeName.equals(hint.getTargetTypeName())) { matchingHints.add(hint); } } return matchingHints; } finally { rLock.unlock(); } } @Override public String getXPath(ObjRef toObjRef) { rLock.lock(); try { StringBuffer sb = new StringBuffer(); for (ObjRef objRef : Lists.reverse(getAllAncestors(toObjRef))) { EObject eObject = get(objRef); EStructuralFeature feature = eObject.eContainingFeature(); if (feature == null) { continue; } sb.append("/"); sb.append(feature.getName()); if (feature.isMany()) { sb.append("["); sb.append(((EList<?>) eObject.eContainer().eGet(feature)).indexOf(eObject) + 1); sb.append("]"); } } return sb.toString(); } finally { rLock.unlock(); } } XPathContextFactory<EObject> ecoreXPathContextFactory = EcoreXPathContextFactory.newInstance(); @Override public List<ObjRef> resolveObjRefs(ObjRef contextObjRef, String xPath) throws XPathException { rLock.lock(); try { Iterator<EObject> it = ecoreXPathContextFactory.newContext(get(contextObjRef)).iterate(xPath); List<ObjRef> result = Lists.newArrayList(); while (it.hasNext()) { result.add(put(it.next())); } return result; } finally { rLock.unlock(); } } @Override public List<Serializable> resolveSerializables(ObjRef contextObjRef, String xPath) throws XPathException { rLock.lock(); try { Iterator<EObject> it = ecoreXPathContextFactory.newContext(get(contextObjRef)).iterate(xPath); List<Serializable> result = Lists.newArrayList(); while (it.hasNext()) { result.add(check(it.next())); } return result; } finally { rLock.unlock(); } } }