Skip to main content
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorOlivier Constant2015-07-02 13:49:54 +0000
committerOlivier Constant2015-07-02 13:49:54 +0000
commit791a743dc9e2811a4d57b53731bd71f302f87db1 (patch)
tree078db028429a1546c7d54826adb3f95ad4b18f1e
parent355335ee514c53b62411d6e078a54f2a7031502f (diff)
downloadorg.eclipse.emf.diffmerge.core-791a743dc9e2811a4d57b53731bd71f302f87db1.tar.gz
org.eclipse.emf.diffmerge.core-791a743dc9e2811a4d57b53731bd71f302f87db1.tar.xz
org.eclipse.emf.diffmerge.core-791a743dc9e2811a4d57b53731bd71f302f87db1.zip
Adaptation of f500a59c6fe42ec4951586a983058c9ac233404b to branch v0.4.x
Change-Id: I65541940fafa99304a66a8cfcd1081696b52bbbb Signed-off-by: Olivier Constant <olivier.constant@thalesgroup.com>
-rw-r--r--plugins/org.eclipse.emf.diffmerge/src/org/eclipse/emf/diffmerge/impl/scopes/FragmentedModelScope.java1316
1 files changed, 663 insertions, 653 deletions
diff --git a/plugins/org.eclipse.emf.diffmerge/src/org/eclipse/emf/diffmerge/impl/scopes/FragmentedModelScope.java b/plugins/org.eclipse.emf.diffmerge/src/org/eclipse/emf/diffmerge/impl/scopes/FragmentedModelScope.java
index 485535b9..b454c816 100644
--- a/plugins/org.eclipse.emf.diffmerge/src/org/eclipse/emf/diffmerge/impl/scopes/FragmentedModelScope.java
+++ b/plugins/org.eclipse.emf.diffmerge/src/org/eclipse/emf/diffmerge/impl/scopes/FragmentedModelScope.java
@@ -1,653 +1,663 @@
-/**
- * <copyright>
- *
- * Copyright (c) 2010-2014 Thales Global Services S.A.S.
- * 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:
- * Thales Global Services S.A.S. - initial API and implementation
- *
- * </copyright>
- */
-package org.eclipse.emf.diffmerge.impl.scopes;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.NoSuchElementException;
-import java.util.Set;
-
-import org.eclipse.emf.common.notify.Adapter;
-import org.eclipse.emf.common.util.TreeIterator;
-import org.eclipse.emf.common.util.URI;
-import org.eclipse.emf.diffmerge.api.scopes.IFragmentedModelScope;
-import org.eclipse.emf.diffmerge.api.scopes.IPersistentModelScope;
-import org.eclipse.emf.diffmerge.util.ModelImplUtil;
-import org.eclipse.emf.diffmerge.util.structures.FArrayList;
-import org.eclipse.emf.diffmerge.util.structures.FOrderedSet;
-import org.eclipse.emf.diffmerge.util.structures.HashBinaryRelation;
-import org.eclipse.emf.diffmerge.util.structures.IBinaryRelation;
-import org.eclipse.emf.ecore.EObject;
-import org.eclipse.emf.ecore.EReference;
-import org.eclipse.emf.ecore.resource.Resource;
-import org.eclipse.emf.ecore.resource.ResourceSet;
-import org.eclipse.emf.ecore.util.ECrossReferenceAdapter;
-import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
-import org.eclipse.emf.edit.domain.EditingDomain;
-import org.eclipse.emf.edit.domain.IEditingDomainProvider;
-
-
-/**
- * A model scope which covers the contents of a given resource plus the contents
- * of other resources which are being logically included or referenced according
- * to custom rules, recursively.
- * This kind of scope is typically useful for loading models which are fragmented
- * among multiple resources via containment references and cross-references.
- * The scope is built incrementally: the additional resources are discovered dynamically
- * the first time the scope is being explored using getAllContents().
- * A fix point is guaranteed to be found and the exploration of the scope is guaranteed
- * to be complete every time.
- * This implementation assumes an initialization phase that consists in a full exploration
- * of the scope. No other action on the scope or modification of the resource set is
- * expected to happen during this initialization phase.
- * EMF undo/redo is supported because the local state does not change after full exploration,
- * unless unload() has been called.
- * @author Olivier Constant
- */
-public class FragmentedModelScope extends AbstractEditableModelScope
-implements IFragmentedModelScope.Editable {
-
- /** Whether the resources should be opened in read-only mode */
- private final boolean _isReadOnly;
-
- /** The optional editing domain that encompasses the scope */
- protected EditingDomain _editingDomain;
-
- /** The non-null resource set that encompasses the scope */
- protected final ResourceSet _resourceSet;
-
- /** The non-null, non-empty ordered set of resources defining the scope.
- * It includes _rootResources, _includedResources and _referencedResources. */
- protected final List<Resource> _resources;
-
- /** The non-null, non-empty ordered subset of the resources which are roots */
- protected final List<Resource> _rootResources;
-
- /** The non-null, potentially empty set of resources initially present
- * in the resource set before the scope was loaded (content is temporary) */
- protected final Set<Resource> _initiallyPresentResources;
-
- /** The non-null, initially empty set of resources that have been loaded due
- * to the exploration of the scope */
- protected final Set<Resource> _loadedResources;
-
- /** The resource inclusion relationship */
- protected final IBinaryRelation.Editable<Resource, Resource> _includedResources;
-
- /** The resource referencing relationship */
- protected final IBinaryRelation.Editable<Resource, Resource> _referencedResources;
-
- /** The possible states of the scope, in order */
- protected enum ScopeState {
- INITIALIZED, // The scope has just been created
- LOADED, // The scope has been loaded but not fully explored
- FULLY_EXPLORED, // The scope has been loaded and fully explored
- UNLOADED // The scope has been unloaded
- }
-
- /** The current state of the scope */
- protected ScopeState _state;
-
-
- /**
- * Constructor
- * @param resource_p a non-null resource that belongs to a non-null resource set
- * @param readOnly_p whether the scope is in read-only mode, if applicable
- * Precondition: resource_p != null && resource_p.getResourceSet() != null
- */
- public FragmentedModelScope(Resource resource_p, boolean readOnly_p) {
- this(resource_p.getURI(), resource_p.getResourceSet(), readOnly_p);
- }
-
- /**
- * Constructor
- * @param uri_p a non-null URI of the resource to load as root
- * @param editingDomain_p a non-null editing domain that encompasses the scope
- * @param readOnly_p whether the scope should be read-only, if supported
- */
- public FragmentedModelScope(URI uri_p, EditingDomain editingDomain_p, boolean readOnly_p) {
- this(Collections.singleton(uri_p), editingDomain_p, readOnly_p);
- }
-
- /**
- * Constructor
- * @param uri_p a non-null resource URI
- * @param resourceSet_p a non-null resource set where the resources must be loaded
- * @param readOnly_p whether the scope is in read-only mode, if applicable
- */
- public FragmentedModelScope(URI uri_p, ResourceSet resourceSet_p, boolean readOnly_p) {
- this(Collections.singleton(uri_p), resourceSet_p, readOnly_p);
- }
-
- /**
- * Constructor
- * @param uris_p a non-null collection of URIs of resources to load as roots
- * @param editingDomain_p a non-null editing domain that encompasses the scope
- * @param readOnly_p whether the scope should be read-only, if supported
- */
- public FragmentedModelScope(Collection<URI> uris_p, EditingDomain editingDomain_p, boolean readOnly_p) {
- this(uris_p, editingDomain_p.getResourceSet(), readOnly_p);
- _editingDomain = editingDomain_p;
- }
-
- /**
- * Constructor
- * @param uris_p a non-null collection of URIs of resources to load as roots
- * @param resourceSet_p a non-null resource set where the resources must be loaded
- * @param readOnly_p whether the scope is in read-only mode, if applicable
- */
- public FragmentedModelScope(Collection<URI> uris_p, ResourceSet resourceSet_p, boolean readOnly_p) {
- this(resourceSet_p, readOnly_p);
- for (URI uri : uris_p) {
- Resource rootResource = _resourceSet.getResource(uri, false);
- if (rootResource == null)
- rootResource = _resourceSet.createResource(uri);
- _rootResources.add(rootResource);
- addNewResource(rootResource);
- }
- }
-
- /**
- * Common constructor
- * @param resourceSet_p the non-null resource set that encompasses the scope
- * @param readOnly_p whether the scope is in read-only mode, if applicable
- */
- protected FragmentedModelScope(ResourceSet resourceSet_p, boolean readOnly_p) {
- _state = ScopeState.INITIALIZED;
- _isReadOnly = readOnly_p;
- _resourceSet = resourceSet_p;
- _resources = new ArrayList<Resource>();
- _rootResources = new ArrayList<Resource>();
- _includedResources = new HashBinaryRelation<Resource, Resource>();
- _referencedResources = new HashBinaryRelation<Resource, Resource>();
- _initiallyPresentResources = new HashSet<Resource>();
- _initiallyPresentResources.addAll(_resourceSet.getResources());
- _loadedResources = new HashSet<Resource>();
- if (_resourceSet instanceof IEditingDomainProvider)
- _editingDomain = ((IEditingDomainProvider)_resourceSet).getEditingDomain();
- }
-
- /**
- * @see org.eclipse.emf.diffmerge.api.scopes.IEditableModelScope#add(org.eclipse.emf.ecore.EObject)
- */
- public boolean add(EObject element_p) {
- boolean result = false;
- Resource defaultResource = getResourceForNewRoot(element_p);
- if (defaultResource != null) {
- defaultResource.getContents().add(element_p);
- result = true;
- }
- return result;
- }
-
- /**
- * @see org.eclipse.emf.diffmerge.impl.scopes.AbstractModelScope#add(EObject, EReference, EObject)
- */
- @Override
- public boolean add(EObject source_p, EReference reference_p, EObject value_p) {
- Resource oldResource = value_p.eResource();
- boolean wasRoot = oldResource != null && oldResource.getContents().contains(value_p);
- Object formerId = getExtrinsicID(value_p);
- boolean result = super.add(source_p, reference_p, value_p);
- if (wasRoot && reference_p.isContainment())
- oldResource.getContents().remove(value_p); // Not automatically handled
- if (formerId != null)
- // In case resource has changed, thus changing the extrinsic ID
- setExtrinsicID(value_p, formerId);
- return result;
- }
-
- /**
- * Add the given resource to the set of covered resources
- * @param resource_p a non-null resource which is not contained in getResources()
- * Precondition: !getResources().contains(resource_p)
- * Postcondition: getResources().contains(resource_p)
- */
- protected void addNewResource(Resource resource_p) {
- _resources.add(resource_p);
- }
-
- /**
- * Return whether the given collection contains proxies relative to the
- * given holding element whose target is already loaded
- * @param collection_p a non-null collection of elements
- * @param source_p a non-null element potentially holding proxies
- */
- protected boolean containsUnnecessaryProxies(Collection<EObject> collection_p,
- EObject source_p) {
- for (EObject current : collection_p) {
- if (current.eIsProxy() && current != ModelImplUtil.resolveIfLoaded(current, source_p))
- return true;
- }
- return false;
- }
-
- /**
- * Called as soon as full scope exploration has been done
- */
- protected void explorationFinished() {
- _state = ScopeState.FULLY_EXPLORED;
- _loadedResources.addAll(_resourceSet.getResources());
- _loadedResources.removeAll(_initiallyPresentResources);
- _initiallyPresentResources.clear();
- // Handling read-only on loaded resources
- if (isReadOnly() && _editingDomain instanceof AdapterFactoryEditingDomain) {
- AdapterFactoryEditingDomain afEditingDomain = (AdapterFactoryEditingDomain)_editingDomain;
- Map<Resource, Boolean> readOnlyMap = afEditingDomain.getResourceToReadOnlyMap();
- if (readOnlyMap != null) {
- for (Resource loadedResource : _loadedResources) {
- readOnlyMap.put(loadedResource, Boolean.TRUE);
- }
- }
- }
- }
-
- /**
- * @see org.eclipse.emf.diffmerge.impl.scopes.AbstractModelScope#get(EObject, EReference)
- */
- @Override
- public List<EObject> get(EObject source_p, EReference reference_p) {
- // Current result, may require resolution of in-scope proxies
- List<EObject> result = super.get(source_p, reference_p);
- boolean requiresResolution = containsUnnecessaryProxies(result, source_p);
- if (requiresResolution) // Recompute result if needed
- result = get(source_p, reference_p, true);
- return result;
- }
-
- /**
- * @see org.eclipse.emf.diffmerge.impl.scopes.AbstractModelScope#getAllContents()
- */
- @Override
- public TreeIterator<EObject> getAllContents() {
- return new ExpandingMultiResourceTreeIterator();
- }
-
- /**
- * @see org.eclipse.emf.diffmerge.api.scopes.IModelScope#getContents()
- * Result is guaranteed to be accurate only if hasBeenExplored().
- */
- public List<EObject> getContents() {
- List<EObject> result = new FArrayList<EObject>();
- for (Resource resource : _rootResources)
- result.addAll(resource.getContents());
- return Collections.unmodifiableList(result);
- }
-
- /**
- * Return the cross-references, for the given element, which are in the scope
- * @param element_p a non-null element belonging to the scope
- * @return a non-null, potentially empty collection of non-containment, non-container references
- */
- protected Collection<EReference> getCrossReferencesInScope(EObject element_p) {
- // Override if needed
- return new ArrayList<EReference>();
- }
-
- /**
- * @see IPersistentModelScope#getExtrinsicID(EObject)
- */
- @Override
- public Object getExtrinsicID(EObject element_p) {
- // Increases visibility
- return super.getExtrinsicID(element_p);
- }
-
- /**
- * @see org.eclipse.emf.diffmerge.api.scopes.IPersistentModelScope#getHoldingResource()
- */
- public Resource getHoldingResource() {
- return _rootResources.isEmpty()? null: _rootResources.get(0);
- }
-
- /**
- * @see org.eclipse.emf.diffmerge.api.scopes.IFragmentedModelScope#getIncludedResources(org.eclipse.emf.ecore.resource.Resource)
- * Result is guaranteed to be accurate only if hasBeenExplored().
- */
- public List<Resource> getIncludedResources(Resource resource_p) {
- return _includedResources.get(resource_p);
- }
-
- /**
- * Return load options for loading the given resource
- * @param resource_p a non-null resource
- * @return a non-null, potentially empty, modifiable option map
- */
- protected Map<Object, Object> getLoadOptions(Resource resource_p) {
- // Override if needed
- return new HashMap<Object, Object>();
- }
-
- /**
- * @see org.eclipse.emf.diffmerge.impl.scopes.AbstractModelScope#getOriginator()
- */
- @Override
- public Object getOriginator() {
- return getHoldingResource();
- }
-
- /**
- * @see org.eclipse.emf.diffmerge.api.scopes.IFragmentedModelScope#getReferencedResources(org.eclipse.emf.ecore.resource.Resource)
- * Result is guaranteed to be accurate only if hasBeenExplored().
- */
- public List<Resource> getReferencedResources(Resource resource_p) {
- return _referencedResources.get(resource_p);
- }
-
- /**
- * @see org.eclipse.emf.diffmerge.api.scopes.IFragmentedModelScope#getRootResources()
- * Result is guaranteed to be accurate only if hasBeenExplored().
- */
- public List<Resource> getRootResources() {
- return Collections.unmodifiableList(_rootResources);
- }
-
- /**
- * Return a list of resources that must be integrated to the scope, given a
- * contextual element. The resource of the element needs not be included.
- * @param element_p a non-null element belonging to the scope
- * @return a non-null, potentially empty list
- */
- protected List<Resource> getRelevantReferencedResources(EObject element_p) {
- List<Resource> result = new FOrderedSet<Resource>();
- Collection<EReference> refsInScope = getCrossReferencesInScope(element_p);
- for (EReference ref : refsInScope) {
- if (!ref.isContainment() && !ref.isContainer() && element_p.eIsSet(ref)) {
- List<EObject> values = get(element_p, ref, true);
- for (EObject value : values) {
- Resource valueResource = value.eResource();
- if (valueResource != null)
- result.add(valueResource);
- }
- }
- }
- return result;
- }
-
- /**
- * Return the Resource to use for adding the given root element
- * @param newRoot_p a non-null element to be integrated to the scope as a root
- * @return a Resource, or null if none was found
- */
- protected Resource getResourceForNewRoot(EObject newRoot_p) {
- // Return the first suitable resource
- for (Resource resource : _resources) {
- if (isSuitableFor(resource, newRoot_p))
- return resource;
- }
- return null;
- }
-
- /**
- * @see org.eclipse.emf.diffmerge.api.scopes.IFragmentedModelScope#getResources()
- * Result is guaranteed to be accurate only if hasBeenExplored().
- * @return a non-null, non-empty list of resources
- */
- public List<Resource> getResources() {
- return Collections.unmodifiableList(_resources);
- }
-
- /**
- * Return whether this scope has been fully explored, that is, at least one complete iteration
- * based on getAllContents has been performed and the contents are still available
- * @see org.eclipse.emf.diffmerge.api.scopes.IFragmentedModelScope#isFullyExplored()
- */
- public boolean isFullyExplored() {
- return _state == ScopeState.FULLY_EXPLORED;
- }
-
- /**
- * @see org.eclipse.emf.diffmerge.api.scopes.IPersistentModelScope#isLoaded()
- */
- public boolean isLoaded() {
- return _state != ScopeState.INITIALIZED && _state != ScopeState.UNLOADED;
- }
-
- /**
- * @see org.eclipse.emf.diffmerge.impl.scopes.AbstractEditableModelScope#isReadOnly()
- */
- @Override
- public boolean isReadOnly() {
- return _isReadOnly;
- }
-
- /**
- * Return whether the given resource is suitable for storing the given element as a root
- * @param resource_p a non-null resource
- * @param root_p a non-null element
- */
- protected boolean isSuitableFor(Resource resource_p, EObject root_p) {
- // Override if needed
- return true;
- }
-
- /**
- * @see org.eclipse.emf.diffmerge.api.scopes.IPersistentModelScope#load()
- */
- public boolean load() throws Exception {
- boolean result = false;
- if (_state == ScopeState.INITIALIZED || _state == ScopeState.LOADED) {
- for (Resource rootResource : _rootResources) {
- Map<?,?> options = getLoadOptions(rootResource);
- rootResource.load(options);
- }
- _state = ScopeState.LOADED;
- result = true;
- }
- return result;
- }
-
- /**
- * Get notified that the given resource is included via the containment tree
- * into the other given resource. We assume that all roots of the included resource
- * are reachable from the including resource as is normally the case with
- * the fragmentation mechanism.
- * @param including_p a non-null resource
- * @param included_p a non-null resource
- */
- protected final void notifyInclusion(Resource including_p, Resource included_p) {
- if (!_resources.contains(included_p))
- addNewResource(included_p);
- // New inclusion
- _includedResources.add(including_p, included_p);
- // Remove from roots and referencing relation
- _rootResources.remove(included_p);
- // Inclusion takes precedence over referencing
- _referencedResources.remove(including_p, included_p);
- }
-
- /**
- * Get notified that the given source resource references the given target resource
- * @param source_p a non-null resource
- * @param target_p a non-null resource
- */
- protected final void notifyReference(Resource source_p, Resource target_p) {
- if (!_resources.contains(target_p)) {
- addNewResource(target_p);
- _rootResources.add(target_p);
- _referencedResources.add(source_p, target_p);
- }
- }
-
- /**
- * @see org.eclipse.emf.diffmerge.api.scopes.IPersistentModelScope#save()
- */
- public boolean save() throws Exception {
- Map<Object, Object> options = new HashMap<Object, Object>();
- options.put(Resource.OPTION_SAVE_ONLY_IF_CHANGED,
- Resource.OPTION_SAVE_ONLY_IF_CHANGED_MEMORY_BUFFER);
- for (Resource resource : getResources()) {
- resource.save(options);
- }
- return true;
- }
-
- /**
- * @see IPersistentModelScope#setExtrinsicID(EObject, Object)
- */
- @Override
- public boolean setExtrinsicID(EObject element_p, Object id_p) {
- // Increases visibility
- return super.setExtrinsicID(element_p, id_p);
- }
-
- /**
- * @see org.eclipse.emf.diffmerge.api.scopes.IPersistentModelScope#unload()
- */
- public List<Resource> unload() {
- for (Resource loadedResource : _loadedResources) {
- for (Adapter adapter : new ArrayList<Adapter>(loadedResource.eAdapters())) {
- if (adapter instanceof ECrossReferenceAdapter)
- loadedResource.eAdapters().remove(adapter);
- }
- }
- for (Resource loadedResource : _loadedResources) {
- unloadResource(loadedResource);
- }
- List<Resource> result = new ArrayList<Resource>(_loadedResources);
- _loadedResources.clear();
- if (!result.isEmpty())
- _state = ScopeState.UNLOADED;
- return result;
- }
-
- /**
- * Unload the given resource
- * @param resource_p a non-null resource
- */
- protected void unloadResource(Resource resource_p) {
- try {
- if (resource_p.isLoaded()) // Actually loaded, not just assumed as such
- resource_p.unload();
- _resourceSet.getResources().remove(resource_p);
- } catch (Exception e) {
- // Proceed
- e.printStackTrace();
- }
- }
-
-
- /**
- * An iterator over the dynamic list of resources
- */
- protected class ExpandingMultiResourceTreeIterator extends MultiResourceTreeIterator {
- /** The non-null, non-empty ordered set of resources whose exploration has started */
- protected final Set<Resource> _exploredResources;
- /** The potentially null next element */
- protected EObject _next;
- /** The potentially null actual resource of the preceding element, if any */
- protected Resource _currentResource;
- /** Whether iteration has finished */
- protected boolean _finished;
- /**
- * Constructor
- */
- public ExpandingMultiResourceTreeIterator() {
- super(new DynamicUniqueListIterator<Resource>(_resources));
- _exploredResources = new LinkedHashSet<Resource>();
- _next = null;
- _currentResource = null;
- _finished = false;
- }
- /**
- * Update to the next resource if relevant, and return whether it was
- */
- protected boolean checkNextResource() {
- boolean result = false;
- while ((_contentIterator == null || !_contentIterator.hasNext()) &&
- _resourceIterator.hasNext()) {
- result = true;
- Resource nextResource = _resourceIterator.next();
- if (!_exploredResources.contains(nextResource))
- _contentIterator = nextResource.getAllContents();
- }
- return result;
- }
- /**
- * @see org.eclipse.emf.diffmerge.impl.scopes.MultiResourceTreeIterator#hasNext()
- */
- @Override
- public boolean hasNext() {
- update();
- return _next != null;
- }
- /**
- * @see org.eclipse.emf.diffmerge.impl.scopes.MultiResourceTreeIterator#next()
- */
- @Override
- public EObject next() {
- if (hasNext()) {
- EObject result = _next;
- _currentResource = _next.eResource();
- _next = null;
- return result;
- }
- throw new NoSuchElementException();
- }
- /**
- * @see org.eclipse.emf.diffmerge.impl.scopes.MultiResourceTreeIterator#update()
- */
- @Override
- protected void update() {
- while (_next == null && !_finished) {
- boolean resourceChangedInList = checkNextResource();
- boolean firstExploration = _state != ScopeState.FULLY_EXPLORED;
- if (_contentIterator == null || !_contentIterator.hasNext()) {
- // Iteration finished
- _finished = true;
- _exploredResources.clear();
- _currentResource = null;
- if (firstExploration)
- // First exploration finished
- explorationFinished();
- } else {
- // Elements remaining
- EObject candidate = _contentIterator.next();
- boolean candidateOK = true;
- Resource candidateResource = candidate.eResource();
- if (candidateResource != null) {
- boolean resourceAlreadyExplored = !_exploredResources.add(candidateResource);
- // Since the element is in a resource, we know the resource will be explored
- // because we assume all resource roots are reachable in the case of resource inclusion
- boolean resourceChangedByInclusion = false;
- resourceChangedByInclusion = !resourceChangedInList &&
- _currentResource != null && _currentResource != candidateResource;
- if (resourceChangedByInclusion && firstExploration)
- notifyInclusion(_currentResource, candidateResource);
- if (resourceAlreadyExplored && resourceChangedByInclusion) {
- // Resource reached via inclusion but already visited: Skip element and its subtree
- _contentIterator.prune();
- candidateOK = false;
- }
- }
- if (candidateOK) {
- _next = candidate;
- if (firstExploration && candidateResource != null) {
- for (Resource additionalResource : getRelevantReferencedResources(_next))
- notifyReference(candidateResource, additionalResource);
- }
- }
- }
- }
- }
- }
-
-}
+/**
+ * <copyright>
+ *
+ * Copyright (c) 2010-2014 Thales Global Services S.A.S.
+ * 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:
+ * Thales Global Services S.A.S. - initial API and implementation
+ *
+ * </copyright>
+ */
+package org.eclipse.emf.diffmerge.impl.scopes;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+import org.eclipse.emf.common.notify.Adapter;
+import org.eclipse.emf.common.util.TreeIterator;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.diffmerge.api.scopes.IFragmentedModelScope;
+import org.eclipse.emf.diffmerge.api.scopes.IPersistentModelScope;
+import org.eclipse.emf.diffmerge.util.ModelImplUtil;
+import org.eclipse.emf.diffmerge.util.structures.FArrayList;
+import org.eclipse.emf.diffmerge.util.structures.FOrderedSet;
+import org.eclipse.emf.diffmerge.util.structures.HashBinaryRelation;
+import org.eclipse.emf.diffmerge.util.structures.IBinaryRelation;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EReference;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.emf.ecore.resource.ResourceSet;
+import org.eclipse.emf.ecore.util.ECrossReferenceAdapter;
+import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
+import org.eclipse.emf.edit.domain.EditingDomain;
+import org.eclipse.emf.edit.domain.IEditingDomainProvider;
+
+
+/**
+ * A model scope which covers the contents of a given resource plus the contents
+ * of other resources which are being logically included or referenced according
+ * to custom rules, recursively.
+ * This kind of scope is typically useful for loading models which are fragmented
+ * among multiple resources via containment references and cross-references.
+ * The scope is built incrementally: the additional resources are discovered dynamically
+ * the first time the scope is being explored using getAllContents().
+ * A fix point is guaranteed to be found and the exploration of the scope is guaranteed
+ * to be complete every time.
+ * This implementation assumes an initialization phase that consists in a full exploration
+ * of the scope. No other action on the scope or modification of the resource set is
+ * expected to happen during this initialization phase.
+ * EMF undo/redo is supported because the local state does not change after full exploration,
+ * unless unload() has been called.
+ * @author Olivier Constant
+ */
+public class FragmentedModelScope extends AbstractEditableModelScope
+implements IFragmentedModelScope.Editable {
+
+ /** Whether the resources should be opened in read-only mode */
+ private final boolean _isReadOnly;
+
+ /** The optional editing domain that encompasses the scope */
+ protected EditingDomain _editingDomain;
+
+ /** The non-null resource set that encompasses the scope */
+ protected final ResourceSet _resourceSet;
+
+ /** The non-null, non-empty ordered set of resources defining the scope.
+ * It includes _rootResources, _includedResources and _referencedResources. */
+ protected final List<Resource> _resources;
+
+ /** The non-null, non-empty ordered subset of the resources which are roots */
+ protected final List<Resource> _rootResources;
+
+ /** The non-null, potentially empty set of resources initially present
+ * in the resource set before the scope was loaded (content is temporary) */
+ protected final Set<Resource> _initiallyPresentResources;
+
+ /** The non-null, initially empty set of resources that have been loaded due
+ * to the exploration of the scope */
+ protected final Set<Resource> _loadedResources;
+
+ /** The resource inclusion relationship */
+ protected final IBinaryRelation.Editable<Resource, Resource> _includedResources;
+
+ /** The resource referencing relationship */
+ protected final IBinaryRelation.Editable<Resource, Resource> _referencedResources;
+
+ /** The possible states of the scope, in order */
+ protected enum ScopeState {
+ INITIALIZED, // The scope has just been created
+ LOADED, // The scope has been loaded but not fully explored
+ FULLY_EXPLORED, // The scope has been loaded and fully explored
+ UNLOADED // The scope has been unloaded
+ }
+
+ /** The current state of the scope */
+ protected ScopeState _state;
+
+
+ /**
+ * Constructor
+ * @param resource_p a non-null resource that belongs to a non-null resource set
+ * @param readOnly_p whether the scope is in read-only mode, if applicable
+ * Precondition: resource_p != null && resource_p.getResourceSet() != null
+ */
+ public FragmentedModelScope(Resource resource_p, boolean readOnly_p) {
+ this(resource_p.getURI(), resource_p.getResourceSet(), readOnly_p);
+ }
+
+ /**
+ * Constructor
+ * @param uri_p a non-null URI of the resource to load as root
+ * @param editingDomain_p a non-null editing domain that encompasses the scope
+ * @param readOnly_p whether the scope should be read-only, if supported
+ */
+ public FragmentedModelScope(URI uri_p, EditingDomain editingDomain_p, boolean readOnly_p) {
+ this(Collections.singleton(uri_p), editingDomain_p, readOnly_p);
+ }
+
+ /**
+ * Constructor
+ * @param uri_p a non-null resource URI
+ * @param resourceSet_p a non-null resource set where the resources must be loaded
+ * @param readOnly_p whether the scope is in read-only mode, if applicable
+ */
+ public FragmentedModelScope(URI uri_p, ResourceSet resourceSet_p, boolean readOnly_p) {
+ this(Collections.singleton(uri_p), resourceSet_p, readOnly_p);
+ }
+
+ /**
+ * Constructor
+ * @param uris_p a non-null collection of URIs of resources to load as roots
+ * @param editingDomain_p a non-null editing domain that encompasses the scope
+ * @param readOnly_p whether the scope should be read-only, if supported
+ */
+ public FragmentedModelScope(Collection<URI> uris_p, EditingDomain editingDomain_p, boolean readOnly_p) {
+ this(uris_p, editingDomain_p.getResourceSet(), readOnly_p);
+ _editingDomain = editingDomain_p;
+ }
+
+ /**
+ * Constructor
+ * @param uris_p a non-null collection of URIs of resources to load as roots
+ * @param resourceSet_p a non-null resource set where the resources must be loaded
+ * @param readOnly_p whether the scope is in read-only mode, if applicable
+ */
+ public FragmentedModelScope(Collection<URI> uris_p, ResourceSet resourceSet_p, boolean readOnly_p) {
+ this(resourceSet_p, readOnly_p);
+ for (URI uri : uris_p) {
+ Resource rootResource = _resourceSet.getResource(uri, false);
+ if (rootResource == null)
+ rootResource = _resourceSet.createResource(uri);
+ _rootResources.add(rootResource);
+ addNewResource(rootResource);
+ }
+ }
+
+ /**
+ * Common constructor
+ * @param resourceSet_p the non-null resource set that encompasses the scope
+ * @param readOnly_p whether the scope is in read-only mode, if applicable
+ */
+ protected FragmentedModelScope(ResourceSet resourceSet_p, boolean readOnly_p) {
+ _state = ScopeState.INITIALIZED;
+ _isReadOnly = readOnly_p;
+ _resourceSet = resourceSet_p;
+ _resources = new ArrayList<Resource>();
+ _rootResources = new ArrayList<Resource>();
+ _includedResources = new HashBinaryRelation<Resource, Resource>();
+ _referencedResources = new HashBinaryRelation<Resource, Resource>();
+ _initiallyPresentResources = new HashSet<Resource>();
+ _initiallyPresentResources.addAll(_resourceSet.getResources());
+ _loadedResources = new HashSet<Resource>();
+ if (_resourceSet instanceof IEditingDomainProvider)
+ _editingDomain = ((IEditingDomainProvider)_resourceSet).getEditingDomain();
+ }
+
+ /**
+ * @see org.eclipse.emf.diffmerge.api.scopes.IEditableModelScope#add(org.eclipse.emf.ecore.EObject)
+ */
+ public boolean add(EObject element_p) {
+ boolean result = false;
+ Resource defaultResource = getResourceForNewRoot(element_p);
+ if (defaultResource != null) {
+ defaultResource.getContents().add(element_p);
+ result = true;
+ }
+ return result;
+ }
+
+ /**
+ * @see org.eclipse.emf.diffmerge.impl.scopes.AbstractModelScope#add(EObject, EReference, EObject)
+ */
+ @Override
+ public boolean add(EObject source_p, EReference reference_p, EObject value_p) {
+ Resource oldResource = value_p.eResource();
+ boolean wasRoot = oldResource != null && oldResource.getContents().contains(value_p);
+ Object formerId = getExtrinsicID(value_p);
+ boolean result = super.add(source_p, reference_p, value_p);
+ if (wasRoot && reference_p.isContainment())
+ oldResource.getContents().remove(value_p); // Not automatically handled
+ if (formerId != null)
+ // In case resource has changed, thus changing the extrinsic ID
+ setExtrinsicID(value_p, formerId);
+ return result;
+ }
+
+ /**
+ * Add the given resource to the set of covered resources
+ * @param resource_p a non-null resource which is not contained in getResources()
+ * Precondition: !getResources().contains(resource_p)
+ * Postcondition: getResources().contains(resource_p)
+ */
+ protected void addNewResource(Resource resource_p) {
+ _resources.add(resource_p);
+ }
+
+ /**
+ * Return whether the given collection contains proxies relative to the
+ * given holding element whose target is already loaded
+ * @param collection_p a non-null collection of elements
+ * @param source_p a non-null element potentially holding proxies
+ */
+ protected boolean containsUnnecessaryProxies(Collection<EObject> collection_p,
+ EObject source_p) {
+ for (EObject current : collection_p) {
+ if (current.eIsProxy() && current != ModelImplUtil.resolveIfLoaded(current, source_p))
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Called as soon as full scope exploration has been done
+ */
+ protected void explorationFinished() {
+ _state = ScopeState.FULLY_EXPLORED;
+ _loadedResources.addAll(_resourceSet.getResources());
+ _loadedResources.removeAll(_initiallyPresentResources);
+ _initiallyPresentResources.clear();
+ // Handling read-only on loaded resources
+ if (isReadOnly() && _editingDomain instanceof AdapterFactoryEditingDomain) {
+ AdapterFactoryEditingDomain afEditingDomain = (AdapterFactoryEditingDomain)_editingDomain;
+ Map<Resource, Boolean> readOnlyMap = afEditingDomain.getResourceToReadOnlyMap();
+ if (readOnlyMap != null) {
+ for (Resource loadedResource : _loadedResources) {
+ readOnlyMap.put(loadedResource, Boolean.TRUE);
+ }
+ }
+ }
+ }
+
+ /**
+ * @see org.eclipse.emf.diffmerge.impl.scopes.AbstractModelScope#get(EObject, EReference)
+ */
+ @Override
+ public List<EObject> get(EObject source_p, EReference reference_p) {
+ // Current result, may require resolution of in-scope proxies
+ List<EObject> result = super.get(source_p, reference_p);
+ boolean requiresResolution = containsUnnecessaryProxies(result, source_p);
+ if (requiresResolution) // Recompute result if needed
+ result = get(source_p, reference_p, true);
+ return result;
+ }
+
+ /**
+ * @see org.eclipse.emf.diffmerge.impl.scopes.AbstractModelScope#getAllContents()
+ */
+ @Override
+ public TreeIterator<EObject> getAllContents() {
+ return new ExpandingMultiResourceTreeIterator();
+ }
+
+ /**
+ * @see org.eclipse.emf.diffmerge.api.scopes.IModelScope#getContents()
+ * Result is guaranteed to be accurate only if hasBeenExplored().
+ */
+ public List<EObject> getContents() {
+ List<EObject> result = new FArrayList<EObject>();
+ for (Resource resource : _rootResources)
+ result.addAll(resource.getContents());
+ return Collections.unmodifiableList(result);
+ }
+
+ /**
+ * Return the cross-references, for the given element, which are in the scope
+ * @param element_p a non-null element belonging to the scope
+ * @return a non-null, potentially empty collection of non-containment, non-container references
+ */
+ protected Collection<EReference> getCrossReferencesInScope(EObject element_p) {
+ // Override if needed
+ return new ArrayList<EReference>();
+ }
+
+ /**
+ * @see IPersistentModelScope#getExtrinsicID(EObject)
+ */
+ @Override
+ public Object getExtrinsicID(EObject element_p) {
+ // Increases visibility
+ return super.getExtrinsicID(element_p);
+ }
+
+ /**
+ * @see org.eclipse.emf.diffmerge.api.scopes.IPersistentModelScope#getHoldingResource()
+ */
+ public Resource getHoldingResource() {
+ return _rootResources.isEmpty()? null: _rootResources.get(0);
+ }
+
+ /**
+ * @see org.eclipse.emf.diffmerge.api.scopes.IFragmentedModelScope#getIncludedResources(org.eclipse.emf.ecore.resource.Resource)
+ * Result is guaranteed to be accurate only if hasBeenExplored().
+ */
+ public List<Resource> getIncludedResources(Resource resource_p) {
+ return _includedResources.get(resource_p);
+ }
+
+ /**
+ * Return load options for loading the given resource
+ * @param resource_p a non-null resource
+ * @return a non-null, potentially empty, modifiable option map
+ */
+ protected Map<Object, Object> getLoadOptions(Resource resource_p) {
+ // Override if needed
+ return new HashMap<Object, Object>();
+ }
+
+ /**
+ * @see org.eclipse.emf.diffmerge.impl.scopes.AbstractModelScope#getOriginator()
+ */
+ @Override
+ public Object getOriginator() {
+ return getHoldingResource();
+ }
+
+ /**
+ * @see org.eclipse.emf.diffmerge.api.scopes.IFragmentedModelScope#getReferencedResources(org.eclipse.emf.ecore.resource.Resource)
+ * Result is guaranteed to be accurate only if hasBeenExplored().
+ */
+ public List<Resource> getReferencedResources(Resource resource_p) {
+ return _referencedResources.get(resource_p);
+ }
+
+ /**
+ * @see org.eclipse.emf.diffmerge.api.scopes.IFragmentedModelScope#getRootResources()
+ * Result is guaranteed to be accurate only if hasBeenExplored().
+ */
+ public List<Resource> getRootResources() {
+ return Collections.unmodifiableList(_rootResources);
+ }
+
+ /**
+ * Return a list of resources that must be integrated to the scope, given a
+ * contextual element. The resource of the element needs not be included.
+ * @param element_p a non-null element belonging to the scope
+ * @return a non-null, potentially empty list
+ */
+ protected List<Resource> getRelevantReferencedResources(EObject element_p) {
+ List<Resource> result = new FOrderedSet<Resource>();
+ Collection<EReference> refsInScope = getCrossReferencesInScope(element_p);
+ for (EReference ref : refsInScope) {
+ if (!ref.isContainment() && !ref.isContainer() && element_p.eIsSet(ref)) {
+ List<EObject> values = get(element_p, ref, true);
+ for (EObject value : values) {
+ Resource valueResource = value.eResource();
+ if (valueResource != null)
+ result.add(valueResource);
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Return the Resource to use for adding the given root element
+ * @param newRoot_p a non-null element to be integrated to the scope as a root
+ * @return a Resource, or null if none was found
+ */
+ protected Resource getResourceForNewRoot(EObject newRoot_p) {
+ // Return the first suitable resource
+ for (Resource resource : _resources) {
+ if (isSuitableFor(resource, newRoot_p))
+ return resource;
+ }
+ return null;
+ }
+
+ /**
+ * @see org.eclipse.emf.diffmerge.api.scopes.IFragmentedModelScope#getResources()
+ * Result is guaranteed to be accurate only if hasBeenExplored().
+ * @return a non-null, non-empty list of resources
+ */
+ public List<Resource> getResources() {
+ return Collections.unmodifiableList(_resources);
+ }
+
+ /**
+ * Return whether this scope has been fully explored, that is, at least one complete iteration
+ * based on getAllContents has been performed and the contents are still available
+ * @see org.eclipse.emf.diffmerge.api.scopes.IFragmentedModelScope#isFullyExplored()
+ */
+ public boolean isFullyExplored() {
+ return _state == ScopeState.FULLY_EXPLORED;
+ }
+
+ /**
+ * @see org.eclipse.emf.diffmerge.api.scopes.IPersistentModelScope#isLoaded()
+ */
+ public boolean isLoaded() {
+ return _state != ScopeState.INITIALIZED && _state != ScopeState.UNLOADED;
+ }
+
+ /**
+ * @see org.eclipse.emf.diffmerge.impl.scopes.AbstractEditableModelScope#isReadOnly()
+ */
+ @Override
+ public boolean isReadOnly() {
+ return _isReadOnly;
+ }
+
+ /**
+ * Return whether the given resource is suitable for storing the given element as a root
+ * @param resource_p a non-null resource
+ * @param root_p a non-null element
+ */
+ protected boolean isSuitableFor(Resource resource_p, EObject root_p) {
+ // Override if needed
+ return true;
+ }
+
+ /**
+ * @see org.eclipse.emf.diffmerge.api.scopes.IPersistentModelScope#load()
+ */
+ public boolean load() throws Exception {
+ boolean result = false;
+ if (_state == ScopeState.INITIALIZED || _state == ScopeState.LOADED) {
+ for (Resource rootResource : _rootResources) {
+ Map<?,?> options = getLoadOptions(rootResource);
+ rootResource.load(options);
+ }
+ _state = ScopeState.LOADED;
+ result = true;
+ }
+ return result;
+ }
+
+ /**
+ * Get notified that the given resource is included via the containment tree
+ * into the other given resource. We assume that all roots of the included resource
+ * are reachable from the including resource as is normally the case with
+ * the fragmentation mechanism.
+ * @param including_p a non-null resource
+ * @param included_p a non-null resource
+ */
+ protected final void notifyInclusion(Resource including_p, Resource included_p) {
+ if (!_resources.contains(included_p))
+ addNewResource(included_p);
+ // New inclusion
+ _includedResources.add(including_p, included_p);
+ // Remove from roots and referencing relation
+ _rootResources.remove(included_p);
+ // Inclusion takes precedence over referencing
+ _referencedResources.remove(including_p, included_p);
+ }
+
+ /**
+ * Get notified that the given source resource references the given target resource
+ * @param source_p a non-null resource
+ * @param target_p a non-null resource
+ */
+ protected final void notifyReference(Resource source_p, Resource target_p) {
+ if (!_resources.contains(target_p)) {
+ addNewResource(target_p);
+ _rootResources.add(target_p);
+ _referencedResources.add(source_p, target_p);
+ }
+ }
+
+ /**
+ * @see org.eclipse.emf.diffmerge.api.scopes.IPersistentModelScope#save()
+ */
+ public boolean save() throws Exception {
+ Map<Object, Object> options = new HashMap<Object, Object>();
+ options.put(Resource.OPTION_SAVE_ONLY_IF_CHANGED,
+ Resource.OPTION_SAVE_ONLY_IF_CHANGED_MEMORY_BUFFER);
+ for (Resource resource : getResources()) {
+ resource.save(options);
+ }
+ return true;
+ }
+
+ /**
+ * @see IPersistentModelScope#setExtrinsicID(EObject, Object)
+ */
+ @Override
+ public boolean setExtrinsicID(EObject element_p, Object id_p) {
+ // Increases visibility
+ return super.setExtrinsicID(element_p, id_p);
+ }
+
+ /**
+ * @see org.eclipse.emf.diffmerge.api.scopes.IPersistentModelScope#unload()
+ */
+ public List<Resource> unload() {
+ for (Resource loadedResource : _loadedResources) {
+ for (Adapter adapter : new ArrayList<Adapter>(loadedResource.eAdapters())) {
+ if (adapter instanceof ECrossReferenceAdapter)
+ loadedResource.eAdapters().remove(adapter);
+ }
+ }
+ for (Resource loadedResource : _loadedResources) {
+ unloadResource(loadedResource);
+ }
+ List<Resource> result = new ArrayList<Resource>(_loadedResources);
+ _loadedResources.clear();
+ if (!result.isEmpty())
+ _state = ScopeState.UNLOADED;
+ return result;
+ }
+
+ /**
+ * Unload the given resource
+ * @param resource_p a non-null resource
+ */
+ protected void unloadResource(Resource resource_p) {
+ try {
+ if (resource_p.isLoaded()) // Actually loaded, not just assumed as such
+ resource_p.unload();
+ _resourceSet.getResources().remove(resource_p);
+ } catch (Exception e) {
+ // Proceed
+ e.printStackTrace();
+ }
+ }
+
+
+ /**
+ * An iterator over the dynamic list of resources
+ */
+ protected class ExpandingMultiResourceTreeIterator extends MultiResourceTreeIterator {
+ /** The non-null, non-empty ordered set of resources whose exploration has started */
+ protected final Set<Resource> _exploredResources;
+ /** The potentially null next element */
+ protected EObject _next;
+ /** The potentially null actual resource of the preceding element, if any */
+ protected Resource _currentResource;
+ /** Whether iteration has finished */
+ protected boolean _finished;
+ /**
+ * Constructor
+ */
+ public ExpandingMultiResourceTreeIterator() {
+ super(new DynamicUniqueListIterator<Resource>(_resources));
+ _exploredResources = new LinkedHashSet<Resource>();
+ _next = null;
+ _currentResource = null;
+ _finished = false;
+ }
+ /**
+ * Update to the next resource if relevant, and return whether it was
+ */
+ protected boolean checkNextResource() {
+ boolean result = false;
+ while ((_contentIterator == null || !_contentIterator.hasNext()) &&
+ _resourceIterator.hasNext()) {
+ result = true;
+ Resource nextResource = _resourceIterator.next();
+ if (!_exploredResources.contains(nextResource))
+ _contentIterator = nextResource.getAllContents();
+ }
+ return result;
+ }
+ /**
+ * @see org.eclipse.emf.diffmerge.impl.scopes.MultiResourceTreeIterator#hasNext()
+ */
+ @Override
+ public boolean hasNext() {
+ update();
+ return _next != null;
+ }
+ /**
+ * @see org.eclipse.emf.diffmerge.impl.scopes.MultiResourceTreeIterator#next()
+ */
+ @Override
+ public EObject next() {
+ if (hasNext()) {
+ EObject result = _next;
+ _currentResource = _next.eResource();
+ _next = null;
+ return result;
+ }
+ throw new NoSuchElementException();
+ }
+ /**
+ * @see org.eclipse.emf.diffmerge.impl.scopes.MultiResourceTreeIterator#update()
+ */
+ @Override
+ protected void update() {
+ while (_next == null && !_finished) {
+ boolean resourceChangedInList = checkNextResource();
+ boolean firstExploration = _state != ScopeState.FULLY_EXPLORED;
+ if (_contentIterator == null || !_contentIterator.hasNext()) {
+ // Iteration finished
+ _finished = true;
+ _exploredResources.clear();
+ _currentResource = null;
+ if (firstExploration)
+ // First exploration finished
+ explorationFinished();
+ } else {
+ // Elements remaining
+ EObject candidate = _contentIterator.next();
+ boolean candidateOK = true;
+ Resource candidateResource = candidate.eResource();
+ if (candidateResource != null) {
+ boolean resourceAlreadyExplored = !_exploredResources.add(candidateResource);
+ // Since the element is in a resource, we know the resource will be explored
+ // because we assume all resource roots are reachable in the case of resource inclusion
+ boolean resourceChangedByInclusion = false;
+ Resource candidateContainerResource = null;
+ // Determine whether the current element leads to a new resource by inclusion
+ if (!resourceChangedInList && _currentResource != null && _currentResource != candidateResource) {
+ EObject candidateContainer = candidate.eContainer();
+ if (candidateContainer != null) {
+ candidateContainerResource = candidateContainer.eResource();
+ resourceChangedByInclusion =
+ candidateContainerResource != null && candidateContainerResource != candidateResource;
+ }
+ }
+ if (resourceChangedByInclusion && firstExploration) {
+ // Resource reached by inclusion: Notify (candidateContainerResource cannot be null)
+ notifyInclusion(candidateContainerResource, candidateResource);
+ }
+ if (resourceChangedByInclusion && resourceAlreadyExplored) {
+ // Resource reached by inclusion but already visited: Skip element and its subtree
+ _contentIterator.prune();
+ candidateOK = false;
+ }
+ }
+ if (candidateOK) {
+ _next = candidate;
+ if (firstExploration && candidateResource != null) {
+ for (Resource additionalResource : getRelevantReferencedResources(_next))
+ notifyReference(candidateResource, additionalResource);
+ }
+ }
+ }
+ }
+ }
+ }
+
+}

Back to the top