/** * <copyright> * * Copyright (c) 2005, 2006, 2007, 2008 Springsite BV (The Netherlands) and others * 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: * Martin Taal - Initial API and implementation * Benjamin Cabé - See bugzilla 176356 * * </copyright> * * $Id: StoreResource.java,v 1.37 2009/02/24 12:05:05 mtaal Exp $ */ package org.eclipse.emf.teneo.resource; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.TreeMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.eclipse.emf.common.notify.Adapter; import org.eclipse.emf.common.notify.Notification; import org.eclipse.emf.common.notify.NotificationChain; import org.eclipse.emf.common.notify.impl.AdapterImpl; import org.eclipse.emf.common.notify.impl.NotificationImpl; import org.eclipse.emf.common.util.AbstractTreeIterator; import org.eclipse.emf.common.util.BasicEMap; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.common.util.TreeIterator; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.InternalEObject; import org.eclipse.emf.ecore.impl.BasicEObjectImpl; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.impl.ResourceImpl; import org.eclipse.emf.ecore.util.EContentsEList; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.emf.ecore.util.InternalEList; import org.eclipse.emf.ecore.xmi.XMIResource; import org.eclipse.emf.ecore.xmi.XMLResource; import org.eclipse.emf.teneo.EContainerRepairControl; import org.eclipse.emf.teneo.StoreValidationException; import org.eclipse.emf.teneo.util.FieldUtil; /** * General part of Store Resources. Main feature is that it keeps track of * changes to the resource content and that settrackingmodification will not * load unloaded elists. * * @author <a href="mailto:mtaal@elver.org">Martin Taal</a> * @version $Revision: 1.37 $ */ public abstract class StoreResource extends ResourceImpl { /** The name of the parameter used for the datastore name */ public static final String DS_NAME_PARAM = "dsname"; /** * Prefix to use when specifying query parameters in the uri or in the * property file */ public static final String QUERY_PREFIX = "query"; /** Load strategy */ public static final String LOAD_STRATEGY_PARAM = "loadStrategy"; /** The default strategy */ public static final String ADD_TO_CONTENTS = "addToContents"; /** The just set eResource */ public static final String SET_ERESOURCE = "setEResource"; /** * Don't do anything, risk of dangling_references but is better if objects * get added to other resources anyway. */ public static final String NOT_SET_ERESOURCE = "notSetEResource"; /** The logger */ private static Log log = LogFactory.getLog(StoreResource.class); /** * The list of objects which will be deleted at commit time. Needs to be a * list because of the order of deletion. */ protected List<EObject> removedEObjects = new ArrayList<EObject>(); /** * The set of objects loaded from the backend. objects which have been * removed are still part of this list. This is to prevent them from being * added to the newEObjects set. */ protected HashSet<EObject> loadedEObjects = new HashSet<EObject>(); // is used for more efficient access protected HashSet<EObject> loadedEObjectSet = new HashSet<EObject>(); /** * The set of new objects new EObjects are never part of the loadedEObjects, * when a newEObject is removed it is just removed from this set and not * added to the removed EObjects. new eobjects are never part of the * modifiedEObjects set. */ protected List<EObject> newEObjects = new ArrayList<EObject>(); // is used for more efficient access protected HashSet<EObject> newEObjectSet = new HashSet<EObject>(); /** * The set of changed objects, this contains the loadedEObjects which have * changed new EObjects are never part of this set */ protected HashSet<EObject> modifiedEObjects = new HashSet<EObject>(); /** * Apparently the super EMF resource classes have a different idea of loaded * During the unload action they again start loading from the db, this * should be prevented. */ protected boolean isUnLoading = false; /** The list of topclasses */ protected String[] topClassNames; /** * The list of queries if they are defined for this resource. If not set * (length is 0) then the resource will as default behavior read the * topclasses of the database. */ private String[] definedQueries = new String[0]; /** Send notifications under load */ private boolean sendNotificationsOnLoad = true; /** Is send notification set by parameter */ private boolean sendNotificationSetByParam = false; /** The load strategy */ private String loadStrategy = SET_ERESOURCE; /** * The constructor, gets an uri and retrieves the backing OJBStore */ public StoreResource(URI uri) { super(uri); final Map<String, String> params = decodeQueryString(uri.query()); if (params.get(LOAD_STRATEGY_PARAM) != null) { loadStrategy = params.get(LOAD_STRATEGY_PARAM); } if (log.isDebugEnabled()) { log.debug("Created " + this.getClass().getName() + " using uri: " + uri.toString()); } if (params.get(XMLResource.OPTION_DISABLE_NOTIFY) != null) { sendNotificationSetByParam = true; sendNotificationsOnLoad = "false".compareToIgnoreCase(params .get(XMLResource.OPTION_DISABLE_NOTIFY)) == 0; } else if (params.get(XMIResource.OPTION_DISABLE_NOTIFY) != null) { sendNotificationSetByParam = true; sendNotificationsOnLoad = "false".compareToIgnoreCase(params .get(XMIResource.OPTION_DISABLE_NOTIFY)) == 0; } setLoaded(false); // note this ugly statement is required because in this class // it is required to have direct access to the contents member // the call to getSuperContents will initialize it if (getContents().size() == 0) { log.debug("Initialized contents member"); } // set the intrinsicmap setIntrinsicIDToEObjectMap(new HashMap<String, EObject>()); } /** Sets topclasses */ protected void init(String[] topClasses) { topClassNames = topClasses; } public void setIsLoading(boolean loading) { isLoading = loading; } // ----------------------------------- Defined Queries // ---------------------------------- /** Sets the defined queries */ public void setDefinedQueries(String[] qrys) { log.debug("Setting defined queries of resource " + getURI().toString() + "/" + this.getClass().getName()); for (String element : qrys) { log.debug("Adding query: " + element); } definedQueries = qrys; } /** Returns true if there are defined queries */ public boolean definedQueriesPresent() { return definedQueries.length > 0; } /** * Returns the current array of defined queries. Note if there are no * definedQueries then an array of length 0 is returned. */ public String[] getDefinedQueries() { return definedQueries; } /** Returns a list of queries if they are passed as parameters */ protected String[] getQueries(Map<?, ?> params) { final ArrayList<String> queries = new ArrayList<String>(); for (Object key : params.keySet()) { if (((String) key).startsWith(QUERY_PREFIX)) { queries.add((String) params.get(key)); } } return queries.toArray(new String[queries.size()]); } /** Get the parameter from the hashmap, if not found then throw an exception */ protected String getParam(Map<String, String> params, String paramName, String report) { final String param = params.get(paramName); if (param == null) { throw new StoreResourceException("Parameter " + paramName + " missing in querystring: " + report); } return param; } /** Decode the query string in a hashmap */ protected Map<String, String> decodeQueryString(String qryStr) { final TreeMap<String, String> result = new TreeMap<String, String>(); if (qryStr == null) { return result; } final String[] qryParts = qryStr.split("&"); for (final String qryPart : qryParts) { final String fieldName = qryPart.substring(0, qryPart.indexOf('=')); final String fieldValue = URI.decode(qryPart.substring(qryPart .indexOf('=') + 1)); result.put(fieldName, fieldValue); } return result; } // ------------------------------ Subclass methods // --------------------------------- /** * Returns an array of EObjects which refer to a certain EObject, note if * the array is of length zero then no refering EObjects where found. */ public abstract Object[] getCrossReferencers(EObject referedTo); /** Load implemented by subclass */ protected abstract List<EObject> loadResource(Map<?, ?> options); /** Save implemented by subclass */ protected abstract void saveResource(Map<?, ?> options); // ------------------------------------ Load // ------------------------------------------ /** Returns true if the resource is unloading */ public boolean isUnLoading() { return isUnLoading; } /** Loads the resource */ @Override public void load(Map<?, ?> options) { if (isUnLoading) { return; } if (isLoaded()) { return; } if (isLoading()) { return; } String option; if (options != null && (option = (String) options .get(XMLResource.OPTION_DISABLE_NOTIFY)) != null) { sendNotificationsOnLoad = "false".compareToIgnoreCase(option) == 0; } else if (options != null && (option = (String) options .get(XMIResource.OPTION_DISABLE_NOTIFY)) != null) { sendNotificationsOnLoad = "false".compareToIgnoreCase(option) == 0; } else if (!sendNotificationSetByParam) { sendNotificationsOnLoad = true; } if (options != null && options.get(LOAD_STRATEGY_PARAM) != null) { loadStrategy = (String) options.get(LOAD_STRATEGY_PARAM); } isLoading = true; Notification notification = setLoaded(true); try { clearChangeTrackerArrays(); List<EObject> list = loadResource(options); for (EObject eObject : list) { addToContent((InternalEObject) eObject); } } finally { if (notification != null) { eNotify(notification); } setModified(false); isLoading = false; } } /** Add to this resource */ protected void addToContent(InternalEObject eObject) { // note direct access to super member if (!getLocalContents().contains(eObject)) { final NotificationChain notifications = getLocalContents() .basicAdd(eObject, null); if (notifications != null && sendNotificationsOnLoad) { notifications.dispatch(); } setEResource(eObject, true); attached(eObject); } } protected void addUsingContainmentStructure(InternalEObject eObject) { final boolean oldLoading = isLoading(); try { setIsLoading(true); // find the topcontainer InternalEObject currentEObject = eObject; while (currentEObject.eContainer() != null && !currentEObject.eContainmentFeature().isResolveProxies()) { currentEObject = (InternalEObject) currentEObject.eContainer(); } // if the topcontainer is not yet part of the resource then add it if (!loadedEObjectSet.contains(currentEObject)) { final NotificationChain notifications = getLocalContents() .basicAdd(eObject, null); if (notifications != null && sendNotificationsOnLoad) { notifications.dispatch(); } setEResource(eObject, true); // attached will also call the children attached(currentEObject); } else if (!loadedEObjectSet.contains(eObject)) { // container is already part of the resource // just add the current object to the loaded eobjects attached(eObject); } } finally { setIsLoading(oldLoading); } } // This method is called when ereferences are resolved. public void addToContentOrAttach(InternalEObject eObject, EReference eReference) { final boolean oldLoading = isLoading(); try { if (loadStrategy.compareTo(ADD_TO_CONTENTS) == 0) { if (eObject.eResource() != null && eObject.eResource() != this) { return; } // always add to the resource, this will change // the contents of the resource addUsingContainmentStructure(eObject); } else if (eReference.isContainment()) { // if resolve proxies then it is allowed to have child objects // in other // resources if (eReference.isResolveProxies() && eObject.eResource() != null && eObject.eResource() != this) { return; } // only add if contained, just add to the loaded eobjects lists assert (eObject.eContainer().eResource() == this); attached(eObject); } else if (loadStrategy.compareTo(SET_ERESOURCE) == 0) { // this is not the nicest solution because everyone is added to // the top of the // resource. // but it prevents dangling references // when objects are not added explicitly to a resource setEResource(eObject, false); attached(eObject); } } finally { setIsLoading(oldLoading); } } /** * Called by subclass * * /** Saves the resource */ @Override public void save(Map<?, ?> options) { boolean err = true; try { setAllowNotifications(false); validateContents(); saveResource(options); err = false; } finally { // now clear the changed eobjects and move the new objects // to the loaded eobjects setAllowNotifications(true); if (!err) { modifiedEObjects.clear(); removedEObjects.clear(); loadedEObjects.addAll(newEObjects); loadedEObjectSet.addAll(newEObjects); newEObjects.clear(); newEObjectSet.clear(); setModified(false); } } } /** * Clears different lists to start with an empty resource again. */ @Override protected void doUnload() { isUnLoading = true; super.doUnload(); clearChangeTrackerArrays(); isUnLoading = false; } /* * Javadoc copied from interface. */ @Override public EList<EObject> getContents() { if (contents == null) { contents = new LocalContentsEList(); } return contents; } protected LocalContentsEList getLocalContents() { return (LocalContentsEList) getContents(); } // this specific ContentsElist overrides inverseremove to handle the // following case // using queries it is possible to load a parent and child both in the root // of the // resource. During unload of the resource the child is removed from the // parent // see the BasicEObjectImpl.eSetResource implementation. This is undesirable // therefore // the inverseRemove method is overridden. // See bugzilla 227500 protected class LocalContentsEList extends ContentsEList<EObject> { private static final long serialVersionUID = 1L; @Override public NotificationChain inverseRemove(EObject object, NotificationChain notifications) { if (!isUnLoading()) { return super.inverseRemove(object, notifications); } if (!unloadingContents.contains(object)) { return super.inverseRemove(object, notifications); } final InternalEObject eObject = (InternalEObject) object; final InternalEObject eContainer = eObject.eInternalContainer(); if (eContainer == null) { return super.inverseRemove(object, notifications); } // specific case which works fine as in this case the // object is not removed from its container if (eObject.eContainmentFeature().isResolveProxies()) { return super.inverseRemove(object, notifications); } // can this ever happen? if (!(eObject instanceof BasicEObjectImpl)) { return super.inverseRemove(object, notifications); } // now the only thing remaining is mimick the inverseRemove without // removal // from the container // this is the invariant: // eDirectResource() != null && eContainer != null // in this case the directresource has to be set, using java // reflection to handle that final Resource oldResource = eObject.eDirectResource(); if (oldResource != null) { notifications = ((InternalEList<?>) oldResource.getContents()) .basicRemove(this, notifications); } FieldUtil.callMethod(eObject, "eSetDirectResource", new Object[] { null }); return notifications; } } /** * Validate the contents of the resource, only the changed/new EObjects are * checked */ protected void validateContents() throws StoreValidationException { // get the changed or new eobjects final ArrayList<EObject> newOrChangedObjects = new ArrayList<EObject>( newEObjects); newOrChangedObjects.addAll(modifiedEObjects); log.debug("Validating contents of resource " + uri + " approx. " + newOrChangedObjects.size() + " will be checked"); final ArrayList<org.eclipse.emf.common.util.Diagnostic> diags = new ArrayList<org.eclipse.emf.common.util.Diagnostic>(); for (int i = 0; i < newOrChangedObjects.size(); i++) { final InternalEObject obj = (InternalEObject) newOrChangedObjects .get(i); // ensure that the resource is set correctly before validating if (obj.eResource() != this) { assert (obj.eResource() == this); } EContainerRepairControl.setEResourceToAlLContent(obj, this); if (newOrChangedObjects.contains(obj.eContainer())) { continue; // they will be checked as part of their container } final org.eclipse.emf.common.util.Diagnostic diag = validateObject(newOrChangedObjects .get(i)); if (diag != null) { diags.add(diag); } } log.debug("Found " + diags.size() + " errors "); if (diags.size() > 0) { throw new StoreValidationException(diags .toArray(new org.eclipse.emf.common.util.Diagnostic[diags .size()])); } } /** Copied from IBM tutorial, validates one eobject */ public org.eclipse.emf.common.util.Diagnostic validateObject(EObject eObject) { org.eclipse.emf.common.util.Diagnostic diagnostic = NonLoadingDiagnostician.INSTANCE .validate(eObject); if (diagnostic.getSeverity() == org.eclipse.emf.common.util.Diagnostic.ERROR) { return diagnostic; } return null; } // -------------------------- Content Iteration // ----------------------------------- // /** // * Override load to force a load when getContents is called without a // previous load call. // */ // @Override // public EList<EObject> getContents() { // // the getContents should not load but should return an // // empty string if not yet loaded. // // if (!isLoaded() && !isLoading) { // // load(null); // // } // // // // and then do our thing! // return super.getContents(); // } // // /** // * Extra method to allow subclasses to easily reach the contents of the // superclass of this class // */ // protected EList<EObject> getSuperContents() { // return super.getContents(); // } // -------------------------------- Change Tracker // ---------------------------------- /** Clears the change tracker arrays */ private void clearChangeTrackerArrays() { removedEObjects.clear(); newEObjects.clear(); newEObjectSet.clear(); loadedEObjects.clear(); loadedEObjectSet.clear(); modifiedEObjects.clear(); } /** * Keeps track of changed objects, CHECK: currently if a tree of objects has * been added to this resource and afterwards a child in the tree is changed * then the child is added to the modifiedEObjects list while its containing * parent is part of the addedEObjects list. This should maybe be prevented * here but this can come at some cost as it means that the complete * container path has to be loaded for each modification. */ public void modifiedEObject(EObject eObject) { // if (addedEObjects.contains(eObject)) return; // see remark above, if // childs are added to the modified list // then childs also if (loadedEObjectSet.contains(eObject) && !modifiedEObjects.contains(eObject)) { assert (!newEObjectSet.contains(eObject)); modifiedEObjects.add(eObject); } } /** Keeps track of new objects */ public void addedEObject(EObject eObject) { if (isLoading()) { if (removedEObjects.contains(eObject)) { // special case an eobject was removed from the resource but is // readded during load of an elist // the remove will be done at save return; } // assert (!removedEObjects.contains(eObject)); assert (!loadedEObjectSet.contains(eObject)); loadedEObjects.add(eObject); loadedEObjectSet.add(eObject); } else { // assert (!removedEObjects.contains(eObject)); assert (!newEObjectSet.contains(eObject)); if (removedEObjects.remove(eObject)) { modifiedEObjects.add(eObject); } else { newEObjects.add(eObject); newEObjectSet.add(eObject); } } } /** Keeps track of removed objects */ public void removedEObject(EObject eObject) { assert (!removedEObjects.contains(eObject)); if (newEObjectSet.contains(eObject)) { assert (newEObjects.contains(eObject)); newEObjects.remove(eObject); // just remove from this list newEObjectSet.remove(eObject); return; } if (!loadedEObjectSet.contains(eObject)) { assert (loadedEObjects.contains(eObject)); } assert (!removedEObjects.contains(eObject)); if (!(eObject instanceof BasicEMap.Entry)) { removedEObjects.add(eObject); } loadedEObjects.remove(eObject); loadedEObjectSet.remove(eObject); modifiedEObjects.remove(eObject); } /** Object is attached, is overridden to use non-resolving iterator */ @Override public void attached(EObject eObject) { attachedHelper(eObject); for (Iterator<EObject> tree = getNonResolvingContent(eObject) .basicIterator(); tree.hasNext();) { final Object obj = tree.next(); // before this called: // attachedHelper((EObject) obj); // however this resulted in child elements (which were loaded) not // being // added to the internal id map attached((EObject) obj); } } /** Detached means deleted from resource */ @Override public void detached(EObject eObject) { detachedHelper(eObject); for (EObject object : getNonResolvingContent(eObject)) { detachedHelper(object); } } /** * Attached the object to this resource, This object will be stored at the * next save action. Also handles the specific id generation used for * different resource impl. * * Note that this method does not add the object to the * resource.getContents(). It just adds the object to the internal lists of * this resource. */ // 20 April 2008: cleaned up side-effects of this method, add to contents, // setting // eresource has been disabled, this should be done in calling methods @Override protected void attachedHelper(EObject eObject) { // also attach the container // if (eObject.eContainer() != null && eObject.eContainer().eResource() // == null && // !eObject.eContainmentFeature().isResolveProxies()) { // attached(eObject.eContainer()); // } // a bit strange as an object can only be contained once but this can // happen if someone // adds an object to a resource directly and then later add this same // object as a child // to a container if (newEObjectSet.contains(eObject) || loadedEObjectSet.contains(eObject)) { return; } // Already belongs to another resource if (eObject.eResource() != null && eObject.eResource() != this) { return; } addedEObject(eObject); // if (eObject instanceof InternalEObject && eObject.eResource() == // null) { // setEResource((InternalEObject) eObject, false); // } // NOTE: should this really happen here? My feel is that this should // be cleaned up and be done outside of this method // This is required here because the attached method is called // recursively for the container, see the first if in this method // only add an eobject to contents if it does not have a container or if // the container // relation allows resolve proxies (container and contained can be in // different resources) // and the load strategy is correct // if ((eObject.eContainer() == null || eObject.eContainmentFeature() == // null || // eObject.eContainmentFeature() // .isResolveProxies()) && // !getContents().contains(eObject) && // loadStrategy.compareTo(ADD_TO_CONTENTS) == 0) { // // note direct access to super contents // final NotificationChain notifications = contents.basicAdd(eObject, // null); // if (notifications != null && sendNotificationsOnLoad) { // notifications.dispatch(); // } // } if (isTrackingModification()) { eObject.eAdapters().add(modificationTrackingAdapter); } Map<String, EObject> map = getIntrinsicIDToEObjectMap(); if (map != null) { String id = EcoreUtil.getID(eObject); if (id != null) { map.put(id, eObject); } else { id = getURIFragment(eObject); if (id != null) { map.put(id, eObject); } } } // now also attach all ereferences with single values which are // contained for (EReference eref : eObject.eClass().getEAllReferences()) { if (!eref.isMany() && eObject.eGet(eref, false) != null) { // the // ismanies // are handled // differently final Resource res = ((EObject) eObject.eGet(eref)).eResource(); if (res == null) { // attach it to this resource because it has // no other final InternalEObject referedTo = (InternalEObject) eObject .eGet(eref); addToContentOrAttach(referedTo, eref); } } } } /** * Add to the contents of this resource and dispatch if send notification. * It also takes care of attaching this object and its contained children to * the internal lists and setting the eResource. * * Approaches: - Set eResource and add to contents (if no econtainer) - Just * set eResource and add to contents if it does not have a container public * void addToResource(InternalEObject eObject, boolean forceAddToContents) { * // if it has an econtainer then also add the econtainer if * (eObject.eContainer() != null && eObject.eContainer().eResource() == * null) { addToResource((InternalEObject)eObject.eContainer(), false); } // * if the econtainer is already part of this resource then just attach if * (!forceAddToContents && eObject.eContainer() != null && * eObject.eContainer().eResource() == this) { attached(eObject); return; } * // probably lazy load action of non-containment, already part of this * just return if (!forceAddToContents && eObject.eResource() == this && * !loadedEObjects.contains(eObject) && !removedEObjects.contains(eObject) * && !newEObjects.contains(eObject)) { attached(eObject); return; } // * already part of another resource if (!forceAddToContents && * eObject.eResource() != null) { return; } * * final ContentsEList elist = (ContentsEList) super.getContents(); if * (elist.contains(eObject)) { // can happen because of extends, * polymorphism return; } // fill in the resource, do not use the normal add * method because it // is possible that a child of a container is loaded, * in that case // the normal add will remove the container of the object * when the // resource is set in the child object, this issue can happen * with // direct reads using queries. NotificationChain notification = * null; if (loadStrategy.compareToIgnoreCase(ADD_TO_CONTENTS) == 0 || * forceAddToContents) { notification = elist.basicAdd(eObject, null); } if * (eObject.eResource() == null || (forceAddToContents && * eObject.eResource() != this)) { setEResource(eObject, * forceAddToContents); } // attached(eObject); if (sendNotificationsOnLoad * && notification != null) { notification.dispatch(); } } */ /** * Sets the eresource by walking up the containment structure and finding * the highest parent. There the resource is set.If the resource is already * set nothing is done. */ public void setEResource(InternalEObject eobj, boolean force) { if (eobj.eResource() != null && eobj.eResource() != this && !force) { return; } InternalEObject currentEObject = eobj; while (currentEObject.eContainer() != null && !currentEObject.eContainmentFeature().isResolveProxies()) { currentEObject = (InternalEObject) currentEObject.eContainer(); } if (currentEObject.eResource() != this) { currentEObject.eSetResource(this, null); attached(currentEObject); } } /** * Overridden to also support persistence specific id instead of single emf * id */ @Override protected void detachedHelper(EObject eObject) { // support move to other resource if (eObject.eResource() != this) { removedEObjects.remove(eObject); modifiedEObjects.remove(eObject); loadedEObjects.remove(eObject); loadedEObjectSet.remove(eObject); newEObjects.remove(eObject); newEObjectSet.remove(eObject); return; } removedEObject(eObject); Map<String, EObject> map = getIntrinsicIDToEObjectMap(); if (map != null) { String id = EcoreUtil.getID(eObject); if (id != null) { map.remove(id); } else { id = getURIFragment(eObject); if (id != null) { map.remove(id); } } } if (isTrackingModification()) { eObject.eAdapters().remove(modificationTrackingAdapter); } } /** Returns the added objects */ public List<EObject> getNewEObjects() { return newEObjects; } public HashSet<EObject> getNewEObjectSet() { return newEObjectSet; } /** Return the new eobjects */ public HashSet<EObject> getModifiedEObjects() { return modifiedEObjects; } /** Return the new eobjects */ public List<EObject> getRemovedEObjects() { return removedEObjects; } /** * Overridden to make it non resolving for not loaded elists and proxy * eobjects */ @Override public void setTrackingModification(boolean isTrackingModification) { boolean oldIsTrackingModification = modificationTrackingAdapter != null; if (oldIsTrackingModification != isTrackingModification) { if (isTrackingModification) { // note the global modification adapter is set after the for // loop // because in the for loop extra objects can be added to the // resource // these objects would get two adapters, once in the // attachedHelper method // and once here, btw can also prevent this by adding a contains // check within // for loop final Adapter adapter = createModificationTrackingAdapter(); for (Iterator<EObject> i = getNonResolvingAllContents(); i .hasNext();) { EObject eObject = i.next(); assert (!eObject.eAdapters().contains(adapter)); eObject.eAdapters().add(adapter); } modificationTrackingAdapter = adapter; } else { Adapter oldModificationTrackingAdapter = modificationTrackingAdapter; for (Iterator<EObject> i = getNonResolvingAllContents(); i .hasNext();) { EObject eObject = i.next(); assert (eObject.eAdapters() .contains(modificationTrackingAdapter)); eObject.eAdapters().remove(oldModificationTrackingAdapter); } modificationTrackingAdapter = null; } } if (eNotificationRequired()) { Notification notification = new NotificationImpl(Notification.SET, oldIsTrackingModification, isTrackingModification) { @Override public Object getNotifier() { return StoreResource.this; } @Override public int getFeatureID(Class<?> expectedClass) { return RESOURCE__IS_TRACKING_MODIFICATION; } }; eNotify(notification); } } /** * Always returns a non-resolving iterator because resolving is defined on * model level and the resource should adhere to that */ @SuppressWarnings("serial") protected TreeIterator<EObject> getNonResolvingAllContents() { return new AbstractTreeIterator<EObject>(this, false) { @Override public Iterator<EObject> getChildren(Object object) { return object == StoreResource.this ? getLocalContents() .basicIterator() : getNonResolvingContent( (EObject) object).basicIterator(); } }; } @Override // Method is called at unload, only loaded content should be iterated protected TreeIterator<EObject> getAllProperContents(List<EObject> contents) { return getNonResolvingAllContents(); } /** Returns a non-resolving contents elist for an eobject */ protected EContentsEList<EObject> getNonResolvingContent(EObject eObject) { return NonLoadingEContentsEList.create(eObject, false); } /* * (non-Javadoc) * * @seeorg.eclipse.emf.ecore.resource.impl.ResourceImpl# * createModificationTrackingAdapter() */ @Override protected Adapter createModificationTrackingAdapter() { return new StoreModificationTrackingAdapter(); } // Enable or disable notifications for the current content protected void setAllowNotifications(boolean allow) { for (Iterator<?> i = getNonResolvingAllContents(); i.hasNext();) { EObject eObject = (EObject) i.next(); eObject.eSetDeliver(allow); } } /** * An adapter implementation for tracking resource modification, registers * changed objects */ protected class StoreModificationTrackingAdapter extends AdapterImpl { @Override public void notifyChanged(Notification notification) { switch (notification.getEventType()) { case Notification.SET: case Notification.UNSET: case Notification.MOVE: { if (!notification.isTouch()) { setModified(true); modifiedEObject((EObject) notification.getNotifier()); } break; } case Notification.ADD: case Notification.REMOVE: case Notification.ADD_MANY: case Notification.REMOVE_MANY: { setModified(true); break; } } } } /** * @return the sendNotificationsOnLoad */ public boolean isSendNotificationsOnLoad() { return sendNotificationsOnLoad; } }