/******************************************************************************* * Copyright (c) 2008-2011 Chair for Applied Software Engineering, * Technische Universitaet Muenchen. * 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: * Maximilian Koegel, Edgar Mueller - initial API and implementation ******************************************************************************/ package org.eclipse.emf.emfstore.internal.common.model.impl; import java.io.IOException; import java.text.MessageFormat; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.common.util.TreeIterator; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature.Setting; import org.eclipse.emf.ecore.impl.EObjectImpl; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.util.EcoreUtil.Copier; import org.eclipse.emf.ecore.util.EcoreUtil.UsageCrossReferencer; import org.eclipse.emf.ecore.xmi.XMIResource; import org.eclipse.emf.emfstore.common.extensionpoint.ESExtensionElement; import org.eclipse.emf.emfstore.common.extensionpoint.ESExtensionPoint; import org.eclipse.emf.emfstore.common.model.ESModelElementIdGenerator; import org.eclipse.emf.emfstore.internal.common.ESDisposable; import org.eclipse.emf.emfstore.internal.common.model.IdEObjectCollection; import org.eclipse.emf.emfstore.internal.common.model.ModelElementId; import org.eclipse.emf.emfstore.internal.common.model.ModelFactory; import org.eclipse.emf.emfstore.internal.common.model.util.ModelUtil; /** * Implementation of an ID based storage mechanism for {@link EObject}s. * * @author emueller * @author mkoegel */ public abstract class IdEObjectCollectionImpl extends EObjectImpl implements IdEObjectCollection, ESDisposable { /** * The extension point id to configure the {@link ESModelElementIdGenerator}. */ public static final String MODELELEMENTID_GENERATOR_EXTENSIONPOINT = "org.eclipse.emf.emfstore.common.model.modelelementIdGenerator"; //$NON-NLS-1$ /** * The attribute identifying the class of the {@link ESModelElementIdGenerator} extension point. */ public static final String MODELELEMENTID_GENERATOR_CLASS_ATTRIBUTE = "class"; //$NON-NLS-1$ // Caches private Map<EObject, String> eObjectToIdMap; private Map<String, EObject> idToEObjectMap; // These caches will be used to assign specific IDs to newly created EObjects. // Additionally, IDs of deleted model elements will also be put into these caches, in case // the deleted elements will be restored during a command. private final Map<EObject, String> allocatedEObjectToIdMap; private final Map<String, EObject> allocatedIdToEObjectMap; private boolean cachesInitialized; /** * A {@link ESModelElementIdGenerator} for other plugins to register a special ID generation. */ private ESModelElementIdGenerator<ModelElementId> modelElementIdGenerator; /** * Constructor. */ @SuppressWarnings("unchecked") public IdEObjectCollectionImpl() { eObjectToIdMap = new LinkedHashMap<EObject, String>(); idToEObjectMap = new LinkedHashMap<String, EObject>(); allocatedEObjectToIdMap = new LinkedHashMap<EObject, String>(); allocatedIdToEObjectMap = new LinkedHashMap<String, EObject>(); final ESExtensionElement element = new ESExtensionPoint(MODELELEMENTID_GENERATOR_EXTENSIONPOINT) .getElementWithHighestPriority(); if (element != null) { modelElementIdGenerator = element.getClass( MODELELEMENTID_GENERATOR_CLASS_ATTRIBUTE, ESModelElementIdGenerator.class); } } /** * Constructor. Adds the contents of the given {@link XMIResource} as model * elements to the collection. If the {@link XMIResource} also has XMI IDs * assigned to the {@link EObject}s it contains, they will be used for * creating the {@link ModelElementId}s within the project, if not, the {@link ModelElementId}s will get created on * the fly. * * @param xmiResource * a {@link XMIResource} * @throws IOException * if the given {@link XMIResource} could not be loaded */ public IdEObjectCollectionImpl(XMIResource xmiResource) throws IOException { this(); boolean resourceHasIds = false; try { if (!xmiResource.isLoaded()) { ModelUtil.loadResource(xmiResource, ModelUtil.getResourceLogger()); } } catch (final IOException e) { ModelUtil.logException( String.format(Messages.IdEObjectCollectionImpl_XMIResourceNotLoaded, xmiResource.getURI()), e); throw e; } final TreeIterator<EObject> it = xmiResource.getAllContents(); while (it.hasNext()) { final EObject eObject = it.next(); if (ModelUtil.isIgnoredDatatype(eObject)) { continue; } final String id = xmiResource.getID(eObject); final ModelElementId eObjectId = getNewModelElementID(); if (id != null) { eObjectId.setId(id); resourceHasIds = true; } else { xmiResource.setID(eObject, eObjectId.getId()); } putIntoCaches(eObject, eObjectId.getId()); } if (resourceHasIds) { cachesInitialized = true; } final EList<EObject> contents = xmiResource.getContents(); setModelElements(contents); if (!resourceHasIds) { // save, in order to write IDs back into resource ModelUtil.saveResource(xmiResource, ModelUtil.getResourceLogger()); } } /** * * {@inheritDoc} * * @see org.eclipse.emf.emfstore.internal.common.model.IdEObjectCollection#getModelElements() */ public abstract EList<EObject> getModelElements(); /** * Sets the model elements of this collection. * * @param modelElements * the new list of model elements the collection should hold */ protected abstract void setModelElements(EList<EObject> modelElements); /** * {@inheritDoc} * * @see org.eclipse.emf.emfstore.internal.common.model.IdEObjectCollection#addModelElement(org.eclipse.emf.ecore.EObject) */ public void addModelElement(EObject eObject) { getModelElements().add(eObject); } /** * * {@inheritDoc} * * @see org.eclipse.emf.emfstore.internal.common.model.IdEObjectCollection#contains(org.eclipse.emf.ecore.EObject) */ public boolean contains(EObject modelElement) { return getEObjectsCache().contains(modelElement); } /** * * {@inheritDoc} * * @see org.eclipse.emf.emfstore.internal.common.model.IdEObjectCollection#contains(org.eclipse.emf.emfstore.internal.common.model.ModelElementId) */ public boolean contains(ModelElementId id) { if (!isCacheInitialized()) { initMapping(); } return getIdToEObjectCache().containsKey(id); } /** * Returns the ID of a deleted model element. * <b>NOTE</b>: If commands are used, IDs of deleted model elements are only available during command execution. * If commands aren't used IDs of deleted model elements remain as long available until either a share, * a commit or an update happens. * * @param deletedModelElement * the model element that has been deleted * @return the ID of the deleted model element or {@code null} if no such ID exists */ public ModelElementId getDeletedModelElementId(EObject deletedModelElement) { final String id = allocatedEObjectToIdMap.get(deletedModelElement); if (id != null) { final ModelElementId modelElementId = ModelFactory.eINSTANCE.createModelElementId(); modelElementId.setId(id); return modelElementId; } return ModelUtil.getSingletonModelElementId(deletedModelElement); } /** * Returns the deleted model element by means of an ID. * <b>NOTE</b>: If commands are used, deleted model elements are only available during command execution. * If commands aren't used IDs of deleted model elements remain as long available until either a share, * a commit or an update happens. * * @param deletedModelElementId * the ID of an already deleted model element * @return the deleted model element or {@code null} if no such element exists */ public EObject getDeletedModelElement(ModelElementId deletedModelElementId) { if (deletedModelElementId == null) { return null; } final EObject eObject = allocatedIdToEObjectMap.get(deletedModelElementId); return eObject != null ? eObject : ModelUtil.getSingleton(deletedModelElementId); } /** * * {@inheritDoc} * * @see org.eclipse.emf.emfstore.internal.common.model.IdEObjectCollection#getModelElementId(org.eclipse.emf.ecore.EObject) */ public ModelElementId getModelElementId(EObject eObject) { // EObject _is_ project -> assign magic ModelElementId if (this == eObject) { final ModelElementId modelElementId = getNewModelElementID(); modelElementId.setId("001"); //$NON-NLS-1$ return modelElementId; } if (!eObjectToIdMap.containsKey(eObject) && !isCacheInitialized()) { // EObject contained in project, load ID from resource try { final Resource resource = eObject.eResource(); // EM: is this a potential error case we have to consider? if (!(resource instanceof XMIResource)) { return null; } final XMIResource xmiResource = (XMIResource) resource; ModelUtil.loadResource(xmiResource, ModelUtil.getResourceLogger()); final ModelElementId modelElementId = getNewModelElementID(); final String id = xmiResource.getID(eObject); if (id != null) { // change generated ID if one has been found in the resource modelElementId.setId(id); } eObjectToIdMap.put(eObject, modelElementId.getId()); return modelElementId; } catch (final IOException e) { throw new RuntimeException(Messages.IdEObjectCollectionImpl_CouldNotLoadElementResource + eObject); } } final String id = eObjectToIdMap.get(eObject); final ModelElementId modelElementId = getNewModelElementID(); modelElementId.setId(id); return id != null ? modelElementId : ModelUtil.getSingletonModelElementId(eObject); } /** * * {@inheritDoc} * * @see org.eclipse.emf.emfstore.internal.common.model.IdEObjectCollection#getModelElement(org.eclipse.emf.emfstore.internal.common.model.ModelElementId) */ public EObject getModelElement(ModelElementId modelElementId) { if (modelElementId == null) { return null; } if (!isCacheInitialized()) { initMapping(); } final EObject eObject = getIdToEObjectCache().get(modelElementId.getId()); return eObject != null ? eObject : ModelUtil.getSingleton(modelElementId); } /** * * {@inheritDoc} * * @see org.eclipse.emf.emfstore.internal.common.model.IdEObjectCollection#deleteModelElement(org.eclipse.emf.ecore.EObject) */ public void deleteModelElement(final EObject modelElement) { if (!this.contains(modelElement)) { throw new IllegalArgumentException(Messages.IdEObjectCollectionImpl_ElementNotContainedInProject); } // remove cross references ModelUtil.deleteOutgoingCrossReferences(this, modelElement); final Collection<Setting> crossReferences = UsageCrossReferencer.find(modelElement, this); ModelUtil.deleteIncomingCrossReferencesToElement(modelElement, crossReferences, new LinkedHashSet<EObject>()); // remove containment final EObject containerModelElement = ModelUtil.getContainerModelElement(modelElement); if (containerModelElement == null) { // removeModelElementAndChildrenFromCache(modelElement); // getEobjectsIdMap().remove(modelElement); getModelElements().remove(modelElement); } else { final EReference containmentFeature = modelElement.eContainmentFeature(); if (containmentFeature.isMany()) { final EList<?> containmentList = (EList<?>) containerModelElement.eGet(containmentFeature); containmentList.remove(modelElement); } else { containerModelElement.eSet(containmentFeature, null); } removeModelElementAndChildrenFromResource(modelElement); } } /** * Removes the the given {@link EObject} and all its contained children from * their respective {@link XMIResource}s. * * @param eObject * the {@link EObject} to remove */ public void removeModelElementAndChildrenFromResource(EObject eObject) { final Set<EObject> children = ModelUtil.getAllContainedModelElements(eObject, false); for (final EObject child : children) { removeModelElementFromResource(child); } removeModelElementFromResource(eObject); } /** * Removes the the given {@link EObject} from its {@link XMIResource}. * * @param xmiResource * the {@link EObject}'s resource * @param eObject * the {@link EObject} to remove */ private void removeModelElementFromResource(EObject eObject) { if (!(eObject.eResource() instanceof XMIResource)) { return; } final XMIResource xmiResource = (XMIResource) eObject.eResource(); if (xmiResource.getURI() == null) { return; } xmiResource.setID(eObject, null); try { ModelUtil.saveResource(xmiResource, ModelUtil.getResourceLogger()); } catch (final IOException e) { throw new RuntimeException( MessageFormat.format( Messages.IdEObjectCollectionImpl_ResourceCouldNotBeSaved, eObject, e.getMessage())); } } /** * Returns the {@link ModelElementId} for the given model element. If no * such ID exists, a new one will be created. * * @param modelElement * a model element to fetch a {@link ModelElementId} for * @return the {@link ModelElementId} for the given model element */ private ModelElementId getIdForModelElement(EObject modelElement) { final Resource resource = modelElement.eResource(); if (resource != null && resource instanceof XMIResource) { // resource available, read ID final XMIResource xmiResource = (XMIResource) resource; try { ModelUtil.loadResource(xmiResource, ModelUtil.getResourceLogger()); } catch (final IOException e) { throw new RuntimeException( MessageFormat.format(Messages.IdEObjectCollectionImpl_ResoruceCouldNotBeLoaded, modelElement)); } final String id = xmiResource.getID(modelElement); if (id != null) { final ModelElementId objId = getNewModelElementID(); objId.setId(id); return objId; } } // create new ID return getNewModelElementID(); } /** * * {@inheritDoc} * * @see org.eclipse.emf.emfstore.internal.common.model.IdEObjectCollection#getAllModelElements() */ public Set<EObject> getAllModelElements() { if (!isCacheInitialized()) { initMapping(); } return Collections.unmodifiableSet(eObjectToIdMap.keySet()); } /** * Gets all model elements by class. * * @param modelElementClass the EClass * @param list the list of model elements * @return the model elements matching the class * * @param <T> type */ public <T extends EObject> EList<T> getAllModelElementsByClass(EClass modelElementClass, EList<T> list) { return getAllModelElementsByClass(modelElementClass, list, true); } /** * Gets all model elements by class. * * @param modelElementClass the EClass * @param list the list of model elements * @return the model elements matching the class * * @param <T> type */ // cast below is guarded by sanity check @SuppressWarnings("unchecked") public <T extends EObject> EList<T> getModelElementsByClass(EClass modelElementClass, EList<T> list) { for (final EObject modelElement : getModelElements()) { if (modelElementClass.isInstance(modelElement)) { list.add((T) modelElement); } } return list; } /** * Gets all model elements by class. * * @param modelElementClass the EClass * @param list the list of model elements * @param subclasses whether to use subclasses * @return the model elements matching the class * * @param <T> type */ // two casts below are guarded by initial sanity check and if statement @SuppressWarnings("unchecked") public <T extends EObject> EList<T> getAllModelElementsByClass(EClass modelElementClass, EList<T> list, Boolean subclasses) { if (subclasses) { for (final EObject modelElement : getAllModelElements()) { if (modelElementClass.isInstance(modelElement)) { list.add((T) modelElement); } } } else { for (final EObject modelElement : getAllModelElements()) { if (modelElement.eClass() == modelElementClass) { list.add((T) modelElement); } } } return list; } /** * {@inheritDoc} * * @see org.eclipse.emf.emfstore.common.model.ESObjectContainer#getAllModelElementsByClass(java.lang.Class) */ public <T extends EObject> Set<T> getAllModelElementsByClass(Class<T> modelElementClass) { return getAllModelElementsByClass(modelElementClass, true); } /** * {@inheritDoc} * * @see org.eclipse.emf.emfstore.common.model.ESObjectContainer#getAllModelElementsByClass(java.lang.Class, * java.lang.Boolean) */ @SuppressWarnings("unchecked") public <T extends EObject> Set<T> getAllModelElementsByClass(Class<T> modelElementClass, Boolean includeSubclasses) { final LinkedHashSet<T> result = new LinkedHashSet<T>(); if (includeSubclasses) { for (final EObject modelElement : getAllModelElements()) { if (modelElementClass.isInstance(modelElement)) { result.add((T) modelElement); } } } else { for (final EObject modelElement : getAllModelElements()) { if (modelElement.getClass() == modelElementClass) { result.add((T) modelElement); } } } return result; } /** * Whether the cache has been initialized. * * @return true, if the cache is initialized, false otherwise */ protected boolean isCacheInitialized() { return cachesInitialized; } /** * Returns the cache that maps {@link ModelElementId} to model elements. * * @return a map containing mappings from {@link ModelElementId}s to model * element */ protected Map<String, EObject> getIdToEObjectCache() { if (!isCacheInitialized()) { initMapping(); } return idToEObjectMap; } /** * Returns the model element cache. * * @return a set containing all model elements */ protected Set<EObject> getEObjectsCache() { if (!isCacheInitialized()) { initMapping(); } return eObjectToIdMap.keySet(); } /** * * {@inheritDoc} * * @see org.eclipse.emf.emfstore.internal.common.model.IdEObjectCollection#initMapping() */ public void initMapping() { if (isCacheInitialized()) { return; } for (final EObject modelElement : getModelElements()) { putModelElementIntoCache(modelElement); } cachesInitialized = true; } /** * Puts the given model element into the collections' caches. * * @param modelElement * the model element which should be added to the caches */ protected void putModelElementIntoCache(EObject modelElement) { // put model element into cache final ModelElementId modelElementId = getIdForModelElement(modelElement); putIntoCaches(modelElement, modelElementId.getId()); // put children of model element into cache final TreeIterator<EObject> it = modelElement.eAllContents(); while (it.hasNext()) { final EObject obj = it.next(); final ModelElementId id = getIdForModelElement(obj); putIntoCaches(obj, id.getId()); } } /** * Adds a model element and all its children to the caches. * * @param modelElement * the model element, that should get added to the caches */ protected void addModelElementAndChildrenToCache(EObject modelElement) { final HashSet<String> removableIds = new LinkedHashSet<String>(); final Set<EObject> containedModelElements = ModelUtil.getAllContainedModelElements(modelElement, false); containedModelElements.add(modelElement); for (final EObject child : containedModelElements) { // first check whether ID should be reassigned String childId = allocatedEObjectToIdMap.get(child); if (childId == null) { // if not, create a new ID childId = getNewModelElementID().getId(); } else { removableIds.add(childId); } if (isCacheInitialized()) { putIntoCaches(child, childId); } } // remove all IDs that are in use now for (final String modelElementId : removableIds) { final EObject eObject = allocatedIdToEObjectMap.get(modelElementId); allocatedEObjectToIdMap.remove(eObject); } allocatedIdToEObjectMap.keySet().removeAll(removableIds); } /** * * {@inheritDoc} * * @see org.eclipse.emf.emfstore.internal.common.model.IdEObjectCollection#initMapping(java.util.Map, java.util.Map) */ public void initMapping(Map<EObject, String> eObjectToIdMap, Map<String, EObject> idToEObjectMap) { cachesInitialized = true; this.eObjectToIdMap = eObjectToIdMap; this.idToEObjectMap = idToEObjectMap; } /** * Creates a mapping for the given model element and the given {@link ModelElementId} within the cache. * * @param modelElement * a model element * @param modelElementId * a {@link ModelElementId} */ protected void putIntoCaches(EObject modelElement, String modelElementId) { // never overwrite existing IDs if (!eObjectToIdMap.containsKey(modelElement)) { eObjectToIdMap.put(modelElement, modelElementId); idToEObjectMap.put(modelElementId, modelElement); } } /** * Copies the collection. * * @param <T> * a collection type * @return the copied collection instance */ @SuppressWarnings("unchecked") public <T extends IdEObjectCollection> T copy() { final Copier copier = new IdEObjectCollectionCopier(); final T result = (T) copier.copy(this); ((IdEObjectCollectionImpl) result).cachesInitialized = true; copier.copyReferences(); return result; } /** * * {@inheritDoc} * * @see org.eclipse.emf.emfstore.internal.common.ESDisposable#dispose() */ public void dispose() { eObjectToIdMap.clear(); idToEObjectMap.clear(); clearAllocatedCaches(); cachesInitialized = false; } /** * Removes a model element and all its children from the cache. * * @param modelElement * a model element to be removed from the cache */ protected void removeModelElementAndChildrenFromCache(EObject modelElement) { if (allocatedEObjectToIdMap.containsKey(modelElement)) { return; } removeFromCaches(modelElement); for (final EObject child : ModelUtil.getAllContainedModelElements(modelElement, false)) { removeFromCaches(child); } } /** * Removes the given model element from the caches. * * @param modelElement * t#he model element to be removed from the caches */ private void removeFromCaches(EObject modelElement) { if (isCacheInitialized()) { final ModelElementId id = getModelElementId(modelElement); putIntoAllocatedCaches(modelElement, id); getEObjectsCache().remove(modelElement); getIdToEObjectCache().remove(id.getId()); } } /** * {@inheritDoc} * * @see org.eclipse.emf.emfstore.internal.common.model.IdEObjectCollection#allocateModelElementIds(java.util.Map) */ public void allocateModelElementIds(Map<EObject, ModelElementId> eObjectToIdMapping) { for (final Map.Entry<EObject, ModelElementId> entry : eObjectToIdMapping.entrySet()) { final EObject modelElement = entry.getKey(); final ModelElementId modelElementId = entry.getValue(); final EObject alreadyContainedElement = getModelElement(modelElementId); // if model element to be allocated is different to // model element with same ID in cache OR if model // element ID is not yet in cache if (alreadyContainedElement != modelElement) { if (alreadyContainedElement != null) { eObjectToIdMap.put(modelElement, modelElementId.getId()); idToEObjectMap.put(modelElementId.getId(), modelElement); } else { // do this even if the model element is already contained; // this is the case when a copied instance of the model element gets // added again putIntoAllocatedCaches(modelElement, modelElementId); } } } } /** * * {@inheritDoc} * * @see org.eclipse.emf.emfstore.internal.common.model.IdEObjectCollection#disallocateModelElementIds(java.util.Set) */ public void disallocateModelElementIds(Set<ModelElementId> modelElementIds) { for (final ModelElementId modelElementId : modelElementIds) { allocatedIdToEObjectMap.remove(modelElementId.getId()); allocatedEObjectToIdMap.values().remove(modelElementId.getId()); } } /** * * {@inheritDoc} * * @see org.eclipse.emf.emfstore.internal.common.model.IdEObjectCollection#clearAllocatedCaches() */ public void clearAllocatedCaches() { allocatedEObjectToIdMap.clear(); allocatedIdToEObjectMap.clear(); } /** * * {@inheritDoc} * * @see org.eclipse.emf.emfstore.internal.common.model.IdEObjectCollection#clearAllocatedCaches(java.util.Set) */ public void clearAllocatedCaches(Set<ModelElementId> modelElementIds) { allocatedIdToEObjectMap.keySet().removeAll(modelElementIds); allocatedEObjectToIdMap.values().removeAll(modelElementIds); } private void putIntoAllocatedCaches(EObject modelElement, ModelElementId modelElementId) { allocatedEObjectToIdMap.put(modelElement, modelElementId.getId()); allocatedIdToEObjectMap.put(modelElementId.getId(), modelElement); } private ModelElementId getNewModelElementID() { // if there is registered modelElementIdGenerator, use it if (modelElementIdGenerator != null) { final ESModelElementIdImpl modelElementId = (ESModelElementIdImpl) modelElementIdGenerator .generateModelElementId(this); return modelElementId.toInternalAPI(); } // else create it via ModelFactory return ModelFactory.eINSTANCE.createModelElementId(); } /** * * {@inheritDoc} * * @see org.eclipse.emf.emfstore.common.model.ESIdToEObjectMapping#get(java.lang.Object) */ public EObject get(ModelElementId modelElementId) { final EObject modelElement = getModelElement(modelElementId); if (modelElement != null) { return modelElement; } return getDeletedModelElement(modelElementId); } /** * {@inheritDoc} * * @see org.eclipse.emf.emfstore.internal.common.model.IdEObjectCollection#getIdToEObjectMapping() */ public Map<String, EObject> getIdToEObjectMapping() { return idToEObjectMap; } /** * {@inheritDoc} * * @see org.eclipse.emf.emfstore.internal.common.model.IdEObjectCollection#getEObjectToIdMapping() */ public Map<EObject, String> getEObjectToIdMapping() { return eObjectToIdMap; } /** * Removes a model element. * * @param modelElement the element to remove */ public void removeModelElement(EObject modelElement) { getModelElements().remove(modelElement); } }