blob: f2301b732f297283db5878738ee9389d904ca4b2 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2015 Obeo.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.acceleo.common.internal.utils;
import java.io.IOException;
import java.util.AbstractCollection;
import java.util.AbstractSet;
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 org.eclipse.acceleo.common.IAcceleoConstants;
import org.eclipse.emf.common.EMFPlugin;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EFactory;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EPackage.Registry;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.URIConverter;
import org.eclipse.emf.ecore.resource.impl.ExtensibleURIConverterImpl;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
/**
* This registry will act as an extension of the global package registry : dynamic models will be registered
* and added here, yet they'll never be seen by other plugins.
* <p>
* We need to be able to react to changes made to these models : if the user removes or adds a new class, he
* expects to be able to use this new concept in his generators. Likewise, if the workspace model is deleted,
* we need to be able to restore the package registry so that the metamodel as installed in the plugins can be
* used anew without relaunching a new eclipse instance.
* </p>
* <p>
* This registry <b>must be separate from the global registry</b>! We cannot risk adding a model in the global
* registry and potentially breaking any model the user edits!
* </p>
*
* @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
* @since 3.0
*/
public final class AcceleoPackageRegistry extends HashMap<String, Object> implements EPackage.Registry {
/** Singleton instance of our dynamic registry. */
public static final AcceleoPackageRegistry INSTANCE = new AcceleoPackageRegistry();
/** Generated SUID. */
private static final long serialVersionUID = 5976916017848022583L;
/** This is the registry we'll delegate calls to. */
private EPackage.Registry delegate = EPackage.Registry.INSTANCE;
/**
* For dynamic ecore files only. To get the ecore file path of the registered nsURI. Dynamic packages are
* registered in the EMF Registry by using the {@link #registerEcorePackages(String)} method. The map key
* is the dynamic nsURI of an EPackage and the value is the ecore file path used to register this nsURI.
*/
private Map<String, String> dynamicEcorePackagePaths = new HashMap<String, String>();
/**
* This is a singleton. Access the sole instance through {@link #INSTANCE}.
*/
private AcceleoPackageRegistry() {
// Hides default constructor.
}
/**
* {@inheritDoc}
*
* @see java.util.HashMap#clear()
*/
@Override
public void clear() {
super.clear();
delegate.clear();
}
/**
* {@inheritDoc}
*
* @see java.util.HashMap#containsKey(java.lang.Object)
*/
@Override
public boolean containsKey(Object key) {
return super.containsKey(key) || delegate.containsKey(key);
}
/**
* {@inheritDoc}
*
* @see java.util.HashMap#containsValue(java.lang.Object)
*/
@Override
public boolean containsValue(Object value) {
return super.containsValue(value) || delegate.containsValue(value);
}
/**
* {@inheritDoc}
*
* @see java.util.HashMap#entrySet()
*/
@Override
public Set<Map.Entry<String, Object>> entrySet() {
Set<Map.Entry<String, Object>> dynamicEntries = super.entrySet();
Set<Map.Entry<String, Object>> globalEntries = delegate.entrySet();
return new AcceleoMultipleSet<Map.Entry<String, Object>>(dynamicEntries, globalEntries);
}
/**
* {@inheritDoc}
*
* @see java.util.HashMap#get(java.lang.Object)
*/
@Override
public Object get(Object key) {
Object result = super.get(key);
if (result == null) {
result = delegate.get(key);
}
return result;
}
/**
* This will be used by our workspace listener so that it can react to changes on the dynamic ecore
* models.
*
* @return The map of dynamic ecore models.
*/
public Map<String, String> getDynamicEcorePackagePaths() {
return dynamicEcorePackagePaths;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.ecore.EPackage.Registry#getEFactory(java.lang.String)
*/
public EFactory getEFactory(String nsURI) {
if (containsKey(nsURI)) {
Object ePackage = get(nsURI);
if (ePackage instanceof EPackage) {
EPackage result = (EPackage)ePackage;
return result.getEFactoryInstance();
}
}
return delegate.getEFactory(nsURI);
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.ecore.EPackage.Registry#getEPackage(java.lang.String)
*/
public EPackage getEPackage(String nsURI) {
EPackage found = null;
if (containsKey(nsURI)) {
Object ePackage = get(nsURI);
if (ePackage instanceof EPackage) {
found = (EPackage)ePackage;
} else if (ePackage instanceof EPackage.Descriptor) {
found = ((EPackage.Descriptor)ePackage).getEPackage();
}
}
if (found == null) {
found = delegate.getEPackage(nsURI);
if (found == null && nsURI != null && !nsURI.startsWith(IAcceleoConstants.LITERAL_BEGIN)) {
found = searchInRegisteredEPackageInstances(nsURI);
}
}
return found;
}
/**
* Search the nsURI by browsing through the registerd EPackage instances and looking up their nsURI
* attribute. This case might arise when for instance, the plugin.xml registration of the nsURI does not
* match the EPackage nsURI.
*
* @param nsURI
* the nsURI we are looking for.
* @return a matching {@link EPackage} if found, null otherwise.
*/
private EPackage searchInRegisteredEPackageInstances(String nsURI) {
Collection<Object> values = this.values();
for (Object object : values) {
if (object instanceof EPackage && ((EPackage)object).eResource() != null) {
EPackage ePackage = (EPackage)object;
Resource eResource = ePackage.eResource();
URI uri = eResource.getURI();
if (uri != null && nsURI.equals(uri.toString())) {
return ePackage;
}
}
}
return null;
}
/**
* To get the ecore file path of the registered nsURI. Dynamic packages are registered in the EMF EPackage
* Registry by using the 'registerEcorePackages' method. The result is not null when the EPackage has been
* registered in the EMF Registry with the 'registerEcorePackages' method.
*
* @param nsURI
* the NsURI of an EPackage
* @return the ecore file path that contains the given EPackage, or null if it hasn't been registered in
* the EMF Registry with the 'registerEcorePackages' method
*/
public String getRegisteredEcorePackagePath(String nsURI) {
return dynamicEcorePackagePaths.get(nsURI);
}
/**
* {@inheritDoc}
*
* @see java.util.HashMap#isEmpty()
*/
@Override
public boolean isEmpty() {
return super.isEmpty() && delegate.isEmpty();
}
/**
* {@inheritDoc}
*
* @see java.util.HashMap#keySet()
*/
@Override
public Set<String> keySet() {
Set<String> dynamicKeys = super.keySet();
Set<String> globalKeys = delegate.keySet();
return new AcceleoMultipleSet<String>(dynamicKeys, globalKeys);
}
/**
* {@inheritDoc}
*
* @see java.util.HashMap#put(java.lang.Object, java.lang.Object)
*/
@Override
public Object put(String key, Object value) {
if (dynamicEcorePackagePaths.containsKey(key)) {
return super.put(key, value);
}
return delegate.put(key, value);
}
/**
* {@inheritDoc}
*
* @see java.util.HashMap#putAll(java.util.Map)
*/
@Override
public void putAll(Map<? extends String, ? extends Object> m) {
for (Map.Entry<? extends String, ? extends Object> entry : m.entrySet()) {
// delegation will be taken care of in put(String, Object)
put(entry.getKey(), entry.getValue());
}
}
/**
* Register the given ecore file in the EMF Package Registry. It loads the ecore file and browses the
* elements, it means the root EPackage and its descendants.
*
* @param pathName
* is the path of the ecore file to register
* @param resourceSet
* The resource set.
* @return the NsURI of the ecore root package, or the given path name if it isn't possible to find the
* corresponding NsURI
*/
public List<String> registerEcorePackages(String pathName, ResourceSet resourceSet) {
List<String> res = new ArrayList<String>();
List<LazyEPackageDescriptor> descriptors = new ArrayList<LazyEPackageDescriptor>();
URIConverter converter = resourceSet.getURIConverter();
if (converter == null) {
converter = new ExtensibleURIConverterImpl();
}
URI metaURI = null;
// Specifically get rid of Ecore.ecore so that we never dynamically register it
if (pathName != null && pathName.endsWith(".ecore") && !pathName.startsWith("http://") //$NON-NLS-1$ //$NON-NLS-2$
&& !pathName.endsWith("Ecore.ecore")) { //$NON-NLS-1$
// Try and load the ecore file with its URI as-is or fall back to platform resource URI.
metaURI = URI.createURI(URI.decode(pathName));
if (!metaURI.isPlatform()) {
metaURI = URI.createPlatformResourceURI(pathName, true);
}
/*
* If the resourceset already have the EPackage loaded as a model, lets reload it.
*/
List<Resource> resources = resourceSet.getResources();
for (Resource resource : resources) {
if (resource.getURI() != null && resource.getURI().equals(metaURI)) {
resource.unload();
try {
resource.load(new HashMap<String, String>());
} catch (IOException e) {
AcceleoLogger.log(e, false);
}
}
}
descriptors.addAll(LazyEPackageDescriptor.create(metaURI, resourceSet, this));
// If that failed, try and load the ecore file with a platform:/resource URI
if (descriptors.size() == 0) {
metaURI = URI.createPlatformResourceURI(pathName, false);
descriptors.addAll(LazyEPackageDescriptor.create(metaURI, resourceSet, this));
}
// If all failed, try and load the model with a platform:/plugin URI
if (descriptors.size() == 0) {
metaURI = URI.createPlatformPluginURI(pathName, false);
descriptors.addAll(LazyEPackageDescriptor.create(metaURI, resourceSet, this));
}
}
final List<LazyEPackageDescriptor> toRemove = new ArrayList<LazyEPackageDescriptor>();
for (LazyEPackageDescriptor descriptor : descriptors) {
if ("".equals(descriptor.getNsURI())) { //$NON-NLS-1$
toRemove.add(descriptor);
}
}
descriptors.removeAll(toRemove);
if (descriptors.size() != 0) {
/*
* If there is already a LazyEPackageDescriptor for this uri, lets start by removing it.
*/
registerOrReplaceInRegistry(this, metaURI, descriptors);
for (LazyEPackageDescriptor descriptor : descriptors) {
res.add(descriptor.getNsURI());
}
return res;
}
res.add(pathName);
return res;
}
/**
* Register the given descriptor in the registry but first de-registering any instance which might have
* been previously registered with an equivalent resource URI. This is done to support cases where for
* instance, the end user changed the nsURI of an {@link EPackage} in its workspace. We don't want the old
* nsURI to stay around.
*
* @param registry
* registry to update.
* @param resourceURI
* the resource URI
* @param descriptors
* descriptors to register.
*/
private void registerOrReplaceInRegistry(Registry registry, URI resourceURI,
List<LazyEPackageDescriptor> descriptors) {
List<String> toRemove = Lists.newArrayList();
Set<LazyEPackageDescriptor> toUnload = Sets.newLinkedHashSet();
for (Map.Entry<String, Object> entry : registry.entrySet()) {
/*
* I only take care of my own instances to avoid unpredictable side effects on other tools.
*/
if (entry.getValue() instanceof LazyEPackageDescriptor) {
LazyEPackageDescriptor registered = (LazyEPackageDescriptor)entry.getValue();
if (registered.getResourceURI().equals(resourceURI)) {
toRemove.add(entry.getKey());
}
toUnload.add(registered);
}
}
for (String nsURI : toRemove) {
registry.remove(nsURI);
}
for (LazyEPackageDescriptor descriptor : descriptors) {
registerDescriptorsHierarchy(registry, descriptor);
}
}
/**
* Register the given descriptor and its sub descriptors in the given registry.
*
* @param registry
* registry to update.
* @param descriptor
* the descriptor to register.
*/
private void registerDescriptorsHierarchy(Registry registry, LazyEPackageDescriptor descriptor) {
if (descriptor.getNsURI() != null) {
// The MTL ecore file mustn't be dynamic!!!
// TODO JMU we should use an extension point for the dynamic ecore files we would like to exclude
if (!"mtl".equals(descriptor.getNsPrefix()) && !"mtlnonstdlib".equals(descriptor.getNsPrefix()) //$NON-NLS-1$ //$NON-NLS-2$
&& !"mtlstdlib".equals(descriptor.getNsPrefix()) && !"oclstdlib".equals(descriptor.getNsPrefix())) { //$NON-NLS-1$ //$NON-NLS-2$
dynamicEcorePackagePaths.put(descriptor.getNsURI(), descriptor.getResourceURI().toString());
registry.put(descriptor.getNsURI(), descriptor);
}
}
for (LazyEPackageDescriptor child : descriptor.getESubpackages()) {
registerDescriptorsHierarchy(registry, child);
}
}
/**
* {@inheritDoc}
*
* @see java.util.HashMap#remove(java.lang.Object)
*/
@Override
public Object remove(Object key) {
boolean hasBeenRemoved = false;
if (dynamicEcorePackagePaths.containsKey(key) && EMFPlugin.IS_ECLIPSE_RUNNING) {
List<Resource> resources = AcceleoDynamicMetamodelResourceSetImpl.DYNAMIC_METAMODEL_RESOURCE_SET
.getResources();
Iterator<Resource> iterator = resources.iterator();
while (iterator.hasNext()) {
Resource resource = iterator.next();
if (key instanceof String && key.equals(resource.getURI().toString())) {
iterator.remove();
super.remove(key);
hasBeenRemoved = true;
} else {
String value = dynamicEcorePackagePaths.get(key);
if (value.equals(resource.getURI().toString())) {
iterator.remove();
super.remove(key);
hasBeenRemoved = true;
}
}
}
}
Object value = get(key);
if (value instanceof LazyEPackageDescriptor) {
super.remove(key);
hasBeenRemoved = true;
}
if (!hasBeenRemoved) {
// if we found it in this package registry, it means that it was in the workspace.
// no need to delete another version in the plugin.
return delegate.remove(key);
}
return null;
}
/**
* {@inheritDoc}
*
* @see java.util.HashMap#size()
*/
@Override
public int size() {
return super.size() + delegate.size();
}
/**
* Removes the given ecore file from the EMF Package Registry.
*
* @param pathName
* is the path of the ecore file to remove
*/
public void unregisterEcorePackages(String pathName) {
Iterator<Map.Entry<String, String>> entryIterator = dynamicEcorePackagePaths.entrySet().iterator();
while (entryIterator.hasNext()) {
Map.Entry<String, String> dynamicEcore = entryIterator.next();
if (dynamicEcore.getValue().endsWith(pathName)) {
remove(dynamicEcore.getKey());
entryIterator.remove();
}
}
}
/**
* {@inheritDoc}
*
* @see java.util.HashMap#values()
*/
@Override
public Collection<Object> values() {
Collection<Object> dynamicValues = super.values();
Collection<Object> globalValues = delegate.values();
return new AcceleoMultipleCollection<Object>(dynamicValues, globalValues);
}
/**
* Utility operation to register Ecore packages for dynamic metamodel already loaded by EMF.
*
* @param loadedEPackage
* The EPackage to register.
*/
public void registerEcorePackage(EPackage loadedEPackage) {
this.registerDescriptorsHierarchy(this, LazyEPackageDescriptor.create(loadedEPackage, this));
}
/**
* This custom implementation of a Set will simply allow us to "wrap" around two sets so that iterating
* over one switches to the second once all entries have been browsed.
*
* @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
*/
private class AcceleoMultipleSet<K> extends AbstractSet<K> {
/** First of the two sets to iterate over. */
private final Set<K> firstSet;
/** Second of the two sets to iterate over. */
private final Set<K> secondSet;
/**
* This default constructor initializes the two sets this instance will wrap.
*
* @param set1
* First of the two sets to iterate over.
* @param set2
* Second of the two sets to iterate over.
*/
public AcceleoMultipleSet(Set<K> set1, Set<K> set2) {
firstSet = set1;
secondSet = set2;
}
/**
* {@inheritDoc}
*
* @see java.util.AbstractCollection#iterator()
*/
@Override
public Iterator<K> iterator() {
return new AcceleoMultipleIterator<K>(firstSet.iterator(), secondSet.iterator());
}
/**
* {@inheritDoc}
*
* @see java.util.AbstractCollection#size()
*/
@Override
public int size() {
return firstSet.size() + secondSet.size();
}
}
/**
* This custom implementation of a Collection will simply allow us to "wrap" around two Collections so
* that iterating over one switches to the second once all entries have been browsed.
*
* @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
*/
private class AcceleoMultipleCollection<V> extends AbstractCollection<V> {
/** First of the two collections to iterate over. */
private final Collection<V> firstCollection;
/** Second of the two collections to iterate over. */
private final Collection<V> secondCollection;
/**
* This default constructor initializes the two collections this instance will wrap.
*
* @param collection1
* First of the two collections to iterate over.
* @param collection2
* Second of the two collections to iterate over.
*/
public AcceleoMultipleCollection(Collection<V> collection1, Collection<V> collection2) {
firstCollection = collection1;
secondCollection = collection2;
}
/**
* {@inheritDoc}
*
* @see java.util.AbstractCollection#iterator()
*/
@Override
public Iterator<V> iterator() {
return new AcceleoMultipleIterator<V>(firstCollection.iterator(), secondCollection.iterator());
}
/**
* {@inheritDoc}
*
* @see java.util.AbstractCollection#size()
*/
@Override
public int size() {
return firstCollection.size() + secondCollection.size();
}
/**
* {@inheritDoc}
*
* @see java.util.AbstractCollection#contains(java.lang.Object)
*/
@Override
public boolean contains(Object o) {
return firstCollection.contains(o) || secondCollection.contains(o);
}
/**
* {@inheritDoc}
*
* @see java.util.AbstractCollection#clear()
*/
@Override
public void clear() {
firstCollection.clear();
secondCollection.clear();
}
}
/**
* This custom implementation of an Iterator will simply allow us to "wrap" around two Iterators so that
* iterating over one switches to the second once all entries have been browsed.
*
* @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
*/
private class AcceleoMultipleIterator<E> implements Iterator<E> {
/** First of the two iterators to iterate over. */
private final Iterator<E> firstIterator;
/** Second of the two iterators to iterate over. */
private final Iterator<E> secondIterator;
/** Keeps track of the current iterator. */
private Iterator<E> current;
/**
* This default constructor initializes the two iterators this instance will wrap.
*
* @param iterator1
* First of the two iterators to iterate over.
* @param iterator2
* Second of the two iterators to iterate over.
*/
public AcceleoMultipleIterator(Iterator<E> iterator1, Iterator<E> iterator2) {
firstIterator = iterator1;
secondIterator = iterator2;
current = firstIterator;
}
/**
* {@inheritDoc}
*
* @see java.util.Iterator#hasNext()
*/
public boolean hasNext() {
return firstIterator.hasNext() || secondIterator.hasNext();
}
/**
* {@inheritDoc}
*
* @see java.util.Iterator#next()
*/
public E next() {
if (firstIterator.hasNext()) {
return firstIterator.next();
}
current = secondIterator;
return secondIterator.next();
}
/**
* {@inheritDoc}
*
* @see java.util.Iterator#remove()
*/
public void remove() {
current.remove();
}
}
}