Skip to main content
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLaurent Delaigue2015-04-02 22:26:58 +0000
committerAxel RICHARD2015-05-13 08:16:19 +0000
commit063db4a41280f9822e0537873ffb4375f88e2195 (patch)
treedaa8b4289702dbf71193144492ae470938ae02a0 /plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf
parent6719de0f862809a32d0ed902ad0eb645bd84e985 (diff)
downloadorg.eclipse.emf.compare-063db4a41280f9822e0537873ffb4375f88e2195.tar.gz
org.eclipse.emf.compare-063db4a41280f9822e0537873ffb4375f88e2195.tar.xz
org.eclipse.emf.compare-063db4a41280f9822e0537873ffb4375f88e2195.zip
Refactoring of ThreadedModelResolver.
Split computations in several smaller classes to ease maintenance. Extracted multi-threaded logic in a separate class. Extracted function AS_URI to ResourceUtil. Extracted IResourceDependencyProviderListener in its own class Factor out Ecore and URI dependency from DependencyFoundEvent Encapsulates decision on whether a parent URI is registered (i.e., whether the reference is containment or not) in a new subclass ResourceDependencyFoundEvent. This also allows to remove dependencies to URI and EcoreUtils. To allow mocking the graph, we also change Graph to not be final anymore. Added GraphResolutionTest to test suite. Extracted multiple interfaces to separate: - dependency obtention; - local resolution; - remote resolution. Change-Id: I7fe11b2cf6f4abc13f64821826855fe6fe205ef2 Also-by: Philip Langer <planger@eclipsesource.com> Signed-off-by: Laurent Delaigue <laurent.delaigue@obeo.fr> Signed-off-by: Axel Richard <axel.richard@obeo.fr>
Diffstat (limited to 'plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf')
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/AbstractMonitoredProxyCreationListener.java45
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/AbstractResolution.java165
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/AbstractResourceResolver.java102
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/CrossReferenceResolutionScope.java2
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/DefaultImplicitDependencies.java37
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/DefaultResolutionContext.java127
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/DependencyFoundEvent.java79
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/DependencyGraphUpdater.java82
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/DiagnosticSupport.java70
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/IComputation.java39
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/IImplicitDependencies.java40
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/IResolutionContext.java58
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/IResourceDependencyLocalResolver.java61
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/IResourceDependencyProvider.java60
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/IResourceDependencyRemoteResolver.java73
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/LocalModelResolution.java83
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/LocalModelsResolution.java268
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/LocalMonitoredProxyCreationListener.java69
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/LocalResolveComputation.java90
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ModelResourceListener.java147
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ModelResourceVisitor.java89
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ModelsResolution.java516
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/MonitorCallback.java69
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/RemoteMonitoredProxyCreationListener.java60
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/RemoteResolveComputation.java80
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResolutionUtil.java83
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResolvedEvent.java40
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResourceComputationScheduler.java553
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResourceDependencyFoundEvent.java78
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResourceDependencyLocalResolver.java296
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResourceDependencyProvider.java108
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResourceDependencyRemoteResolver.java84
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResourceRemovedEvent.java32
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResourceUnloader.java51
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/RevisionedURIConverter.java8
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/SynchronizedResourceSet.java19
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ThreadedModelResolver.java1767
37 files changed, 3887 insertions, 1743 deletions
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/AbstractMonitoredProxyCreationListener.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/AbstractMonitoredProxyCreationListener.java
new file mode 100644
index 000000000..edb247e21
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/AbstractMonitoredProxyCreationListener.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Obeo.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.logical.resolver;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import org.eclipse.emf.compare.ide.internal.utils.ProxyNotifierParserPool.IProxyCreationListener;
+import org.eclipse.emf.compare.ide.ui.internal.util.ThreadSafeProgressMonitor;
+
+/**
+ * Asbtract super-class of {@link IProxyCreationListener}s to use for computing the logical model.
+ *
+ * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
+ */
+public abstract class AbstractMonitoredProxyCreationListener implements IProxyCreationListener {
+
+ /** The progress monitor. */
+ protected final ThreadSafeProgressMonitor tspm;
+
+ /** The diagnostic. */
+ protected final DiagnosticSupport diagnostic;
+
+ /**
+ * Constructor.
+ *
+ * @param monitor
+ * The monitor to use, must not be {@code null}
+ * @param diagnostic
+ * The diagnostic, must not be {@code null}
+ */
+ public AbstractMonitoredProxyCreationListener(ThreadSafeProgressMonitor monitor,
+ DiagnosticSupport diagnostic) {
+ this.tspm = checkNotNull(monitor);
+ this.diagnostic = checkNotNull(diagnostic);
+ }
+
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/AbstractResolution.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/AbstractResolution.java
new file mode 100644
index 000000000..283e301ef
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/AbstractResolution.java
@@ -0,0 +1,165 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Obeo.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.logical.resolver;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.eclipse.emf.compare.ide.utils.ResourceUtil.asURI;
+
+import com.google.common.collect.Sets;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IStorage;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.SubMonitor;
+import org.eclipse.emf.common.util.Diagnostic;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.compare.ide.utils.ResourceUtil;
+import org.eclipse.emf.ecore.resource.URIConverter;
+
+/**
+ * Abstract super-class of resolving computations.
+ *
+ * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
+ */
+public abstract class AbstractResolution {
+
+ /** The context. */
+ protected final IResolutionContext context;
+
+ /** The monitor. */
+ protected final SubMonitor monitor;
+
+ /** The diagnostic. */
+ protected DiagnosticSupport diagnostic;
+
+ /**
+ * Constructor.
+ *
+ * @param context
+ * The resolution context, must not be {@code null}
+ * @param monitor
+ * The progress monitor, can be {@code null}
+ */
+ public AbstractResolution(IResolutionContext context, IProgressMonitor monitor) {
+ this.context = checkNotNull(context);
+ this.monitor = SubMonitor.convert(monitor, getTicks());
+ }
+
+ /**
+ * Number of ticks to allocate to the progress monitor used for reporting progress.
+ *
+ * @return The number of ticks to use, 100 by default but can be overridden if necessary.
+ */
+ protected int getTicks() {
+ return 100;
+ }
+
+ /**
+ * Executes the given callable as soon as there is no other computation running, and automatically runs
+ * "finalization" treatment once the computation is over, whatever its outcome (success or failure). A
+ * {@link #diagnostic} is instantiated before the computation and should be used thourghout this whole
+ * computation. It will be set to {@code null} before returning, whatever happens.
+ *
+ * @param <T>
+ * The type of the returned value.
+ * @param callable
+ * Treatment to run
+ * @return The result of the treatment
+ * @throws InterruptedException
+ * If the treatment is interrupted
+ */
+ protected <T> T call(Callable<T> callable) throws InterruptedException {
+ this.diagnostic = new DiagnosticSupport();
+ return context.getScheduler().call(callable, getFinalizeResolvingRunnable());
+ }
+
+ /**
+ * This provides the treatment that is run at the end of the computation, whatever its outcome. It is
+ * guaranteed to run once, in a block "finally", along with other required finalization treatments that
+ * are run systematically. There's no need to acquire a lock, this is guaranteed to have been done before,
+ * and it is released after this treatment ends.
+ *
+ * @return The {@link Runnable} to run after having resolved resources.
+ */
+ protected Runnable getFinalizeResolvingRunnable() {
+ return new Runnable() {
+ public void run() {
+ if (diagnostic.getDiagnostic().getSeverity() >= Diagnostic.ERROR) {
+ // something bad (or a cancel request) happened during resolution, so we invalidate the
+ // dependency graph to avoid weird behavior next time the resolution is called.
+ // TODO Should we really do that?
+ // FIXME dependencyGraph.clear();
+ }
+ diagnostic = null;
+ }
+ };
+ }
+
+ /**
+ * Transforms the given {@link Set} of {@link IStorage}s into a {@link Set} of {@link URI}s.
+ *
+ * @param storages
+ * The storages to transform, must not be {@code null}.
+ * @return A mutable set of {@link URI}s, may be empty but never {@code )null}.
+ */
+ protected Set<URI> asURISet(Set<IStorage> storages) {
+ final Set<URI> uris = new LinkedHashSet<URI>();
+ for (IStorage storage : storages) {
+ uris.add(asURI().apply(storage));
+ }
+ return uris;
+ }
+
+ /**
+ * Computes the traversal of the given file, excluding the given bounds if needed.
+ *
+ * @param file
+ * File for which the traversal is needed
+ * @param bounds
+ * URI to exclude from the logical model computation in case both compared resources are part
+ * of the same logical model
+ * @return A {@link Set} of the file's outgoing and incoming dependencies, never null but possibly empty.
+ */
+ protected Set<IStorage> resolveTraversal(IFile file, Set<URI> bounds) {
+ final Set<IStorage> traversalSet = Sets.newLinkedHashSet();
+ Set<IFile> filesToAdd = Sets.newLinkedHashSet();
+ filesToAdd.add(file);
+ Set<URI> knownURIs = Sets.newLinkedHashSet();
+ while (!filesToAdd.isEmpty()) {
+ Set<IFile> filesToResolve = Sets.newLinkedHashSet();
+ for (IFile newFile : filesToAdd) {
+ URI baseUri = ResourceUtil.createURIFor(newFile);
+ Set<URI> newURIs = context.getImplicitDependencies().of(baseUri, URIConverter.INSTANCE);
+ for (URI uri : newURIs) {
+ if (knownURIs.add(uri)) {
+ IFile toResolve = ResolutionUtil.getFileAt(uri);
+ Iterable<URI> dependencies = context.getDependencyProvider().getDependenciesOf(
+ toResolve, bounds);
+ for (URI dep : dependencies) {
+ IFile dependentFile = ResolutionUtil.getFileAt(dep);
+ if (dependentFile != null && traversalSet.add(dependentFile)
+ && !knownURIs.contains(dep)) {
+ filesToResolve.add(dependentFile);
+ }
+ }
+ }
+ }
+ }
+ filesToAdd.clear();
+ filesToAdd = filesToResolve;
+ }
+ return traversalSet;
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/AbstractResourceResolver.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/AbstractResourceResolver.java
new file mode 100644
index 000000000..e0b8fa68c
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/AbstractResourceResolver.java
@@ -0,0 +1,102 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Obeo.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.logical.resolver;
+
+import com.google.common.util.concurrent.FutureCallback;
+
+import org.eclipse.emf.common.util.BasicDiagnostic;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.compare.ide.ui.internal.util.ThreadSafeProgressMonitor;
+import org.eclipse.emf.ecore.resource.Resource;
+
+/**
+ * Implements a runnable that will load the EMF resource pointed at by a given URI, then resolve all of its
+ * cross-referenced resources and update the dependency graph accordingly.
+ * <p>
+ * Once done with the resolution, this thread will spawn an independent job to unload the resource.
+ * </p>
+ *
+ * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
+ */
+abstract class AbstractResourceResolver implements Runnable {
+ /** The scheduler. */
+ protected final ResourceComputationScheduler<URI> scheduler;
+
+ /** The resource set in which to load the resource. */
+ protected final SynchronizedResourceSet resourceSet;
+
+ /** URI that needs to be loaded as an EMF model. */
+ protected final URI uri;
+
+ /** Monitor on which to report progress to the user. */
+ protected final ThreadSafeProgressMonitor tspm;
+
+ /** The diagnostic. */
+ protected final DiagnosticSupport diagnostic;
+
+ /**
+ * Default constructor.
+ *
+ * @param scheduler
+ * The scheduler to use.
+ * @param diagnostic
+ * The diagnostic to use.
+ * @param resourceSet
+ * The resource set in which to load the resource.
+ * @param uri
+ * URI that needs to be loaded as an EMF model.
+ * @param monitor
+ * Monitor on which to report progress to the user.
+ */
+ public AbstractResourceResolver(ResourceComputationScheduler<URI> scheduler,
+ DiagnosticSupport diagnostic, SynchronizedResourceSet resourceSet, URI uri,
+ ThreadSafeProgressMonitor monitor) {
+ this.scheduler = scheduler;
+ this.diagnostic = diagnostic;
+ this.resourceSet = resourceSet;
+ this.uri = uri;
+ this.tspm = monitor;
+ }
+
+ /**
+ * Allows callers to launch the unloading of the given resource.
+ * <p>
+ * Do note that even though this is called "unload", we won't actually call {@link Resource#unload()} on
+ * the given resource unless we deem it necessary (we only call if for UML because of the CacheAdapter)
+ * for now. This will only remove the resource from its containing resource set so as to allow it to be
+ * garbage collected.
+ * </p>
+ *
+ * @param resource
+ * The resource to unload.
+ * @see ResourceUnloader
+ */
+ protected void demandUnload(Resource resource) {
+ // Regardless of the amount of progress reported so far, use 0.1% of the space remaining in the
+ // monitor to process the next node.
+ tspm.setWorkRemaining(1000);
+ scheduler.scheduleUnload(new ResourceUnloader(resourceSet, resource, tspm),
+ new FutureCallback<Object>() {
+ public void onSuccess(Object result) {
+ if (!ResolutionUtil.isInterruptedOrCanceled(tspm)) {
+ tspm.worked(1);
+ }
+ }
+
+ public void onFailure(Throwable t) {
+ if (!ResolutionUtil.isInterruptedOrCanceled(tspm)) {
+ tspm.worked(1);
+ diagnostic.merge(BasicDiagnostic.toDiagnostic(t));
+ }
+ }
+ });
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/CrossReferenceResolutionScope.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/CrossReferenceResolutionScope.java
index 3e0caa2a0..c8f46a275 100644
--- a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/CrossReferenceResolutionScope.java
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/CrossReferenceResolutionScope.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2014 Obeo.
+ * Copyright (c) 2014, 2015 Obeo.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/DefaultImplicitDependencies.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/DefaultImplicitDependencies.java
new file mode 100644
index 000000000..489ac9c15
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/DefaultImplicitDependencies.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Obeo.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.logical.resolver;
+
+import com.google.common.collect.Sets;
+
+import java.util.Collections;
+import java.util.Set;
+
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.compare.ide.ui.dependency.ModelDependencyProviderRegistry;
+import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIPlugin;
+import org.eclipse.emf.ecore.resource.URIConverter;
+
+/**
+ * Default implementation of {@link IImplicitDependencies}, which uses the extensions collected by the
+ * registry of the <i>modelDependencyProvider</i> extension point.
+ *
+ * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
+ */
+public class DefaultImplicitDependencies implements IImplicitDependencies {
+ /** The registry of declared extensions. */
+ private final ModelDependencyProviderRegistry registry = EMFCompareIDEUIPlugin.getDefault()
+ .getModelDependencyProviderRegistry();
+
+ public Set<URI> of(URI uri, URIConverter uriConverter) {
+ return Sets.union(Collections.singleton(uri), registry.getDependencies(uri, uriConverter));
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/DefaultResolutionContext.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/DefaultResolutionContext.java
new file mode 100644
index 000000000..8d8705437
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/DefaultResolutionContext.java
@@ -0,0 +1,127 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Obeo.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.emf.compare.ide.ui.internal.logical.resolver;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.eventbus.EventBus;
+
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.compare.internal.utils.Graph;
+
+/**
+ * Default implementation of {@link IResolutionContext}.
+ *
+ * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
+ */
+public class DefaultResolutionContext implements IResolutionContext {
+
+ private final EventBus eventBus;
+
+ private final Graph<URI> graph;
+
+ private final DependencyGraphUpdater<URI> graphUpdater;
+
+ private final ResourceComputationScheduler<URI> scheduler;
+
+ private final ModelResourceListener modelResourceListener;
+
+ private IResourceDependencyProvider dependencyProvider;
+
+ private IResourceDependencyLocalResolver localResolver;
+
+ private IResourceDependencyRemoteResolver remoteResolver;
+
+ private IImplicitDependencies implicitDependencies;
+
+ public DefaultResolutionContext(EventBus eventBus, Graph<URI> graph,
+ DependencyGraphUpdater<URI> graphUpdater, ResourceComputationScheduler<URI> scheduler,
+ ModelResourceListener modelResourceListener) {
+ this.eventBus = checkNotNull(eventBus);
+ this.graph = checkNotNull(graph);
+ this.graphUpdater = checkNotNull(graphUpdater);
+ this.scheduler = checkNotNull(scheduler);
+ this.modelResourceListener = checkNotNull(modelResourceListener);
+ }
+
+ public EventBus getEventBus() {
+ return eventBus;
+ }
+
+ public Graph<URI> getGraph() {
+ return graph;
+ }
+
+ public DependencyGraphUpdater<URI> getGraphUpdater() {
+ return graphUpdater;
+ }
+
+ public ResourceComputationScheduler<URI> getScheduler() {
+ return scheduler;
+ }
+
+ public ModelResourceListener getModelResourceListener() {
+ return modelResourceListener;
+ }
+
+ public synchronized IResourceDependencyProvider getDependencyProvider() {
+ if (dependencyProvider == null) {
+ dependencyProvider = new ResourceDependencyProvider(this);
+ }
+ return dependencyProvider;
+ }
+
+ public synchronized IResourceDependencyLocalResolver getLocalResolver() {
+ if (localResolver == null) {
+ localResolver = new ResourceDependencyLocalResolver(this);
+ }
+ return localResolver;
+ }
+
+ public synchronized IResourceDependencyRemoteResolver getRemoteResolver() {
+ if (remoteResolver == null) {
+ remoteResolver = new ResourceDependencyRemoteResolver(this);
+ }
+ return remoteResolver;
+ }
+
+ public synchronized IImplicitDependencies getImplicitDependencies() {
+ if (implicitDependencies == null) {
+ implicitDependencies = new DefaultImplicitDependencies();
+ }
+ return implicitDependencies;
+ }
+
+ /**
+ * {@inheritDoc} When initialized, the context will:
+ * <ol>
+ * <li>install a listener on the workspace to keep track of modified resources</li>
+ * <li>Register its {@link #graphUpdater} to its {@link #eventBus}</li>
+ * <li>initialize its {@link #scheduler}</li>
+ * </ol>
+ */
+ public void initialize() {
+ ResourcesPlugin.getWorkspace().addResourceChangeListener(modelResourceListener);
+ eventBus.register(graphUpdater);
+ scheduler.initialize();
+ }
+
+ /**
+ * Disposes resources allocated during initialization.
+ */
+ public void dispose() {
+ scheduler.dispose();
+ eventBus.unregister(graphUpdater);
+ ResourcesPlugin.getWorkspace().removeResourceChangeListener(modelResourceListener);
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/DependencyFoundEvent.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/DependencyFoundEvent.java
new file mode 100644
index 000000000..98ca84c8c
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/DependencyFoundEvent.java
@@ -0,0 +1,79 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Obeo 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:
+ * Obeo - initial API and implementation
+ * Philip Langer - refactorings
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.logical.resolver;
+
+import com.google.common.base.Optional;
+
+/**
+ * Event indicating a dependency between two resources has been found.
+ *
+ * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
+ * @param <T>
+ * The type of the key used to identify the resources (will be URI in our case).
+ */
+public class DependencyFoundEvent<T> {
+
+ /** Source of the dependency. */
+ private final T from;
+
+ /** Target of the dependency. */
+ private final T to;
+
+ /** Key of the object that owns this dependency */
+ private final Optional<T> parent;
+
+ /**
+ * Constructor.
+ *
+ * @param from
+ * The source
+ * @param to
+ * The target
+ * @param parent
+ * The key of the object at the source causing the dependency
+ */
+ public DependencyFoundEvent(T from, T to, Optional<T> parent) {
+ this.from = from;
+ this.to = to;
+ this.parent = parent;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param from
+ * The source
+ * @param to
+ * The target
+ */
+ public DependencyFoundEvent(T from, T to) {
+ this.from = from;
+ this.to = to;
+ this.parent = Optional.absent();
+ }
+
+ public T getFrom() {
+ return from;
+ }
+
+ public T getTo() {
+ return to;
+ }
+
+ public Optional<T> getParent() {
+ return parent;
+ }
+
+ public boolean hasParent() {
+ return parent.isPresent();
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/DependencyGraphUpdater.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/DependencyGraphUpdater.java
new file mode 100644
index 000000000..d5e010400
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/DependencyGraphUpdater.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Obeo 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:
+ * Obeo - initial API and implementation
+ * Philip Langer - refactorings
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.logical.resolver;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.eventbus.EventBus;
+import com.google.common.eventbus.Subscribe;
+
+import java.util.Collections;
+
+import org.eclipse.emf.compare.internal.utils.Graph;
+
+/**
+ * This class's responsibility is to maintain the state of its graph when notified that a new model resource
+ * or a new dependency have been found.
+ *
+ * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
+ */
+public class DependencyGraphUpdater<T> {
+
+ /** The graph of dependencies between the resources. */
+ private final Graph<T> dependencyGraph;
+
+ /**
+ * Constructor.
+ *
+ * @param graph
+ * The graph, must not be null.
+ * @param eventBus
+ * The event bus that will fire events to record.
+ */
+ public DependencyGraphUpdater(Graph<T> graph, EventBus eventBus) {
+ this.dependencyGraph = checkNotNull(graph);
+ eventBus.register(this);
+ }
+
+ /**
+ * Register a discovered resource in the graph.
+ *
+ * @param event
+ * Event that describes the discovered resource.
+ */
+ @Subscribe
+ public synchronized void recordNode(ResolvedEvent<T> event) {
+ dependencyGraph.add(event.getNode());
+ }
+
+ /**
+ * Register a dependency in the graph.
+ *
+ * @param event
+ * Event that describes the dependency.
+ */
+ @Subscribe
+ public synchronized void recordEdge(DependencyFoundEvent<T> event) {
+ dependencyGraph.addChildren(event.getFrom(), Collections.singleton(event.getTo()));
+ if (event.hasParent()) {
+ dependencyGraph.addParentData(event.getTo(), event.getParent().get());
+ }
+ }
+
+ /**
+ * Register removal of nodes.
+ *
+ * @param event
+ * The event indicating the removed nodes.
+ */
+ @Subscribe
+ public synchronized void recordRemoval(ResourceRemovedEvent<T> event) {
+ dependencyGraph.removeAll(event.getElements());
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/DiagnosticSupport.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/DiagnosticSupport.java
new file mode 100644
index 000000000..fa2e03a6b
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/DiagnosticSupport.java
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Obeo.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.logical.resolver;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import org.eclipse.emf.common.util.BasicDiagnostic;
+import org.eclipse.emf.common.util.Diagnostic;
+import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIPlugin;
+
+/**
+ * Encapsulated a diagnostic to hide multi-threaded details.
+ *
+ * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
+ */
+public class DiagnosticSupport {
+
+ /** The wrapped diagnostic, never {@code null}. */
+ private final BasicDiagnostic diagnostic;
+
+ /**
+ * Constructor.
+ *
+ * @param diagnostic
+ * The diagnostic to wrap, must not be {@code null}.
+ */
+ public DiagnosticSupport(BasicDiagnostic diagnostic) {
+ this.diagnostic = checkNotNull(diagnostic);
+ }
+
+ /**
+ * Constructor, will instantiate a default diagnostic.
+ */
+ public DiagnosticSupport() {
+ this.diagnostic = createDiagnostic();
+ }
+
+ public BasicDiagnostic getDiagnostic() {
+ return diagnostic;
+ }
+
+ /**
+ * Creates the BasicDiagnostic that will be used by this computation. Can be overridden if necessary.
+ *
+ * @return A new empty BasicDiagnostic.
+ */
+ protected BasicDiagnostic createDiagnostic() {
+ return new BasicDiagnostic(EMFCompareIDEUIPlugin.PLUGIN_ID, 0, null, new Object[0]);
+ }
+
+ /**
+ * Thread safely merge the given diagnostic to the {@link #diagnostic} field.
+ *
+ * @param resourceDiagnostic
+ * the diagnostic to be added to the global diagnostic.
+ */
+ public void merge(final Diagnostic resourceDiagnostic) {
+ synchronized(diagnostic) {
+ diagnostic.merge(resourceDiagnostic);
+ }
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/IComputation.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/IComputation.java
new file mode 100644
index 000000000..1f86cb0c7
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/IComputation.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Obeo.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.logical.resolver;
+
+import com.google.common.util.concurrent.FutureCallback;
+
+/**
+ * Encapsulates the logic of a computation that can be identified by a key.
+ *
+ * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
+ * @param <T>
+ * The type of the keys used to identify the computations.
+ */
+public interface IComputation<T> extends Runnable {
+
+ /**
+ * The computation identifier.
+ *
+ * @return The computation identifier.
+ */
+ T getKey();
+
+ /**
+ * Post-treatment.
+ *
+ * @return The post-tretament to run when this computation is over, whatever its outcome. This should be
+ * called by the "framework" in a finally clause to guarantee it is always executed. It is allowed
+ * for implementors to return {@code null}.
+ */
+ FutureCallback<Object> getPostTreatment();
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/IImplicitDependencies.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/IImplicitDependencies.java
new file mode 100644
index 000000000..35fdaa226
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/IImplicitDependencies.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Obeo.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.logical.resolver;
+
+import java.util.Set;
+
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.ecore.resource.URIConverter;
+
+/**
+ * This interface describes the contract of providing the set of files that must be used "together with" a
+ * given file, whatever the desired semantics of "together with" is. It is used in EMFCompare to get the files
+ * that are part of the same logical model when it's not possible to guess that by looking at the actual
+ * dependencies (via EReferences).
+ *
+ * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
+ */
+public interface IImplicitDependencies {
+
+ /**
+ * Provides a set continaing the given URI plus its implicit dependencies (those that cannot be found by
+ * instpecting the cross-references of the resource represented by the given URI).
+ *
+ * @param uri
+ * URI of a model resource for which we want the set of related dependencies
+ * @param uriConverter
+ * URI Converter to use
+ * @return Must return a Set that's never null nor empty, that contains at least the given uri, plus its
+ * implicit dependencies if it has any..
+ */
+ Set<URI> of(URI uri, URIConverter uriConverter);
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/IResolutionContext.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/IResolutionContext.java
new file mode 100644
index 000000000..10cfa05fb
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/IResolutionContext.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Obeo.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.logical.resolver;
+
+import com.google.common.eventbus.EventBus;
+
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.compare.internal.utils.Graph;
+
+/**
+ * A resolution context provides the different elements that can be involved in a comparison taking logical
+ * models into account. One instance of this interface is supposed to provide elements that are consistent
+ * with one another, that is, which can be used together in the same computation.
+ *
+ * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
+ */
+public interface IResolutionContext {
+ /** The {@link EventBus} to use to broadcast and receive events. */
+ EventBus getEventBus();
+
+ /** The graph of dependencies. */
+ Graph<URI> getGraph();
+
+ /** The graph updater, in charge of making changes to the dependency graph. */
+ DependencyGraphUpdater<URI> getGraphUpdater();
+
+ /** The scheduler used to support multi-threading. */
+ ResourceComputationScheduler<URI> getScheduler();
+
+ /** The provider of dependencies between resources / URIs */
+ IResourceDependencyProvider getDependencyProvider();
+
+ /** The resolver for local resources. */
+ IResourceDependencyLocalResolver getLocalResolver();
+
+ /** The resolver for "remote" (git) resources. */
+ IResourceDependencyRemoteResolver getRemoteResolver();
+
+ /** The listener of model resource changes. */
+ ModelResourceListener getModelResourceListener();
+
+ /** The implicit dependencies provider. */
+ IImplicitDependencies getImplicitDependencies();
+
+ /** Initializes the context. */
+ void initialize();
+
+ /** Disposes the context. */
+ void dispose();
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/IResourceDependencyLocalResolver.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/IResourceDependencyLocalResolver.java
new file mode 100644
index 000000000..61bf8ed2c
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/IResourceDependencyLocalResolver.java
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Obeo 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:
+ * Obeo - initial API and implementation
+ * Philip Langer - extract this interface from implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.logical.resolver;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.compare.ide.ui.internal.util.ThreadSafeProgressMonitor;
+
+/**
+ * A resource dependency provider is in charge of providing the dependencies of resources (local or remote
+ * resources, remote meaning hosted in git for instance).
+ *
+ * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
+ */
+public interface IResourceDependencyLocalResolver {
+
+ /**
+ * Allows callers to launch the loading and resolution of the model pointed at by the given URI.
+ * <p>
+ * This will check whether the given storage isn't already being resolved, then submit a job to the
+ * {@link #resolvingPool} to load and resolve the model in a separate thread.
+ * </p>
+ *
+ * @param resourceSet
+ * The resource set in which to load the resource.
+ * @param uri
+ * The uri we are to try and load as a model.
+ * @param diagnostic
+ * The diagnostic
+ * @param tspm
+ * Monitor on which to report progress to the user.
+ * @see LocalResolveComputation
+ */
+ void demandResolve(SynchronizedResourceSet resourceSet, URI uri, DiagnosticSupport diagnostic,
+ ThreadSafeProgressMonitor tspm);
+
+ /**
+ * Make sure that dependencies for the given files are up to date.
+ *
+ * @param monitor
+ * Progress monitor to use
+ * @param diagnostic
+ * Diagnostic to report issues
+ * @param start
+ * files that are the starting points for the update
+ * @throws InterruptedException
+ */
+ void updateDependencies(IProgressMonitor monitor, DiagnosticSupport diagnostic, IFile... start)
+ throws InterruptedException;
+
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/IResourceDependencyProvider.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/IResourceDependencyProvider.java
new file mode 100644
index 000000000..428a1423f
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/IResourceDependencyProvider.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Obeo 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:
+ * Obeo - initial API and implementation
+ * Philip Langer - extract this interface from implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.logical.resolver;
+
+import java.util.Set;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.emf.common.util.URI;
+
+/**
+ * A resource dependency provider is in charge of providing the dependencies of resources (local or remote
+ * resources, remote meaning hosted in git for instance).
+ *
+ * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
+ */
+public interface IResourceDependencyProvider {
+
+ /**
+ * Provides the dependencies of the given file.
+ *
+ * @param file
+ * The file
+ * @return The file's dependencies, as a never null Iterable over the dependencies {@link URI}s.
+ */
+ Iterable<URI> getDependenciesOf(IFile file);
+
+ /**
+ * Provides the dependencies of the given file.
+ *
+ * @param file
+ * The file
+ * @param bounds
+ * The bounds to exclude from the research, in cas the compared resources are part of the same
+ * logical model.
+ * @return The file's dependencies, as a never null Iterable over the dependencies {@link URI}s.
+ */
+ Iterable<URI> getDependenciesOf(IFile file, Set<URI> bounds);
+
+ /**
+ * Indicates whether the given parent has the given URI as child according to the graph of dependencies.
+ *
+ * @param parent
+ * The parent URI
+ * @param candidate
+ * The candidate child
+ * @return {@code true} if and only if the candidate URI is known as a child of the parent URI in the
+ * graph of dependencies.
+ */
+ boolean hasChild(URI parent, URI candidate);
+
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/IResourceDependencyRemoteResolver.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/IResourceDependencyRemoteResolver.java
new file mode 100644
index 000000000..27f657c5c
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/IResourceDependencyRemoteResolver.java
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Obeo 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:
+ * Obeo - initial API and implementation
+ * Philip Langer - extract this interface from implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.logical.resolver;
+
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.compare.ide.ui.internal.util.ThreadSafeProgressMonitor;
+
+/**
+ * A resource dependency provider is in charge of providing the dependencies of resources (local or remote
+ * resources, remote meaning hosted in git for instance).
+ *
+ * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
+ */
+public interface IResourceDependencyRemoteResolver {
+
+ /**
+ * Allows callers to launch the loading and resolution of the model pointed at by the given URI, without
+ * updating the {@link #dependencyGraph} along the way.
+ * <p>
+ * This will check whether the given storage isn't already being resolved, then submit a job to the
+ * {@link #resolvingPool} to load and resolve the model in a separate thread.
+ * </p>
+ *
+ * @param resourceSet
+ * The resource set in which to load the resource.
+ * @param uri
+ * The uri we are to try and load as a model.
+ * @param diagnostic
+ * The diagnostic
+ * @param tspm
+ * Monitor on which to report progress to the user.
+ */
+ void demandRemoteResolve(SynchronizedResourceSet resourceSet, URI uri, DiagnosticSupport diagnostic,
+ ThreadSafeProgressMonitor tspm);
+
+ /**
+ * Provides the computation to run for remote resolutions.
+ *
+ * @param resourceSet
+ * The resource set
+ * @param uri
+ * The URI
+ * @param diagnostic
+ * The diagnostic
+ * @param tspm
+ * The progress monitor
+ * @return The computation to run in the scheduler to resolve the remote URIs.
+ */
+ RemoteResolveComputation getRemoteResolveComputation(SynchronizedResourceSet resourceSet, URI uri,
+ DiagnosticSupport diagnostic, ThreadSafeProgressMonitor tspm);
+
+ /**
+ * Provides the resources set to use for remote resolve computations.
+ *
+ * @param diagnostic
+ * The diagnostic
+ * @param tspm
+ * The progress monitor
+ * @return The resource set to use for remote resolutions.
+ */
+ SynchronizedResourceSet getResourceSetForRemoteResolution(DiagnosticSupport diagnostic,
+ ThreadSafeProgressMonitor tspm);
+
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/LocalModelResolution.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/LocalModelResolution.java
new file mode 100644
index 000000000..d270207f3
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/LocalModelResolution.java
@@ -0,0 +1,83 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Obeo.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.logical.resolver;
+
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IStorage;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.compare.ide.utils.StorageTraversal;
+
+/**
+ * This will be called by Team in order to determine whether a given file can be compared alone, or if it
+ * needs to be compared along with others (and, thus, compared from the synchronize view). Note that only
+ * local data is available here.
+ *
+ * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
+ */
+public class LocalModelResolution extends AbstractResolution {
+ /** The local resolver. */
+ private final IResourceDependencyLocalResolver resolver;
+
+ /**
+ * Constructor.
+ *
+ * @param dependencyProvider
+ * The dependency provider
+ * @param scheduler
+ * multi-thread support to use
+ * @param eventBus
+ * The event bus
+ * @param monitor
+ * Progress monitor to use
+ */
+ public LocalModelResolution(IResolutionContext context, IProgressMonitor monitor) {
+ super(context, monitor);
+ this.resolver = context.getLocalResolver();
+ }
+
+ /**
+ * Executes this treatment.
+ *
+ * @param start
+ * Resource for which we want the traversal
+ * @return The {@link StorageTraversal} for the given resource, never null but possibly empty.
+ * @throws InterruptedException
+ * If the computation is interrupted.
+ */
+ public StorageTraversal run(final IResource start) throws InterruptedException {
+ if (!(start instanceof IFile)) {
+ return new StorageTraversal(new LinkedHashSet<IStorage>());
+ }
+ return call(new Callable<StorageTraversal>() {
+ public StorageTraversal call() throws InterruptedException {
+ resolver.updateDependencies(monitor, diagnostic, (IFile)start);
+
+ if (monitor.isCanceled()) {
+ throw new OperationCanceledException();
+ }
+
+ final Set<IStorage> traversalSet = resolveTraversal((IFile)start, Collections
+ .<URI> emptySet());
+ StorageTraversal traversal = new StorageTraversal(traversalSet, diagnostic.getDiagnostic());
+
+ return traversal;
+ }
+ });
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/LocalModelsResolution.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/LocalModelsResolution.java
new file mode 100644
index 000000000..2eee6e435
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/LocalModelsResolution.java
@@ -0,0 +1,268 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Obeo.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.logical.resolver;
+
+import static com.google.common.collect.Sets.intersection;
+import static org.eclipse.emf.compare.ide.utils.ResourceUtil.asURI;
+import static org.eclipse.emf.compare.ide.utils.ResourceUtil.createURIFor;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IStorage;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.SubMonitor;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIMessages;
+import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIPlugin;
+import org.eclipse.emf.compare.ide.ui.logical.SynchronizationModel;
+import org.eclipse.emf.compare.ide.utils.StorageTraversal;
+
+/**
+ * Called by EMF Compare in order to resolve the logical models corresponding to the given IResources. Only
+ * local data is available.
+ *
+ * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
+ */
+public class LocalModelsResolution extends AbstractResolution {
+ /** The local resolver. */
+ private final IResourceDependencyLocalResolver resolver;
+
+ /** The left resource, never null. */
+ private final IResource left;
+
+ /** The right resource, never null. */
+ private final IResource right;
+
+ /** The origin resource, may be null. */
+ private final IResource origin;
+
+ /**
+ * Constructor.
+ *
+ * @param dependencyProvider
+ * The dependency provider
+ * @param scheduler
+ * multi-thread support
+ * @param eventBus
+ * The event bus to signal events
+ * @param left
+ * left resource
+ * @param right
+ * right resource
+ * @param origin
+ * common ancestor resource, may be null
+ * @param monitor
+ * The progress monitor to use
+ */
+ public LocalModelsResolution(IResolutionContext context, IResource left, IResource right,
+ IResource origin, IProgressMonitor monitor) {
+ super(context, monitor);
+ this.left = Preconditions.checkNotNull(left);
+ this.right = Preconditions.checkNotNull(right);
+ this.origin = origin;
+ this.resolver = context.getLocalResolver();
+ }
+
+ /**
+ * Executes this treatment.
+ *
+ * @return The Logical model to use to compare the 2 or 3 resources.
+ * @throws InterruptedException
+ * If the treatment is interrupted.
+ */
+ public SynchronizationModel run() throws InterruptedException {
+ try {
+ if (allResourcesAreFiles()) {
+ return resolveLocalFiles();
+ } else {
+ return resolveLocalResources();
+ }
+ } finally {
+ monitor.setWorkRemaining(0);
+ }
+ }
+
+ /**
+ * Indicates whether all the resources are instances of {@link IFile}.
+ *
+ * @return <code>true</code> if and only if left and right are {@link IFile}s and origin is null or is an
+ * {@link IFile}.
+ */
+ protected boolean allResourcesAreFiles() {
+ return left instanceof IFile && right instanceof IFile && (origin == null || origin instanceof IFile);
+ }
+
+ /**
+ * Resolve the local resources.
+ *
+ * @return The synchronization model that contains the logical models of each side.
+ * @throws InterruptedException
+ * In case it is interrupted.
+ */
+ private SynchronizationModel resolveLocalResources() throws InterruptedException {
+ // Sub-optimal implementation, we'll only try and resolve each side individually
+ final StorageTraversal leftTraversal;
+ final StorageTraversal rightTraversal;
+ final StorageTraversal originTraversal;
+ // CHECKSTYLE:OFF No, I won't create constants.
+ if (origin != null) {
+ leftTraversal = resolveLocalModel(left, monitor.newChild(33));
+ rightTraversal = resolveLocalModel(right, monitor.newChild(33));
+ originTraversal = resolveLocalModel(origin, monitor.newChild(34));
+ } else {
+ leftTraversal = resolveLocalModel(left, monitor.newChild(50));
+ rightTraversal = resolveLocalModel(right, monitor.newChild(50));
+ originTraversal = new StorageTraversal(Sets.<IStorage> newLinkedHashSet());
+ }
+ // CHECKSTYLE:ON
+
+ return new SynchronizationModel(leftTraversal, rightTraversal, originTraversal);
+ }
+
+ /**
+ * Resolve one local model.
+ *
+ * @param start
+ * The resource to start from.
+ * @param subMonitor
+ * The progress monitor to use
+ * @return The traversal for the given resource.
+ * @throws InterruptedException
+ * In case it is interrupted.
+ */
+ private StorageTraversal resolveLocalModel(final IResource start, SubMonitor subMonitor)
+ throws InterruptedException {
+ LocalModelResolution comp = new LocalModelResolution(context, subMonitor);
+ return comp.run(start);
+ }
+
+ /**
+ * Resolve several local files.
+ *
+ * @return The synchronization model that contains the logical models of each side.
+ * @throws InterruptedException
+ * In case it is interrupted.
+ */
+ private SynchronizationModel resolveLocalFiles() throws InterruptedException {
+ return call(new Callable<SynchronizationModel>() {
+ public SynchronizationModel call() throws Exception {
+ if (origin instanceof IFile) {
+ resolver.updateDependencies(monitor, diagnostic, (IFile)left, (IFile)right, (IFile)origin);
+ } else {
+ resolver.updateDependencies(monitor, diagnostic, (IFile)left, (IFile)right);
+ }
+
+ final URI leftURI = createURIFor((IFile)left);
+ final URI rightURI = createURIFor((IFile)right);
+ final URI originURI;
+ final Set<IFile> startingPoints;
+ if (origin instanceof IFile) {
+ startingPoints = ImmutableSet.of((IFile)left, (IFile)right, (IFile)origin);
+ originURI = createURIFor((IFile)origin);
+ } else {
+ startingPoints = ImmutableSet.of((IFile)left, (IFile)right);
+ originURI = null;
+ }
+
+ if (monitor.isCanceled()) {
+ throw new OperationCanceledException();
+ }
+
+ final Set<IStorage> leftTraversal;
+ final Set<IStorage> rightTraversal;
+ final Set<IStorage> originTraversal;
+ if (origin instanceof IFile) {
+ leftTraversal = resolveTraversal((IFile)left, ImmutableSet.of(rightURI, originURI));
+ rightTraversal = resolveTraversal((IFile)right, ImmutableSet.of(leftURI, originURI));
+ originTraversal = resolveTraversal((IFile)origin, ImmutableSet.of(leftURI, rightURI));
+ } else {
+ leftTraversal = resolveTraversal((IFile)left, Collections.singleton(rightURI));
+ rightTraversal = resolveTraversal((IFile)right, Collections.singleton(leftURI));
+ originTraversal = Collections.emptySet();
+ }
+
+ // If one resource of the logical model was pointing to both (or "all three") of our
+ // starting elements, we'll have way too many things in our traversal. We need to remove the
+ // intersection before going any further.
+ Set<IStorage> intersection = intersection(leftTraversal, rightTraversal);
+ if (!originTraversal.isEmpty()) {
+ intersection = intersection(intersection, originTraversal);
+ }
+ logCoherenceThreats(Iterables.transform(startingPoints, asURI()), Iterables.transform(
+ intersection, asURI()));
+
+ final Set<IStorage> actualLeft = new LinkedHashSet<IStorage>(Sets.difference(leftTraversal,
+ intersection));
+ final Set<IStorage> actualRight = new LinkedHashSet<IStorage>(Sets.difference(rightTraversal,
+ intersection));
+ final Set<IStorage> actualOrigin = new LinkedHashSet<IStorage>(Sets.difference(
+ originTraversal, intersection));
+ final SynchronizationModel synchronizationModel = new SynchronizationModel(
+ new StorageTraversal(actualLeft), new StorageTraversal(actualRight),
+ new StorageTraversal(actualOrigin), diagnostic.getDiagnostic());
+
+ return synchronizationModel;
+ }
+ });
+ }
+
+ /**
+ * When executing local comparisons, we resolve the full logical model of both (or "all three of") the
+ * compared files.
+ * <p>
+ * If there is one resource in the scope that references all of these starting points, then we'll have
+ * perfectly identical logical models for all comparison sides. Because of that, we need to constrain the
+ * logical model of each starting point to only parts that are not accessible from other starting points.
+ * This might cause coherence issues as merging could thus "break" references from other files to our
+ * compared ones.
+ * </p>
+ * <p>
+ * This method will be used to browse the files that are removed from the logical model, and log a warning
+ * for the files that are removed even though they are "parents" of one of the starting points.
+ * </p>
+ *
+ * @param startingPoints
+ * Starting points of the comparison.
+ * @param removedFromModel
+ * All files that have been removed from the comparison scope.
+ */
+ private void logCoherenceThreats(Iterable<URI> startingPoints, Iterable<URI> removedFromModel) {
+ final Set<URI> coherenceThreats = new LinkedHashSet<URI>();
+ for (URI start : startingPoints) {
+ for (URI removed : removedFromModel) {
+ if (context.getDependencyProvider().hasChild(removed, start)) {
+ coherenceThreats.add(removed);
+ }
+ }
+ }
+
+ if (!coherenceThreats.isEmpty()) {
+ // FIXME: should be added to diagnostic instead
+ final String message = EMFCompareIDEUIMessages.getString("ModelResolver.coherenceWarning"); //$NON-NLS-1$
+ final String details = Iterables.toString(coherenceThreats);
+ EMFCompareIDEUIPlugin.getDefault().getLog().log(
+ new Status(IStatus.WARNING, EMFCompareIDEUIPlugin.PLUGIN_ID, message + '\n' + details));
+ }
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/LocalMonitoredProxyCreationListener.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/LocalMonitoredProxyCreationListener.java
new file mode 100644
index 000000000..c4fb12a21
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/LocalMonitoredProxyCreationListener.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Obeo.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.logical.resolver;
+
+import com.google.common.eventbus.EventBus;
+
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.compare.ide.ui.internal.util.ThreadSafeProgressMonitor;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EStructuralFeature;
+import org.eclipse.emf.ecore.InternalEObject;
+import org.eclipse.emf.ecore.resource.Resource;
+
+/**
+ * Implementation of {@link IProxyCreationListener} for local resolutions.
+ *
+ * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
+ */
+class LocalMonitoredProxyCreationListener extends AbstractMonitoredProxyCreationListener {
+
+ /** The event bus to use to notify interesting events. */
+ protected final EventBus eventBus;
+
+ /** The local resolver. */
+ protected final IResourceDependencyLocalResolver localResolver;
+
+ /**
+ * Constructor.
+ *
+ * @param monitor
+ * The progress monitor to use
+ * @param eventBus
+ * The event bus
+ * @param dependencyProvider
+ * The dependency provider
+ * @param diagnostic
+ * The diagnostic
+ */
+ public LocalMonitoredProxyCreationListener(ThreadSafeProgressMonitor monitor, EventBus eventBus,
+ IResourceDependencyLocalResolver localResolver, DiagnosticSupport diagnostic) {
+ super(monitor, diagnostic);
+ this.localResolver = localResolver;
+ this.eventBus = eventBus;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void proxyCreated(Resource source, EObject eObject, EStructuralFeature feature, EObject proxy,
+ int position) {
+ final URI from = source.getURI();
+ final URI to = ((InternalEObject)proxy).eProxyURI().trimFragment();
+ // TODO Does this work with relative URIs? (isPlatformResource())
+ if (ResolutionUtil.getResolutionScope() != CrossReferenceResolutionScope.SELF
+ && to.isPlatformResource()) {
+ SynchronizedResourceSet resourceSet = (SynchronizedResourceSet)source.getResourceSet();
+ eventBus.post(new ResourceDependencyFoundEvent(from, to, eObject, feature));
+ localResolver.demandResolve(resourceSet, to, diagnostic, tspm);
+ }
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/LocalResolveComputation.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/LocalResolveComputation.java
new file mode 100644
index 000000000..f615d530f
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/LocalResolveComputation.java
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Obeo.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.logical.resolver;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.eventbus.EventBus;
+import com.google.common.util.concurrent.FutureCallback;
+
+import org.eclipse.emf.common.util.Diagnostic;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.compare.ide.ui.internal.util.ThreadSafeProgressMonitor;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.emf.ecore.util.EcoreUtil;
+
+/**
+ * Implements a runnable that will load the EMF resource pointed at by a given URI, then resolve all of its
+ * cross-referenced resources and update the dependency graph accordingly.
+ * <p>
+ * Once done with the resolution, this thread will spawn an independent job to unload the resource.
+ * </p>
+ *
+ * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
+ */
+class LocalResolveComputation extends AbstractResourceResolver implements IComputation<URI> {
+
+ /** The event bus. */
+ protected final EventBus eventBus;
+
+ /** The post-treatment to run after completion (whether or not successful). */
+ private FutureCallback<Object> postTreatment;
+
+ /**
+ * Constructor.
+ *
+ * @param scheduler
+ * The scheduler to use
+ * @param eventBus
+ * The event bus
+ * @param diagnostic
+ * The diagnostic
+ * @param resourceSet
+ * The resource set
+ * @param uri
+ * The URI
+ * @param postTreatment
+ * The post-treatment
+ * @param monitor
+ * The progress monitor
+ */
+ public LocalResolveComputation(ResourceComputationScheduler<URI> scheduler, EventBus eventBus,
+ DiagnosticSupport diagnostic, SynchronizedResourceSet resourceSet, URI uri,
+ FutureCallback<Object> postTreatment, ThreadSafeProgressMonitor monitor) {
+ super(scheduler, diagnostic, resourceSet, uri, monitor);
+ this.eventBus = checkNotNull(eventBus);
+ this.postTreatment = postTreatment;
+ }
+
+ /** {@inheritDoc} */
+ public void run() {
+ if (ResolutionUtil.isInterruptedOrCanceled(tspm)) {
+ scheduler.demandShutdown();
+ return;
+ }
+
+ final Resource resource = resourceSet.loadResource(uri);
+ Diagnostic resourceDiagnostic = EcoreUtil.computeDiagnostic(resource, true);
+ if (resourceDiagnostic.getSeverity() >= Diagnostic.WARNING) {
+ diagnostic.merge(resourceDiagnostic);
+ }
+ eventBus.post(new ResolvedEvent<URI>(uri));
+ demandUnload(resource);
+ }
+
+ public URI getKey() {
+ return uri;
+ }
+
+ public FutureCallback<Object> getPostTreatment() {
+ return postTreatment;
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ModelResourceListener.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ModelResourceListener.java
new file mode 100644
index 000000000..f3ea39027
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ModelResourceListener.java
@@ -0,0 +1,147 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Obeo.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.logical.resolver;
+
+import static org.eclipse.emf.compare.ide.utils.ResourceUtil.createURIFor;
+import static org.eclipse.emf.compare.ide.utils.ResourceUtil.hasModelType;
+
+import com.google.common.collect.ImmutableSet;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceChangeEvent;
+import org.eclipse.core.resources.IResourceChangeListener;
+import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.core.resources.IResourceDeltaVisitor;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIPlugin;
+
+/**
+ * This will listen to workspace changes and react to all changes on "model" resources as determined by
+ * {@link ThreadedModelResolver#MODEL_CONTENT_TYPES}.
+ *
+ * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
+ * @see ThreadedModelResolver#hasModelType(IFile)
+ */
+public class ModelResourceListener implements IResourceChangeListener, IResourceDeltaVisitor {
+ /** Keeps track of the URIs that need to be reparsed when next we need the dependencies graph . */
+ protected final Set<URI> changedURIs;
+
+ /** Tracks the files that have been removed. */
+ protected final Set<URI> removedURIs;
+
+ /** Prevents concurrent access to the two internal sets. */
+ protected final ReentrantLock internalLock;
+
+ /** Initializes this listener. */
+ public ModelResourceListener() {
+ this.changedURIs = new LinkedHashSet<URI>();
+ this.removedURIs = new LinkedHashSet<URI>();
+ this.internalLock = new ReentrantLock();
+ }
+
+ /** {@inheritDoc} */
+ public void resourceChanged(IResourceChangeEvent event) {
+ final IResourceDelta delta = event.getDelta();
+ if (delta == null) {
+ return;
+ }
+
+ /*
+ * We must block any and all threads from using the two internal sets through either popChangedURIs or
+ * popRemovedURIs while we are parsing a resource delta. This particular locking is here to avoid such
+ * misuses.
+ */
+ internalLock.lock();
+ try {
+ delta.accept(this);
+ } catch (CoreException e) {
+ EMFCompareIDEUIPlugin.getDefault().log(e);
+ } finally {
+ internalLock.unlock();
+ }
+ }
+
+ /**
+ * Retrieves the set of all changed URIs since we last updated the dependencies graph, and clears it for
+ * subsequent calls.
+ *
+ * @return The set of all changed URIs since we last updated the dependencies graph.
+ */
+ public Set<URI> popChangedURIs() {
+ final Set<URI> changed;
+ internalLock.lock();
+ try {
+ changed = ImmutableSet.copyOf(changedURIs);
+ changedURIs.clear();
+ } finally {
+ internalLock.unlock();
+ }
+ return changed;
+ }
+
+ /**
+ * Retrieves the set of all removed URIs since we last updated the dependencies graph, and clears it for
+ * subsequent calls.
+ *
+ * @return The set of all removed URIs since we last updated the dependencies graph.
+ */
+ public Set<URI> popRemovedURIs() {
+ final Set<URI> removed;
+ internalLock.lock();
+ try {
+ removed = ImmutableSet.copyOf(removedURIs);
+ removedURIs.clear();
+ } finally {
+ internalLock.unlock();
+ }
+ return removed;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.core.resources.IResourceDeltaVisitor#visit(org.eclipse.core.resources.IResourceDelta)
+ */
+ public boolean visit(IResourceDelta delta) throws CoreException {
+ // Note : the lock (#lock) must be acquired by the current thread _before_ calling #accept() on
+ // this visitor.
+ if (delta.getFlags() == IResourceDelta.MARKERS || delta.getResource().getType() != IResource.FILE) {
+ return true;
+ }
+
+ final IFile file = (IFile)delta.getResource();
+ final URI fileURI = createURIFor(file);
+ // We can't check the content type of a removed resource
+ if (delta.getKind() == IResourceDelta.REMOVED) {
+ removedURIs.add(fileURI);
+ changedURIs.remove(fileURI);
+ } else if (hasModelType(file)) {
+ if ((delta.getKind() & IResourceDelta.CHANGED) != 0) {
+ changedURIs.add(fileURI);
+ // Probably can't happen, but let's stay on the safe side
+ removedURIs.remove(fileURI);
+ } else if ((delta.getKind() & IResourceDelta.ADDED) != 0) {
+ // If a previously removed resource is changed, we can assume it's been re-added
+ if (removedURIs.remove(fileURI)) {
+ changedURIs.add(fileURI);
+ }
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ModelResourceVisitor.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ModelResourceVisitor.java
new file mode 100644
index 000000000..274870327
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ModelResourceVisitor.java
@@ -0,0 +1,89 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Obeo.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.logical.resolver;
+
+import static org.eclipse.emf.compare.ide.utils.ResourceUtil.hasModelType;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceVisitor;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.compare.ide.ui.internal.util.ThreadSafeProgressMonitor;
+import org.eclipse.emf.compare.ide.utils.ResourceUtil;
+
+/**
+ * This implementation of a resource visitor will allow us to browse a given hierarchy and resolve the models
+ * files in contains, as determined by {@link ThreadedModelResolver#MODEL_CONTENT_TYPES}.
+ *
+ * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
+ * @see ThreadedModelResolver#hasModelType(IFile)
+ */
+public class ModelResourceVisitor implements IResourceVisitor {
+ /** Resource set in which to load the model files this visitor will find. */
+ private final SynchronizedResourceSet resourceSet;
+
+ /** Monitor on which to report progress to the user. */
+ private final ThreadSafeProgressMonitor tspm;
+
+ /** Scheduler to use. */
+ private final ResourceComputationScheduler<URI> scheduler;
+
+ /** Local dependency resolver. */
+ private final IResourceDependencyLocalResolver resolver;
+
+ /** Diagnostic. */
+ private final DiagnosticSupport diagnostic;
+
+ /**
+ * Default constructor.
+ *
+ * @param scheduler
+ * The scheduler
+ * @param resourceSet
+ * The resource set in which this visitor will load the model files it finds.
+ * @param resolver
+ * the local dependency resolver
+ * @param diagnostic
+ * The diagnostic
+ * @param monitor
+ * Monitor on which to report progress to the user.
+ */
+ public ModelResourceVisitor(ResourceComputationScheduler<URI> scheduler,
+ SynchronizedResourceSet resourceSet, IResourceDependencyLocalResolver resolver,
+ DiagnosticSupport diagnostic, ThreadSafeProgressMonitor monitor) {
+ this.resourceSet = resourceSet;
+ this.scheduler = scheduler;
+ this.resolver = resolver;
+ this.diagnostic = diagnostic;
+ this.tspm = monitor;
+ }
+
+ /** {@inheritDoc} */
+ public boolean visit(IResource resource) throws CoreException {
+ if (ResolutionUtil.isInterruptedOrCanceled(tspm)) {
+ scheduler.demandShutdown();
+ // cancel the visit
+ throw new OperationCanceledException();
+ }
+
+ if (resource instanceof IFile) {
+ final IFile file = (IFile)resource;
+ if (hasModelType(file)) {
+ final URI expectedURI = ResourceUtil.createURIFor(file);
+ resolver.demandResolve(resourceSet, expectedURI, diagnostic, tspm);
+ }
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ModelsResolution.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ModelsResolution.java
new file mode 100644
index 000000000..5cc82ce9d
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ModelsResolution.java
@@ -0,0 +1,516 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Obeo.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.logical.resolver;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Iterables.concat;
+import static com.google.common.collect.Iterables.transform;
+import static org.eclipse.emf.compare.ide.ui.internal.util.PlatformElementUtil.adaptAs;
+import static org.eclipse.emf.compare.ide.utils.ResourceUtil.asURI;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IStorage;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.compare.ide.ui.internal.util.ThreadSafeProgressMonitor;
+import org.eclipse.emf.compare.ide.ui.logical.IStorageProviderAccessor;
+import org.eclipse.emf.compare.ide.ui.logical.IStorageProviderAccessor.DiffSide;
+import org.eclipse.emf.compare.ide.ui.logical.SynchronizationModel;
+import org.eclipse.emf.compare.ide.utils.ResourceUtil;
+import org.eclipse.emf.compare.ide.utils.StorageTraversal;
+import org.eclipse.emf.compare.ide.utils.StorageURIConverter;
+
+/**
+ * Computation that resolves 2 or 3 storages (left, right and potentially origin).
+ *
+ * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
+ */
+public class ModelsResolution extends AbstractResolution {
+
+ /** Storage Accessor. */
+ private final IStorageProviderAccessor storageAccessor;
+
+ /** The left storage. */
+ private final IStorage left;
+
+ /** The right storage. */
+ private final IStorage right;
+
+ /** The ancestor storage, can be <code>null</code>. */
+ private final IStorage origin;
+
+ /** The local resolver. */
+ private final IResourceDependencyLocalResolver localResolver;
+
+ /** The remote resolver. */
+ private final IResourceDependencyRemoteResolver remoteResolver;
+
+ /**
+ * At least one of {@link #left}, {@link #right} and {@link #origin} must be non-null.
+ *
+ * @param dependencyProvider
+ * The dependency provider
+ * @param scheduler
+ * The muti-thread support to use
+ * @param eventBus
+ * The event bus
+ * @param monitor
+ * The progress monitor to use to report progress
+ * @param storageAccessor
+ * The storage accessor, must not be {@code null}
+ * @param left
+ * The left storage, can be {@code null}
+ * @param right
+ * The right storage, can be {@code null}
+ * @param origin
+ * The ancestor storage, can be {@code null}
+ */
+ public ModelsResolution(IResolutionContext context, IProgressMonitor monitor,
+ IStorageProviderAccessor storageAccessor, IStorage left, IStorage right, IStorage origin) {
+ super(context, monitor);
+ this.localResolver = context.getLocalResolver();
+ this.remoteResolver = context.getRemoteResolver();
+ this.storageAccessor = checkNotNull(storageAccessor);
+ this.left = left;
+ this.right = right;
+ this.origin = origin;
+ checkArgument(left != null || right != null || origin != null);
+ }
+
+ /**
+ * Executes the resolution.
+ *
+ * @return The logical model to use to compare the given storages
+ * @throws InterruptedException
+ * If the treatment is interrupted.
+ */
+ public SynchronizationModel run() throws InterruptedException {
+ return call(new Callable<SynchronizationModel>() {
+ public SynchronizationModel call() throws Exception {
+ final IFile leftFile = adaptAs(left, IFile.class);
+
+ final SynchronizationModel synchronizationModel;
+ if (leftFile != null) {
+ synchronizationModel = resolveModelsWithLocal(leftFile, new ThreadSafeProgressMonitor(
+ monitor));
+ } else {
+ synchronizationModel = resolveRemoteModels(new ThreadSafeProgressMonitor(monitor));
+ }
+
+ return synchronizationModel;
+ }
+ });
+ }
+
+ /**
+ * Overridden to set the work remaining to zero on the progress monitor used.
+ */
+ @Override
+ protected Runnable getFinalizeResolvingRunnable() {
+ return new Runnable() {
+ public void run() {
+ ModelsResolution.super.getFinalizeResolvingRunnable().run();
+ if (monitor != null) {
+ monitor.setWorkRemaining(0);
+ }
+ }
+ };
+ }
+
+ /**
+ * The 'left' model we've been fed is a local file. We'll assume that the whole 'left' side of this
+ * comparison is local and resolve everything for that side as we would for local comparisons : update the
+ * dependency graph according to our resource listener, lookup for cross-references to/from the left
+ * resource according to the {@link #getResolutionScope() resolution scope}... Once we've resolved the
+ * local traversal, we'll use that as a base to infer the two remote sides, then "augment" it with the
+ * cross-references of the remote variants of these resources.
+ *
+ * @param leftFile
+ * File corresponding to the left side of this comparison.
+ * @param tspm
+ * Monitor on which to report progress to the user.
+ * @return The SynchronizationModel describing the traversals of all three sides of this logical model.
+ * @throws InterruptedException
+ * Thrown if the resolution is cancelled or interrupted one way or another.
+ */
+ private SynchronizationModel resolveModelsWithLocal(final IFile leftFile,
+ final ThreadSafeProgressMonitor tspm) throws InterruptedException {
+ // Update changes and compute dependencies for left
+ // Then load the same set of resources for the remote sides, completing it top-down
+ localResolver.updateDependencies(monitor, diagnostic, leftFile);
+
+ if (tspm.isCanceled()) {
+ throw new OperationCanceledException();
+ }
+
+ final Set<IStorage> leftTraversal = resolveTraversal(leftFile, Collections.<URI> emptySet());
+
+ return resolveRemoteTraversals(leftTraversal, tspm);
+ }
+
+ /**
+ * All three sides we've been fed are remote. We'll resolve all three with a simple a top-down algorithm
+ * (detect only outgoing cross-references).
+ *
+ * @param tspm
+ * Monitor on which to report progress to the user.
+ * @return The SynchronizationModel describing the traversals of all three sides of this logical model.
+ * @throws InterruptedException
+ * Thrown if the resolution is cancelled or interrupted one way or another.
+ */
+ private SynchronizationModel resolveRemoteModels(ThreadSafeProgressMonitor tspm)
+ throws InterruptedException {
+ final Set<IStorage> leftTraversal = resolveRemoteTraversal(left, Collections.<URI> emptySet(),
+ DiffSide.SOURCE, tspm);
+ return resolveRemoteTraversals(leftTraversal, tspm);
+ }
+
+ /**
+ * Resolve the remote sides (right and origin, or right alone in case of two-way) of this comparison,
+ * inferring a "starting traversal" from the left side.
+ * <p>
+ * Do note that {@code leftTraversal} <b>will be changed</b> as a result of this call if the right and/or
+ * origin sides contain a reference to another resource that was not found from the left
+ * cross-referencing, yet does exist in the left side.
+ * </p>
+ *
+ * @param leftTraversal
+ * The already resolved left traversal, to be augmented if right and/or origin have some new
+ * resources in their logical model.
+ * @param tspm
+ * Monitor on which to report progress to the user.
+ * @return The SynchronizationModel describing the traversals of all three sides of this logical model.
+ * @throws InterruptedException
+ * Thrown if the resolution is cancelled or interrupted one way or another.
+ */
+ private SynchronizationModel resolveRemoteTraversals(Set<IStorage> leftTraversal,
+ ThreadSafeProgressMonitor tspm) throws InterruptedException {
+ final Set<IStorage> rightTraversal = resolveRemoteTraversal(right, Iterables.transform(leftTraversal,
+ asURI()), DiffSide.REMOTE, tspm);
+ final Set<IStorage> differenceRightLeft = difference(rightTraversal, asURISet(leftTraversal));
+ loadAdditionalRemoteStorages(leftTraversal, rightTraversal, differenceRightLeft, tspm);
+
+ final Set<IStorage> originTraversal;
+ if (origin != null) {
+ final Set<URI> unionLeftRight = Sets.newLinkedHashSet(Iterables.transform(Sets.union(
+ leftTraversal, rightTraversal), asURI()));
+ originTraversal = resolveRemoteTraversal(origin, unionLeftRight, DiffSide.ORIGIN, tspm);
+ Set<IStorage> differenceOriginLeft = difference(originTraversal, asURISet(leftTraversal));
+ Set<IStorage> differenceOriginRight = difference(originTraversal, asURISet(rightTraversal));
+ Set<IStorage> additional = symmetricDifference(differenceOriginLeft, differenceOriginRight);
+ loadAdditionalRemoteStorages(leftTraversal, rightTraversal, originTraversal, additional, tspm);
+ } else {
+ originTraversal = Collections.emptySet();
+ }
+ final SynchronizationModel synchronizationModel = new SynchronizationModel(new StorageTraversal(
+ leftTraversal), new StorageTraversal(rightTraversal), new StorageTraversal(originTraversal),
+ diagnostic.getDiagnostic());
+
+ return synchronizationModel;
+ }
+
+ /**
+ * If we found some storages in the right traversal that were not part of the left traversal, we need to
+ * check whether they exist in the left, since in such a case they must be considered as part of the same
+ * logical model.
+ * <p>
+ * <b>Important</b> : note that the input {@code left} and {@code right} sets <b>will be modified</b> as a
+ * result of this call if there are any additional storages to load on these sides.
+ * </p>
+ *
+ * @param leftSet
+ * Traversal of the left logical model.
+ * @param rightSet
+ * Traversal of the right logical model.
+ * @param additional
+ * the addition storages we are to lookup in left.
+ * @param tspm
+ * Monitor on which to report progress to the user.
+ * @return The set of all additional resources (both on left and right) that have been loaded as a result
+ * of this call.
+ * @throws InterruptedException
+ * Thrown if the resolution is cancelled or interrupted one way or another.
+ */
+ private Set<IStorage> loadAdditionalRemoteStorages(Set<IStorage> leftSet, Set<IStorage> rightSet,
+ Set<IStorage> additional, ThreadSafeProgressMonitor tspm) throws InterruptedException {
+ /*
+ * This loop will be extremely costly at best, but we hope the case to be sufficiently rare (and the
+ * new resources well spread when it happens) not to pose an issue in the most frequent cases.
+ */
+
+ final Set<IStorage> additionalStorages = new LinkedHashSet<IStorage>();
+ final Set<URI> additionalURIs = new LinkedHashSet<URI>();
+ // Have we found new resources in the right as compared to the left?
+ Set<IStorage> differenceRightLeft = additional;
+ while (!differenceRightLeft.isEmpty()) {
+ // There's at least one resource in the right that was not found in the left.
+ /*
+ * This might be a new resource added on the right side... but it might also be a cross-reference
+ * that's been either removed from left or added in right. In this second case, we need the
+ * resource to be present in both traversals to make sure we'll be able to properly detect
+ * potential conflicts. However, since this resource could itself be a part of a larger logical
+ * model, we need to start the resolving again with it.
+ */
+ final Set<IStorage> additionalLeft = findAdditionalRemoteTraversal(leftSet, differenceRightLeft,
+ DiffSide.SOURCE, tspm);
+ leftSet.addAll(additionalLeft);
+ for (IStorage storage : additionalLeft) {
+ final URI newURI = asURI().apply(storage);
+ if (additionalURIs.add(newURI)) {
+ additionalStorages.add(storage);
+ }
+ }
+ /*
+ * have we only loaded the resources that were present in the right but not in the left, or have
+ * we found even more?
+ */
+ final Set<IStorage> differenceAdditionalLeftRight = difference(additionalLeft, asURISet(rightSet));
+ // If so, we once more need to augment the right traversal
+ final Set<IStorage> additionalRight = findAdditionalRemoteTraversal(rightSet,
+ differenceAdditionalLeftRight, DiffSide.REMOTE, tspm);
+ rightSet.addAll(additionalRight);
+ for (IStorage storage : additionalRight) {
+ final URI newURI = asURI().apply(storage);
+ if (additionalURIs.add(newURI)) {
+ additionalStorages.add(storage);
+ }
+ }
+ // Start this loop anew if we once again augmented the right further than what we had in
+ // left
+ differenceRightLeft = difference(additionalRight, asURISet(leftSet));
+ }
+ return additionalStorages;
+ }
+
+ /**
+ * If we found some storages in the origin traversal that were part of neither the left nor the right
+ * traversals, we need to check whether they exist in them, since in such a case they must be considered
+ * as part of the same logical model.
+ * <p>
+ * <b>Important</b> : note that the input {@code left}, {@code right} and {@code origin} sets <b>will be
+ * modified</b> as a result of this call if there are any additional storages to load on either side.
+ * </p>
+ *
+ * @param leftSet
+ * Traversal of the left logical model.
+ * @param rightSet
+ * Traversal of the right logical model.
+ * @param originSet
+ * Traversal of the origin logical model.
+ * @param additional
+ * the set of additional storages we are to lookup in right and left.
+ * @param tspm
+ * Monitor on which to report progress to the user.
+ * @throws InterruptedException
+ * Thrown if the resolution is cancelled or interrupted one way or another.
+ */
+ private void loadAdditionalRemoteStorages(Set<IStorage> leftSet, Set<IStorage> rightSet,
+ Set<IStorage> originSet, Set<IStorage> additional, ThreadSafeProgressMonitor tspm)
+ throws InterruptedException {
+ /*
+ * This loop will be extremely costly at best, but we hope the case to be sufficiently rare (and the
+ * new resources well spread when it happens) not to pose an issue in the most frequent cases.
+ */
+
+ Set<IStorage> additionalStorages = additional;
+ while (!additionalStorages.isEmpty()) {
+ // There's at least one resource that is in the origin set yet neither in left nor in
+ // right.
+ final Set<IStorage> additionalLeftRightComparedToOrigin = loadAdditionalRemoteStorages(leftSet,
+ rightSet, additionalStorages, tspm);
+ /*
+ * Have we found even more resources to add to the traversal? If so, augment the origin
+ * accordingly.
+ */
+ final Set<IStorage> additionalOrigin = findAdditionalRemoteTraversal(originSet,
+ additionalLeftRightComparedToOrigin, DiffSide.ORIGIN, tspm);
+ originSet.addAll(additionalOrigin);
+ // If we once again found new storages in the origin, restart the loop.
+ final Set<IStorage> differenceOriginLeft = difference(additionalOrigin, asURISet(leftSet));
+ final Set<IStorage> differenceOriginRight = difference(additionalOrigin, asURISet(rightSet));
+ additionalStorages = symmetricDifference(differenceOriginRight, differenceOriginLeft);
+
+ // Differences between left/right and origin could come from resources that are present in
+ // the origin, but were deleted in one of the sides. As these resources already exist in
+ // the origin, they need to be removed from the additionalStorages
+ additionalStorages.removeAll(originSet);
+ }
+ }
+
+ /**
+ * Tries and resolve the given set of additional storages (as compared to {@code alreadyLoaded}) on the
+ * given side.
+ * <p>
+ * If the storages from {@code additionalStorages} do not (or no longer) exist on the given side, this
+ * will have no effect. Otherwise, they'll be loaded and resolved in order to determine whether they are
+ * part of a larger model. Whether they're part of a larger model or not, they will be returned by this
+ * method as long as they exist on the given side.
+ * </p>
+ *
+ * @param alreadyLoaded
+ * All storages that have already been loaded on the given side. This will prevent us from
+ * resolving the same model more than once.
+ * @param additionalStorages
+ * The set of additional storages we are to find and resolve on the given side.
+ * @param side
+ * Side on which we seek to load additional storages in the traversal.
+ * @param tspm
+ * Monitor on which to report progress to the user.
+ * @return The set of additional storages that are to be added to the traversal of the given side.
+ * @throws InterruptedException
+ * Thrown if the resolution is cancelled or interrupted one way or another.
+ */
+ private Set<IStorage> findAdditionalRemoteTraversal(Set<IStorage> alreadyLoaded,
+ Set<IStorage> additionalStorages, DiffSide side, final ThreadSafeProgressMonitor tspm)
+ throws InterruptedException {
+ final SynchronizedResourceSet resourceSet = remoteResolver.getResourceSetForRemoteResolution(
+ diagnostic, tspm);
+ final StorageURIConverter converter = new RevisionedURIConverter(resourceSet.getURIConverter(),
+ storageAccessor, side);
+ resourceSet.setURIConverter(converter);
+
+ ResourceComputationScheduler<URI> scheduler = context.getScheduler();
+ scheduler.setComputedElements(transform(converter.getLoadedRevisions(), asURI()));
+
+ Iterable<URI> urisToResolve = transform(additionalStorages, asURI());
+ scheduler.computeAll(transform(urisToResolve, resolveRemoteURI(tspm, resourceSet)));
+
+ if (tspm.isCanceled()) {
+ throw new OperationCanceledException();
+ }
+
+ scheduler.clearComputedElements();
+
+ return converter.getLoadedRevisions();
+ }
+
+ /**
+ * Returns the set of all elements that are contained neither in set1 nor in set2.
+ *
+ * @param set1
+ * First of the two sets.
+ * @param set2
+ * Second of the two sets.
+ * @return The set of all elements that are contained neither in set1 nor in set2.
+ */
+ private Set<IStorage> symmetricDifference(Set<IStorage> set1, Set<IStorage> set2) {
+ final Set<URI> uris1 = Sets.newLinkedHashSet(Iterables.transform(set1, asURI()));
+ final Set<URI> uris2 = Sets.newLinkedHashSet(Iterables.transform(set2, asURI()));
+
+ final Set<IStorage> symmetricDifference = new LinkedHashSet<IStorage>();
+ for (IStorage storage1 : set1) {
+ if (!uris2.contains(asURI().apply(storage1))) {
+ symmetricDifference.add(storage1);
+ }
+ }
+ for (IStorage storage2 : set2) {
+ if (!uris1.contains(asURI().apply(storage2))) {
+ symmetricDifference.add(storage2);
+ }
+ }
+ return symmetricDifference;
+ }
+
+ /**
+ * Returns the set of all elements that are contained in {@code set1} but not in {@code set2}.
+ *
+ * @param set1
+ * First of the two sets.
+ * @param set2
+ * Second of the two sets.
+ * @return The set of all elements that are contained in {@code set1} but not in {@code set2}.
+ */
+ private Set<IStorage> difference(Set<IStorage> set1, Set<URI> set2) {
+ final Set<IStorage> difference = new LinkedHashSet<IStorage>();
+ for (IStorage storage1 : set1) {
+ final URI uri = asURI().apply(storage1);
+ if (!set2.contains(uri)) {
+ difference.add(storage1);
+ }
+ }
+ return difference;
+ }
+
+ /**
+ * Resolve the remote traversal of the given storage.
+ *
+ * @param start
+ * Storage to resolve
+ * @param knownVariants
+ * Iterable over all the currently known {@link URI}s that are part of the local logical model
+ * on any side
+ * @param side
+ * The side
+ * @param tspm
+ * Monitor to report progress
+ * @return The traversal of the storage on the given side
+ * @throws InterruptedException
+ * If interrupted.
+ */
+ private Set<IStorage> resolveRemoteTraversal(IStorage start, Iterable<URI> knownVariants, DiffSide side,
+ final ThreadSafeProgressMonitor tspm) throws InterruptedException {
+ // we can't call ResourceUtil.createURIFor(start) if start is null
+ // but the returned Set must be changeable so Collections.emptySet() won't do
+ if (start == null) {
+ return Sets.newLinkedHashSet();
+ }
+ final SynchronizedResourceSet resourceSet = remoteResolver.getResourceSetForRemoteResolution(
+ diagnostic, tspm);
+ final StorageURIConverter converter = new RevisionedURIConverter(resourceSet.getURIConverter(),
+ storageAccessor, side);
+ resourceSet.setURIConverter(converter);
+
+ ResourceComputationScheduler<URI> scheduler = context.getScheduler();
+ scheduler.clearComputedElements();
+
+ final URI startURI = ResourceUtil.createURIFor(start);
+ Iterable<URI> urisToResolve = concat(knownVariants, Collections.singleton(startURI));
+ scheduler.computeAll(transform(urisToResolve, resolveRemoteURI(tspm, resourceSet)));
+
+ if (tspm.isCanceled()) {
+ throw new OperationCanceledException();
+ }
+
+ scheduler.clearComputedElements();
+
+ return converter.getLoadedRevisions();
+ }
+
+ /**
+ * Provides a {@link Function} that converts a givn URI into a Computation that can be run by a
+ * {@link ResourceComputationScheduler}.
+ *
+ * @param tspm
+ * The progress monitor to use
+ * @param resourceSet
+ * The resource set to use
+ * @return A {@link Function}, never {@code null}, that can be used to remotely resolvea given URI.
+ */
+ protected Function<URI, IComputation<URI>> resolveRemoteURI(final ThreadSafeProgressMonitor tspm,
+ final SynchronizedResourceSet resourceSet) {
+ return new Function<URI, IComputation<URI>>() {
+ public IComputation<URI> apply(final URI uri) {
+ return remoteResolver.getRemoteResolveComputation(resourceSet, uri, diagnostic, tspm);
+ }
+ };
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/MonitorCallback.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/MonitorCallback.java
new file mode 100644
index 000000000..298f9603d
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/MonitorCallback.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Obeo.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.logical.resolver;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.util.concurrent.FutureCallback;
+
+import org.eclipse.emf.common.util.BasicDiagnostic;
+import org.eclipse.emf.compare.ide.ui.internal.util.ThreadSafeProgressMonitor;
+
+/**
+ * Callback that updates a progress monitor when called. Also owns a diagnostic that gets update if this is
+ * called upon failure.
+ *
+ * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
+ */
+final class MonitorCallback implements FutureCallback<Object> {
+
+ /** The progress monitor. */
+ private final ThreadSafeProgressMonitor monitor;
+
+ /** The diagnostic. */
+ private final DiagnosticSupport diagnostic;
+
+ /**
+ * Constructor.
+ *
+ * @param diagnostic
+ * The diagnostic.
+ * @param monitor
+ * The monitor.
+ */
+ MonitorCallback(DiagnosticSupport diagnostic, ThreadSafeProgressMonitor monitor) {
+ this.diagnostic = checkNotNull(diagnostic);
+ this.monitor = checkNotNull(monitor);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void onSuccess(Object o) {
+ if (!ResolutionUtil.isInterruptedOrCanceled(monitor)) {
+ // do not report progress anymore when the task has been interrupted of canceled.
+ // It speeds up the cancellation.
+ monitor.worked(1);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void onFailure(final Throwable t) {
+ if (!ResolutionUtil.isInterruptedOrCanceled(monitor)) {
+ // do not report progress or errors anymore when the task has been interrupted of
+ // canceled. It speeds up the cancellation.
+ monitor.worked(1);
+ diagnostic.merge(BasicDiagnostic.toDiagnostic(t));
+ }
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/RemoteMonitoredProxyCreationListener.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/RemoteMonitoredProxyCreationListener.java
new file mode 100644
index 000000000..f5431281a
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/RemoteMonitoredProxyCreationListener.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Obeo.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.logical.resolver;
+
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.compare.ide.internal.utils.ProxyNotifierParserPool.IProxyCreationListener;
+import org.eclipse.emf.compare.ide.ui.internal.util.ThreadSafeProgressMonitor;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EStructuralFeature;
+import org.eclipse.emf.ecore.InternalEObject;
+import org.eclipse.emf.ecore.resource.Resource;
+
+/**
+ * Implementation of {@link IProxyCreationListener} for remote resources.
+ *
+ * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
+ */
+class RemoteMonitoredProxyCreationListener extends AbstractMonitoredProxyCreationListener {
+
+ /** The remote resolver. */
+ protected final IResourceDependencyRemoteResolver remoteResolver;
+
+ /**
+ * Constructor.
+ *
+ * @param monitor
+ * The progress monitor
+ * @param dependencyProvider
+ * The dependency provider
+ * @param diagnostic
+ * The diagnostic
+ */
+ public RemoteMonitoredProxyCreationListener(ThreadSafeProgressMonitor monitor,
+ IResourceDependencyRemoteResolver remoteResolver, DiagnosticSupport diagnostic) {
+ super(monitor, diagnostic);
+ this.remoteResolver = remoteResolver;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void proxyCreated(Resource source, EObject eObject, EStructuralFeature feature, EObject proxy,
+ int position) {
+ final URI to = ((InternalEObject)proxy).eProxyURI().trimFragment();
+ // TODO Does this work with relative URIs? (isPlatformResource())
+ if (ResolutionUtil.getResolutionScope() != CrossReferenceResolutionScope.SELF
+ && to.isPlatformResource()) {
+ SynchronizedResourceSet resourceSet = (SynchronizedResourceSet)source.getResourceSet();
+ remoteResolver.demandRemoteResolve(resourceSet, to, diagnostic, tspm);
+ }
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/RemoteResolveComputation.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/RemoteResolveComputation.java
new file mode 100644
index 000000000..ceb914d0e
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/RemoteResolveComputation.java
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Obeo.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.logical.resolver;
+
+import com.google.common.util.concurrent.FutureCallback;
+
+import org.eclipse.emf.common.util.Diagnostic;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.compare.ide.ui.internal.util.ThreadSafeProgressMonitor;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.emf.ecore.util.EcoreUtil;
+
+/**
+ * Implements a runnable that will load the EMF resource pointed at by a given URI, then resolve all of its
+ * cross-referenced resources and update the dependency graph accordingly.
+ * <p>
+ * Once done with the resolution, this thread will spawn an independent job to unload the resource.
+ * </p>
+ *
+ * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
+ */
+class RemoteResolveComputation extends AbstractResourceResolver implements IComputation<URI> {
+
+ /** Post-treatment to run upon completion, whether or not successful. */
+ private final FutureCallback<Object> postTreatment;
+
+ /**
+ * Constructor.
+ *
+ * @param scheduler
+ * The scheduler
+ * @param diagnostic
+ * The diagnostic
+ * @param resourceSet
+ * The resource set
+ * @param uri
+ * The URI
+ * @param postTreatment
+ * The post-treatment, can be {@code null}
+ * @param monitor
+ * The progress monitor
+ */
+ public RemoteResolveComputation(ResourceComputationScheduler<URI> scheduler,
+ DiagnosticSupport diagnostic, SynchronizedResourceSet resourceSet, URI uri,
+ FutureCallback<Object> postTreatment, ThreadSafeProgressMonitor monitor) {
+ super(scheduler, diagnostic, resourceSet, uri, monitor);
+ this.postTreatment = postTreatment;
+ }
+
+ /** {@inheritDoc} */
+ public void run() {
+ if (ResolutionUtil.isInterruptedOrCanceled(tspm)) {
+ scheduler.demandShutdown();
+ return;
+ }
+
+ final Resource resource = resourceSet.loadResource(uri);
+ Diagnostic resourceDiagnostic = EcoreUtil.computeDiagnostic(resource, true);
+ if (resourceDiagnostic.getSeverity() >= Diagnostic.WARNING) {
+ diagnostic.merge(resourceDiagnostic);
+ }
+ demandUnload(resource);
+ }
+
+ public URI getKey() {
+ return uri;
+ }
+
+ public FutureCallback<Object> getPostTreatment() {
+ return postTreatment;
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResolutionUtil.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResolutionUtil.java
new file mode 100644
index 000000000..27a627e85
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResolutionUtil.java
@@ -0,0 +1,83 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Obeo.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.logical.resolver;
+
+import java.util.List;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIPlugin;
+import org.eclipse.emf.compare.ide.ui.internal.preferences.EMFCompareUIPreferences;
+import org.eclipse.jface.preference.IPreferenceStore;
+
+/**
+ * Utility class that provides a few static methods useful for resolving.
+ *
+ * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
+ */
+public final class ResolutionUtil {
+
+ /**
+ * Private constructor (utility pattern).
+ */
+ private ResolutionUtil() {
+ // Private constructor to prevent instantiation
+ }
+
+ /**
+ * Tells this resolver how much of the dependency graph should be created at once. Note that this value
+ * may change during a resolution, which sole "visible" effect would be to prevent resolution of further
+ * outgoing references if the new value is "SELF".
+ *
+ * @return The current resolution scope.
+ */
+ public static CrossReferenceResolutionScope getResolutionScope() {
+ final IPreferenceStore store = EMFCompareIDEUIPlugin.getDefault().getPreferenceStore();
+ if (store.getBoolean(EMFCompareUIPreferences.DISABLE_RESOLVERS_PREFERENCE)) {
+ return CrossReferenceResolutionScope.SELF;
+ }
+ final String stringValue = store.getString(EMFCompareUIPreferences.RESOLUTION_SCOPE_PREFERENCE);
+ return CrossReferenceResolutionScope.valueOf(stringValue);
+ }
+
+ /**
+ * Checks if the current thread is interrupted or if the given monitor has been canceled.
+ *
+ * @param monitor
+ * the monitor to check
+ * @return true if the current thread has been canceled, false otherwise.
+ */
+ public static boolean isInterruptedOrCanceled(IProgressMonitor monitor) {
+ return Thread.currentThread().isInterrupted() || monitor.isCanceled();
+ }
+
+ /**
+ * Returns the IFile located at the given URI.
+ *
+ * @param uri
+ * URI we need the file for.
+ * @return The IFile located at the given URI.
+ */
+ public static IFile getFileAt(URI uri) {
+ final StringBuilder path = new StringBuilder();
+ List<String> segments = uri.segmentsList();
+ if (uri.isPlatformResource()) {
+ segments = segments.subList(1, segments.size());
+ }
+ for (String segment : segments) {
+ path.append(URI.decode(segment)).append('/');
+ }
+ return ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(path.toString()));
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResolvedEvent.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResolvedEvent.java
new file mode 100644
index 000000000..7f87ec003
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResolvedEvent.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Obeo.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.logical.resolver;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Event that indicates that a resource that should be part of a dependency graph has been found.
+ *
+ * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
+ * @param <T>
+ * The type of the resource identifier.
+ */
+public class ResolvedEvent<T> {
+
+ /** The discovered resource identifier. */
+ private final T node;
+
+ /**
+ * Constructor.
+ *
+ * @param node
+ * The identifier of the discovered resource, must not be {@code null}.
+ */
+ public ResolvedEvent(T node) {
+ this.node = checkNotNull(node);
+ }
+
+ public T getNode() {
+ return node;
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResourceComputationScheduler.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResourceComputationScheduler.java
new file mode 100644
index 000000000..57ccb55f1
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResourceComputationScheduler.java
@@ -0,0 +1,553 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Obeo.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.logical.resolver;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIPlugin;
+
+/**
+ * Class providing facilities to schedule computations and hide as much as possible the multi-threaded
+ * complexity. Each computation is identified by a key. Each computation is supposed to possibly load
+ * resources, and the scheduler also provides a facility to unload resources that are no longer needed.
+ *
+ * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
+ * @param <T>
+ * type of keys used to keep track of currently ongoing computations.
+ */
+public class ResourceComputationScheduler<T> {
+
+ /**
+ * Keeps track of the keys which we are currently computing (or which are queued for computation).
+ * <p>
+ * This along with {@link #computedKeys} will prevent multiple "duplicate" computation threads to be
+ * queued. We assume that this will be sufficient to prevent duplicates, and the computation threads
+ * themselves won't check whether their target has already been computed before starting.
+ * </p>
+ */
+ private final Set<T> currentlyComputing;
+
+ /**
+ * We'll keep track of what's already been computed to avoid duplicate jobs.
+ */
+ private volatile Set<T> computedKeys;
+
+ /** Thread pool for our resolving threads. */
+ private ListeningExecutorService computingPool;
+
+ /** Thread pool for our unloading threads. */
+ private ListeningExecutorService unloadingPool;
+
+ /**
+ * An executor service will be used to shut down the {@link #unloadingPool} and the {@link #computingPool}
+ * .
+ */
+ private ListeningExecutorService terminator;
+
+ /** Tracks if shutdown of {@link #unloadingPool} and {@link #computingPool} is currently in progress. */
+ private final AtomicBoolean shutdownInProgress;
+
+ /**
+ * This will lock will prevent concurrent modifications of this class's fields. Most notably,
+ * {@link #currentlyComputing} and {@link #computedKeys} must not be accessed concurrently by two threads
+ * at once.
+ */
+ private final ReentrantLock lock;
+
+ /** Condition to await for all current task threads to terminate. */
+ private final Condition endOfTasks;
+
+ /** How long to wait for task completion when shutting down the pools. */
+ private final int shutdownWaitDuration;
+
+ /** Unit of the above duration. */
+ private final TimeUnit shutdownWaitUnit;
+
+ /**
+ * Constructor, configured to wait for tasks completion for 5 seconds (will wait at most 10 seconds).
+ */
+ public ResourceComputationScheduler() {
+ this(5, TimeUnit.SECONDS);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param shutdownWaitDuration
+ * Time to wait for current tasks completion when shutting down the pools (will wait at most
+ * twice this amount of time).
+ * @param shutdownWaitUnit
+ * Unit to use to interpret the other parameter.
+ */
+ public ResourceComputationScheduler(int shutdownWaitDuration, TimeUnit shutdownWaitUnit) {
+ this.lock = new ReentrantLock(true);
+ this.endOfTasks = lock.newCondition();
+ this.currentlyComputing = new HashSet<T>();
+ this.shutdownInProgress = new AtomicBoolean(false);
+ this.shutdownWaitDuration = shutdownWaitDuration;
+ this.shutdownWaitUnit = shutdownWaitUnit;
+ }
+
+ /**
+ * Creates the thread pools of this resolver and instantiates the computedElements Set. We cannot keep
+ * pools between resolving calls because in case of cancellation, we have to shutdown the pool to exit
+ * early.
+ * <p>
+ * <b>Pre-conditions:</b>
+ * <ul>
+ * <li>{@link #initialize()} has been called</li>
+ * <li>A lock has been acquired before on this instance's {@link #lock}</li>
+ * <li>{@link #dispose()} has not been called</li>
+ * </ul>
+ * <b>Post-conditions:</b>
+ * <ul>
+ * <li>{@link #computingPool} is not null and is ready to be used</li>
+ * <li>{@link #unloadingPool} is not null and is ready to be used</li>
+ * <li>{@link #computedKeys} is not null</li>
+ * </p>
+ *
+ * @throws InterruptedException
+ * If the set-up is interrupted
+ */
+ private void setUpComputation() throws InterruptedException {
+ final int availableProcessors = Runtime.getRuntime().availableProcessors();
+ ThreadFactory computingThreadFactory = new ThreadFactoryBuilder().setNameFormat(
+ "EMFCompare-ResolvingThread-%d") //$NON-NLS-1$
+ .build();
+ this.computingPool = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(
+ availableProcessors, computingThreadFactory));
+ ThreadFactory unloadingThreadFactory = new ThreadFactoryBuilder().setNameFormat(
+ "EMFCompare-UnloadingThread-%d") //$NON-NLS-1$
+ .build();
+ this.unloadingPool = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(
+ availableProcessors, unloadingThreadFactory));
+ computedKeys = new LinkedHashSet<T>();
+ }
+
+ /**
+ * Shutdown the pools and delete {@link #computedKeys}.
+ * <p>
+ * <b>Post-conditions:</b>
+ * <ul>
+ * <li>{@link #computingPool} is null and is ready to be used</li>
+ * <li>{@link #unloadingPool} is null and is ready to be used</li>
+ * <li>{@link #computedKeys} is null</li>
+ * </p>
+ */
+ private void tearDownComputation() {
+ if (!shutdownInProgress.get()) {
+ shutdownPools();
+ }
+ computedKeys = null;
+ }
+
+ /**
+ * If {@link #shutdownInProgress shutdown has not been requested before}, it submits a new task to
+ * {@link #shutdownPools() shut down} {@link #computingPool} and {@link #unloadingPool}. Do nothing if
+ * current thread already is interrupted.
+ */
+ public void demandShutdown() {
+ if (!Thread.currentThread().isInterrupted()) {
+ if (shutdownInProgress.compareAndSet(false, true)) {
+ Runnable runnable = new Runnable() {
+ public void run() {
+ shutdownPools();
+ }
+ };
+
+ ListenableFuture<?> listenableFuture = terminator.submit(runnable);
+ Futures.addCallback(listenableFuture, new FutureCallback<Object>() {
+ public void onSuccess(Object result) {
+ shutdownInProgress.set(false);
+ }
+
+ public void onFailure(Throwable t) {
+ shutdownInProgress.set(false);
+ EMFCompareIDEUIPlugin.getDefault().log(t);
+ }
+ });
+ }
+ }
+ }
+
+ /**
+ * Shutdown {@link #computingPool} and {@link #unloadingPool} and set these two fields to null.
+ */
+ private synchronized void shutdownPools() {
+ try {
+ // we don't do the closing in one sentence to make sure we call both shutdown methods
+ if (computingPool != null) {
+ shutdownAndAwaitTermination(computingPool);
+ }
+ if (unloadingPool != null) {
+ shutdownAndAwaitTermination(unloadingPool);
+ }
+ } finally {
+ computingPool = null;
+ unloadingPool = null;
+ }
+ }
+
+ /**
+ * Initializes this scheduler, which instantiates its {@link #terminator}. Can be called several times
+ * with no problem.
+ */
+ public synchronized void initialize() {
+ if (!isInitialized()) {
+ this.terminator = MoreExecutors.listeningDecorator(Executors
+ .newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(
+ "EMFCompare-ThreadPoolShutdowner-%d").setPriority(Thread.MAX_PRIORITY).build())); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Indicates whether this scheduler is initialized, i.e. it can run computations.
+ *
+ * @return {@code true} if and only if the scheduler is initialized.
+ */
+ public boolean isInitialized() {
+ return terminator != null;
+ }
+
+ /**
+ * Disposes this scheduler, which shuts down its {@link #terminator}.
+ */
+ public synchronized void dispose() {
+ if (isInitialized()) {
+ terminator.shutdown();
+ terminator = null;
+ }
+ }
+
+ /**
+ * Executes the given callable as soon as possible. Whatever happens, the given callback is run before
+ * returning (in a "finally" clause) and then the "notComputing" condition will be signalled and the lock
+ * released.
+ * <p>
+ * <b>Pre-conditions:</b>
+ * <ul>
+ * <li>{@link #initialize()} has been called</li>
+ * <li>{@link #dispose()} has not been called</li>
+ * </ul>
+ * </p>
+ *
+ * @param callable
+ * will be executed as soon as this instance is no longer computing anything. Must not be
+ * {@code null}.
+ * @param postTreatment
+ * will be called in a finally clause, whatever the outcome of the computation. Can be
+ * {@code null}.
+ * @param <U>
+ * the type of the return value.
+ * @return The result returned by the given callable execution.
+ * @throws InterruptedException
+ * If the computation is interrupted
+ */
+ public synchronized <U> U call(Callable<U> callable, Runnable postTreatment) throws InterruptedException {
+ checkNotNull(callable);
+ try {
+ setUpComputation();
+ return callable.call();
+ } catch (Exception e) {
+ if (e instanceof InterruptedException) {
+ throw (InterruptedException)e;
+ }
+ throw new RuntimeException(e);
+ } finally {
+ tearDownComputation();
+ if (postTreatment != null) {
+ postTreatment.run();
+ }
+ }
+ }
+
+ /**
+ * Schedules all the given computations, which will only be run if no computation for the same key is in
+ * the {@link #computedKeys} variable. It is up to the caller to make sure that the semantics of
+ * computations previously run is the same as thos they are submitting, otherwise computations completely
+ * unrelated to what is being submitted may have marked a key as already computed. Returns after all the
+ * currently running plus submitted computations have finished.
+ *
+ * @param computations
+ * An iterable over the computations to schedule. {@code null} entries are silently ignored.
+ */
+ public void computeAll(Iterable<? extends IComputation<T>> computations) {
+ checkNotNull(computations);
+ lock.lock();
+ for (IComputation<T> comp : computations) {
+ if (comp != null) {
+ scheduleComputation(comp);
+ }
+ }
+ waitForEndOfTasks();
+ }
+
+ /**
+ * Schedules a given computation to be performed as soon as possible, if its key is not present in the
+ * {@link #computedKeys} or in the {@link #currentlyComputing} keys, in which case the computation is
+ * ignored. It is up to the caller to make sure that they submit homogeneous computations, in order for
+ * the filtering of computations by key to be meaningful.
+ * <p>
+ * <b>WARNING!</b> In a multi-threaded execution, this method may return before the computation is run. It
+ * is up to callers to make sure they only invoke that inside of a more general call to
+ * {@link #call(Callable, Runnable)}, {@link #computeAll(Iterable)}, or {@link #runAll(Iterable)}
+ * </p>
+ *
+ * @param computation
+ * The computation to run. Cannot be {@code null}.
+ * @return {@code true} if and only if the given key is not already among either the computed elements or
+ * the currently computing elements.
+ */
+ public boolean scheduleComputation(final IComputation<T> computation) {
+ checkNotNull(computation);
+ lock.lock();
+ try {
+ if (computedKeys.add(computation.getKey()) && currentlyComputing.add(computation.getKey())) {
+ ListenableFuture<?> future = computingPool.submit(new Runnable() {
+ public void run() {
+ computation.run();
+ }
+ });
+ // even if post-treatment is null, we need this callback for proper finalization
+ Futures.addCallback(future, new ComputingFutureCallback<T>(this, computation.getKey(),
+ computation.getPostTreatment()));
+ return true;
+ }
+ return false;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Executes all the runnables in the given iterable, and returns when all computations possibly running or
+ * launched by the given runnables are finished. This must be used when some treatment will possibly
+ * schedule computations but the exact list of computations cannot be computed beforehand.
+ *
+ * @param runnables
+ * An iterable over the runnables to execute, must not be {@code null}. {@code null} entries
+ * are silently ignored.
+ */
+ public void runAll(Iterable<? extends Runnable> runnables) {
+ checkNotNull(runnables);
+ lock.lock();
+ for (Runnable runnable : runnables) {
+ if (runnable != null) {
+ runnable.run();
+ }
+ }
+ waitForEndOfTasks();
+ }
+
+ /**
+ * Schedule a job that is suppoed to unload resource(s) that are no longer needed. This implementation
+ * uses a dedicated thread pool to perform these unloads.
+ *
+ * @param runnable
+ * Runnable to run, must not be {@code null}
+ * @param callback
+ * Callback to call upon completion, can be {@code null}
+ */
+ public void scheduleUnload(Runnable runnable, FutureCallback<Object> callback) {
+ ListenableFuture<?> future = unloadingPool.submit(runnable);
+ if (callback != null) {
+ Futures.addCallback(future, callback);
+ }
+ }
+
+ /**
+ * Provides the set of keys of all the computations that have been run or are still running since its set
+ * of keys {@link #computedKeys} was last set.
+ *
+ * @return The set of keys of all the computations that have been run or are still running since its set
+ * of keys {@link #computedKeys} was last set.
+ */
+ public ImmutableSet<T> getComputedElements() {
+ if (computedKeys == null) {
+ return ImmutableSet.of();
+ }
+ return ImmutableSet.copyOf(computedKeys);
+ }
+
+ /**
+ * Clears the set of computed keys.
+ */
+ public void clearComputedElements() {
+ computedKeys.clear();
+ }
+
+ /**
+ * Sets the computed keys with all the values in the given iterable.
+ *
+ * @param elements
+ * An iterable over the elements to set as computed, must not be {@code null} but can be empty.
+ */
+ public void setComputedElements(Iterable<T> elements) {
+ computedKeys = Sets.newLinkedHashSet(elements);
+ }
+
+ /**
+ * Shuts down an {@link ExecutorService} in two phases, first by calling
+ * {@link ExecutorService#shutdown() shutdown} to reject incoming tasks, and then calling
+ * {@link ExecutorService#shutdownNow() shutdownNow}, if necessary, to cancel any lingering tasks. Returns
+ * true if the pool has been properly shutdown, false otherwise.
+ * <p>
+ * Copy/pasted from {@link ExecutorService} javadoc.
+ *
+ * @param pool
+ * the pool to shutdown, must not be {@code null}
+ * @return true if the pool has been properly shutdown, false otherwise.
+ */
+ private boolean shutdownAndAwaitTermination(ExecutorService pool) {
+ boolean ret = true;
+ pool.shutdown(); // Disable new tasks from being submitted
+ try {
+ // Wait a while for existing tasks to terminate
+ if (!pool.awaitTermination(shutdownWaitDuration, shutdownWaitUnit)) {
+ pool.shutdownNow(); // Cancel currently executing tasks
+ // Wait a while for tasks to respond to being canceled
+ if (!pool.awaitTermination(shutdownWaitDuration, shutdownWaitUnit)) {
+ ret = false;
+ }
+ }
+ } catch (InterruptedException ie) {
+ // (Re-)Cancel if current thread also interrupted
+ pool.shutdownNow();
+ // Preserve interrupt status
+ Thread.currentThread().interrupt();
+ ret = false;
+ }
+ return ret;
+ }
+
+ /**
+ * Wait until all tasks have finished executing, or the current thread is interrupted, in which case an
+ * OperationCanceledException is thrown.
+ */
+ private void waitForEndOfTasks() {
+ try {
+ // TODO Is this test really necessary?
+ // TODO Is this test dangerous (infinite wait)
+ while (!currentlyComputing.isEmpty()) {
+ endOfTasks.await();
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ // FIXME?
+ throw new OperationCanceledException();
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * This will remove the given uri from the {@link #currentlyComputing} set and signal to
+ * {@link #endOfTasks} if the set is empty afterward. This method must be call by every callback of
+ * resolving tasks.
+ *
+ * @param key
+ * the key to remove.
+ */
+ private void finalizeTask(T key) {
+ lock.lock();
+ try {
+ currentlyComputing.remove(key);
+ if (currentlyComputing.isEmpty()) {
+ endOfTasks.signal();
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ // TODO Change javadoc
+ /**
+ * The callback for tasks. It will report progress, log errors and finalize the resolving and as such,
+ * possibly signaling the end of the resolution.
+ *
+ * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a>
+ */
+ private static final class ComputingFutureCallback<T> implements FutureCallback<Object> {
+ /** The scheduler. */
+ private final ResourceComputationScheduler<T> scheduler;
+
+ /** The key. */
+ private final T key;
+
+ /** The wrapped callback. */
+ private final FutureCallback<Object> wrappedCallback;
+
+ /**
+ * Constructor.
+ *
+ * @param scheduler
+ * The scheduler
+ * @param key
+ * The key
+ * @param callback
+ * The callback, can be {@code null}
+ */
+ private ComputingFutureCallback(ResourceComputationScheduler<T> scheduler, T key,
+ FutureCallback<Object> callback) {
+ this.scheduler = checkNotNull(scheduler);
+ this.key = checkNotNull(key);
+ this.wrappedCallback = callback;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void onSuccess(Object result) {
+ try {
+ if (wrappedCallback != null) {
+ wrappedCallback.onSuccess(result);
+ }
+ } finally {
+ scheduler.finalizeTask(key);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void onFailure(Throwable t) {
+ try {
+ if (wrappedCallback != null) {
+ wrappedCallback.onFailure(t);
+ }
+ } finally {
+ scheduler.finalizeTask(key);
+ }
+ }
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResourceDependencyFoundEvent.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResourceDependencyFoundEvent.java
new file mode 100644
index 000000000..886b2f24b
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResourceDependencyFoundEvent.java
@@ -0,0 +1,78 @@
+/*******************************************************************************
+ * Copyright (c) 2015 EclipseSource Munich 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:
+ * Philip Langer - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.logical.resolver;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.Optional;
+
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EReference;
+import org.eclipse.emf.ecore.EStructuralFeature;
+import org.eclipse.emf.ecore.util.EcoreUtil;
+
+/**
+ * An event indicating that a resource dependency is found.
+ *
+ * @author Philip Langer <planger@eclipsesource.com>
+ */
+public class ResourceDependencyFoundEvent extends DependencyFoundEvent<URI> {
+
+ /**
+ * Constructor.
+ *
+ * @param from
+ * The source of the dependency
+ * @param to
+ * The target of the dependency
+ * @param parent
+ * The object in the source causing the dependency
+ * @param feature
+ * The feature through which the parent causes the dependency
+ */
+ public ResourceDependencyFoundEvent(URI from, URI to, EObject parent, EStructuralFeature feature) {
+ super(from, to, getParentUriIfContainmentReference(checkNotNull(parent), feature));
+ }
+
+ /**
+ * Returns the URI of the {@code parent} object causing this dependency, if it is the actual owner of the
+ * dependency.
+ * <p>
+ * The {@code parent} object is the actual owner if the {@code feature} through which the {@code parent}
+ * object refers to the dependency is a {@link EReference#isContainment() containment reference}.
+ * </p>
+ *
+ * @param parent
+ * The object in the source causing the dependency
+ * @param feature
+ * The feature through which the parent causes the dependency
+ * @return the URI of the parent if it is the owner of the dependency, otherwise {@link Optional#absent()}
+ */
+ private static Optional<URI> getParentUriIfContainmentReference(EObject parent, EStructuralFeature feature) {
+ if (feature instanceof EReference && ((EReference)feature).isContainment()) {
+ return Optional.of(getUri(parent));
+ }
+ return Optional.absent();
+ }
+
+ /**
+ * Returns the URI of the given {@code eObject}.
+ *
+ * @param eObject
+ * The object to get the URI of
+ * @return The URI of the given {@code eObject}
+ */
+ public static URI getUri(EObject eObject) {
+ return EcoreUtil.getURI(eObject);
+ }
+
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResourceDependencyLocalResolver.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResourceDependencyLocalResolver.java
new file mode 100644
index 000000000..c39f9f13e
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResourceDependencyLocalResolver.java
@@ -0,0 +1,296 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Obeo 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:
+ * Obeo - initial API and implementation
+ * Philip Langer - extract interface
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.logical.resolver;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+import com.google.common.eventbus.EventBus;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.emf.common.util.BasicDiagnostic;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.compare.ide.ui.internal.util.ThreadSafeProgressMonitor;
+import org.eclipse.emf.compare.ide.utils.ResourceUtil;
+import org.eclipse.emf.compare.internal.utils.Graph;
+
+/**
+ * The default implementation of the {@link IResourceDependencyProvider}.
+ *
+ * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
+ */
+public class ResourceDependencyLocalResolver implements IResourceDependencyLocalResolver {
+ /** The scheduler. */
+ private final ResourceComputationScheduler<URI> scheduler;
+
+ /** The event bus */
+ private final EventBus eventBus;
+
+ /** The dependency graph. */
+ private final Graph<URI> dependencyGraph;
+
+ /** The resource listener. */
+ private final ModelResourceListener resourceListener;
+
+ /** The implicit dependencies */
+ private final IImplicitDependencies implicitDependencies;
+
+ /**
+ * Constructor.
+ *
+ * @param context
+ * The resolution context, must not be {@code null}
+ */
+ public ResourceDependencyLocalResolver(IResolutionContext context) {
+ this.implicitDependencies = context.getImplicitDependencies();
+ this.scheduler = context.getScheduler();
+ this.eventBus = context.getEventBus();
+ this.dependencyGraph = context.getGraph();
+ this.resourceListener = context.getModelResourceListener();
+ }
+
+ public Iterable<URI> getDependenciesOf(IFile file) {
+ return getDependenciesOf(file, Collections.<URI> emptySet());
+ }
+
+ public Iterable<URI> getDependenciesOf(IFile file, Set<URI> bounds) {
+ final URI expectedURI = ResourceUtil.createURIFor(file);
+
+ final Iterable<URI> dependencies;
+ switch (ResolutionUtil.getResolutionScope()) {
+ case WORKSPACE:
+ dependencies = dependencyGraph.getSubgraphContaining(expectedURI, bounds);
+ break;
+ case PROJECT:
+ final Set<URI> allDependencies = dependencyGraph.getSubgraphContaining(expectedURI, bounds);
+ final IResource project = file.getProject();
+ dependencies = Iterables.filter(allDependencies, isInContainer(project));
+ break;
+ case CONTAINER:
+ final Set<URI> allDependencies1 = dependencyGraph.getSubgraphContaining(expectedURI, bounds);
+ final IResource container = file.getParent();
+ dependencies = Iterables.filter(allDependencies1, isInContainer(container));
+ break;
+ case OUTGOING:
+ dependencies = dependencyGraph.getTreeFrom(expectedURI, bounds);
+ break;
+ case SELF:
+ // fall through
+ default:
+ dependencies = Collections.singleton(expectedURI);
+ break;
+ }
+ return dependencies;
+ }
+
+ /**
+ * Checks the current state of our {@link #resourceListener} and updates the dependency graph for all
+ * resources that have been changed since we last checked.
+ *
+ * @param resourceSet
+ * The resource set in which to load our temporary resources.
+ * @param diagnostic
+ * The diagnostic.
+ * @param tspm
+ * Monitor on which to report progress to the user.
+ */
+ protected void updateChangedResources(SynchronizedResourceSet resourceSet, DiagnosticSupport diagnostic,
+ ThreadSafeProgressMonitor tspm) {
+ // this.diagnostic = createDiagnostic();
+ final Set<URI> removedURIs = Sets.difference(resourceListener.popRemovedURIs(), scheduler
+ .getComputedElements());
+ final Set<URI> changedURIs = Sets.difference(resourceListener.popChangedURIs(), scheduler
+ .getComputedElements());
+
+ eventBus.post(new ResourceRemovedEvent<URI>(removedURIs));
+
+ // We need to re-resolve the changed resources, along with their direct parents
+ final Set<URI> recompute = new LinkedHashSet<URI>(changedURIs);
+ final Multimap<URI, URI> parentToGrandParents = ArrayListMultimap.create();
+ for (URI changed : changedURIs) {
+ if (dependencyGraph.contains(changed)) {
+ Set<URI> directParents = dependencyGraph.getDirectParents(changed);
+ recompute.addAll(directParents);
+ for (URI uri : directParents) {
+ Set<URI> grandParents = dependencyGraph.getDirectParents(uri);
+ parentToGrandParents.putAll(uri, grandParents);
+ }
+ }
+ }
+
+ eventBus.post(new ResourceRemovedEvent<URI>(recompute));
+
+ demandResolveAll(recompute, diagnostic, resourceSet, tspm);
+
+ // Re-connect changed resources parents' with their parents
+ demandResolveAll(Iterables.filter(parentToGrandParents.keySet(), new Predicate<URI>() {
+ public boolean apply(URI uri) {
+ return dependencyGraph.contains(uri);
+ }
+ }), diagnostic, resourceSet, tspm);
+ }
+
+ public void demandResolve(final SynchronizedResourceSet resourceSet, final URI uri,
+ final DiagnosticSupport diagnostic, final ThreadSafeProgressMonitor tspm) {
+ if (ResolutionUtil.isInterruptedOrCanceled(tspm)) {
+ scheduler.demandShutdown();
+ return;
+ }
+ for (URI currentUri : implicitDependencies.of(uri, resourceSet.getURIConverter())) {
+ scheduler.scheduleComputation(new LocalResolveComputation(scheduler, eventBus, diagnostic,
+ resourceSet, currentUri, new MonitorCallback(diagnostic, tspm), tspm));
+ }
+ }
+
+ /**
+ * Allows callers to launch the loading and resolution of the model pointed at by the given URI.
+ * <p>
+ * This will check whether the given storage isn't already being resolved, then submit a job to the
+ * {@link #resolvingPool} to load and resolve the model in a separate thread.
+ * </p>
+ *
+ * @param resourceSet
+ * The resource set in which to load the resource.
+ * @param uris
+ * The uris we are to try and load as models.
+ * @param diagnostic
+ * The diagnostic
+ * @param tspm
+ * Monitor on which to report progress to the user.
+ * @see LocalResolveComputation
+ */
+ private void demandResolveAll(Iterable<URI> uris, final DiagnosticSupport diagnostic,
+ final SynchronizedResourceSet resourceSet, final ThreadSafeProgressMonitor tspm) {
+ scheduler.computeAll(Iterables.transform(uris, new Function<URI, IComputation<URI>>() {
+ public IComputation<URI> apply(final URI uri) {
+ // In this case, we don't want to call the implicit dependencies extension point
+ return new LocalResolveComputation(scheduler, eventBus, diagnostic, resourceSet, uri,
+ new MonitorCallback(diagnostic, tspm), tspm);
+ }
+ }));
+ }
+
+ /**
+ * This predicate can be used to check wether a given URI points to a workspace resource contained in the
+ * given container.
+ *
+ * @param container
+ * The container in which we need the resources to be contained.
+ * @return A ready to use predicate.
+ */
+ protected Predicate<URI> isInContainer(final IResource container) {
+ return new Predicate<URI>() {
+ public boolean apply(URI input) {
+ if (input != null) {
+ final IFile pointedFile = ResolutionUtil.getFileAt(input);
+ if (pointedFile != null) {
+ return container.getLocation().isPrefixOf(pointedFile.getLocation());
+ }
+ }
+ return false;
+ }
+ };
+ }
+
+ /**
+ * Update the dependency graph to make sure that it contains the given file.
+ * <p>
+ * If the graph does not yet contain this file, we'll try and find cross-references outgoing from and/or
+ * incoming to the given file, depending on the current {@link #getResolutionScope() resolution scope}.
+ * </p>
+ *
+ * @param monitor
+ * The progress monitor.
+ * @param diagnostic
+ * The diagnostic
+ * @param files
+ * The files which we need to be present in the dependency graph.
+ * @throws InterruptedException
+ * if the computation of dependencies is interrupted.
+ */
+ public void updateDependencies(IProgressMonitor monitor, final DiagnosticSupport diagnostic,
+ IFile... files) throws InterruptedException {
+ final ThreadSafeProgressMonitor tspm = new ThreadSafeProgressMonitor(monitor);
+ final SynchronizedResourceSet resourceSet = new SynchronizedResourceSet(
+ new LocalMonitoredProxyCreationListener(tspm, eventBus, this, diagnostic));
+ Iterable<IFile> filesToResolve = Iterables.filter(Arrays.asList(files), new Predicate<IFile>() {
+ public boolean apply(IFile file) {
+ return !dependencyGraph.contains(ResourceUtil.asURI().apply(file));
+ }
+ });
+ scheduler.runAll(Iterables.transform(filesToResolve, new Function<IFile, Runnable>() {
+ public Runnable apply(final IFile file) {
+ return new Runnable() {
+ public void run() {
+ final IResource startingPoint = getResolutionStartingPoint(file);
+ final ModelResourceVisitor modelVisitor = new ModelResourceVisitor(scheduler,
+ resourceSet, ResourceDependencyLocalResolver.this, diagnostic, tspm);
+ try {
+ startingPoint.accept(modelVisitor);
+ } catch (CoreException e) {
+ diagnostic.merge(BasicDiagnostic.toDiagnostic(e));
+ }
+ }
+ };
+ }
+ }));
+ updateChangedResources(resourceSet, diagnostic, tspm);
+ }
+
+ /**
+ * Returns the starting point for the resolution of the given file's logical model according to
+ * {@link #getResolutionScope()}.
+ *
+ * @param file
+ * The file which logical model we need to add to the current {@link #dependencyGraph}.
+ * @return Starting point for this file's logical model resolution.
+ * @see CrossReferenceResolutionScope
+ */
+ protected IResource getResolutionStartingPoint(IFile file) {
+ final IResource startingPoint;
+ switch (ResolutionUtil.getResolutionScope()) {
+ case WORKSPACE:
+ startingPoint = ResourcesPlugin.getWorkspace().getRoot();
+ break;
+ case PROJECT:
+ startingPoint = file.getProject();
+ break;
+ case CONTAINER:
+ startingPoint = file.getParent();
+ break;
+ case OUTGOING:
+ // fall through, the difference between SELF and OUTGOING will only come later on
+ case SELF:
+ // fall through
+ default:
+ startingPoint = file;
+ break;
+ }
+ return startingPoint;
+ }
+
+ public boolean hasChild(URI parent, URI candidate) {
+ return dependencyGraph.hasChild(parent, candidate);
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResourceDependencyProvider.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResourceDependencyProvider.java
new file mode 100644
index 000000000..0cdec6b4f
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResourceDependencyProvider.java
@@ -0,0 +1,108 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Obeo 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:
+ * Obeo - initial API and implementation
+ * Philip Langer - extract interface
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.logical.resolver;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+
+import java.util.Collections;
+import java.util.Set;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.compare.ide.utils.ResourceUtil;
+import org.eclipse.emf.compare.internal.utils.Graph;
+
+/**
+ * The default implementation of the {@link IResourceDependencyProvider}.
+ *
+ * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
+ */
+public class ResourceDependencyProvider implements IResourceDependencyProvider {
+
+ /** The dependency graph. */
+ private final Graph<URI> dependencyGraph;
+
+ /**
+ * Constructor.
+ *
+ * @param context
+ * The resolution context, must not be null.
+ */
+ public ResourceDependencyProvider(IResolutionContext context) {
+ checkNotNull(context);
+ this.dependencyGraph = checkNotNull(context.getGraph());
+ }
+
+ public Iterable<URI> getDependenciesOf(IFile file) {
+ return getDependenciesOf(file, Collections.<URI> emptySet());
+ }
+
+ public boolean hasChild(URI parent, URI candidate) {
+ return dependencyGraph.hasChild(parent, candidate);
+ }
+
+ public Iterable<URI> getDependenciesOf(IFile file, Set<URI> bounds) {
+ final URI expectedURI = ResourceUtil.createURIFor(file);
+
+ final Iterable<URI> dependencies;
+ switch (ResolutionUtil.getResolutionScope()) {
+ case WORKSPACE:
+ dependencies = dependencyGraph.getSubgraphContaining(expectedURI, bounds);
+ break;
+ case PROJECT:
+ final Set<URI> allDependencies = dependencyGraph.getSubgraphContaining(expectedURI, bounds);
+ final IResource project = file.getProject();
+ dependencies = Iterables.filter(allDependencies, isInContainer(project));
+ break;
+ case CONTAINER:
+ final Set<URI> allDependencies1 = dependencyGraph.getSubgraphContaining(expectedURI, bounds);
+ final IResource container = file.getParent();
+ dependencies = Iterables.filter(allDependencies1, isInContainer(container));
+ break;
+ case OUTGOING:
+ dependencies = dependencyGraph.getTreeFrom(expectedURI, bounds);
+ break;
+ case SELF:
+ // fall through
+ default:
+ dependencies = Collections.singleton(expectedURI);
+ break;
+ }
+ return dependencies;
+ }
+
+ /**
+ * This predicate can be used to check wether a given URI points to a workspace resource contained in the
+ * given container.
+ *
+ * @param container
+ * The container in which we need the resources to be contained.
+ * @return A ready to use predicate.
+ */
+ protected Predicate<URI> isInContainer(final IResource container) {
+ return new Predicate<URI>() {
+ public boolean apply(URI input) {
+ if (input != null) {
+ final IFile pointedFile = ResolutionUtil.getFileAt(input);
+ if (pointedFile != null) {
+ return container.getLocation().isPrefixOf(pointedFile.getLocation());
+ }
+ }
+ return false;
+ }
+ };
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResourceDependencyRemoteResolver.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResourceDependencyRemoteResolver.java
new file mode 100644
index 000000000..8ae455993
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResourceDependencyRemoteResolver.java
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Obeo 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:
+ * Obeo - initial API and implementation
+ * Philip Langer - extract interface
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.logical.resolver;
+
+import com.google.common.util.concurrent.FutureCallback;
+
+import org.eclipse.emf.common.util.BasicDiagnostic;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.compare.ide.ui.internal.util.ThreadSafeProgressMonitor;
+
+/**
+ * The default implementation of the {@link IResourceDependencyRemoteResolver}.
+ *
+ * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
+ */
+public class ResourceDependencyRemoteResolver implements IResourceDependencyRemoteResolver {
+ /** The scheduler. */
+ protected final ResourceComputationScheduler<URI> scheduler;
+
+ /** The implicit dependencies */
+ private final IImplicitDependencies implicitDependencies;
+
+ /**
+ * Constructor.
+ *
+ * @param context
+ * The context, must not be {@code null}
+ */
+ public ResourceDependencyRemoteResolver(IResolutionContext context) {
+ this.implicitDependencies = context.getImplicitDependencies();
+ this.scheduler = context.getScheduler();
+ }
+
+ public void demandRemoteResolve(final SynchronizedResourceSet resourceSet, final URI uri,
+ final DiagnosticSupport diagnostic, final ThreadSafeProgressMonitor tspm) {
+ if (ResolutionUtil.isInterruptedOrCanceled(tspm)) {
+ scheduler.demandShutdown();
+ return;
+ }
+ for (URI currentUri : implicitDependencies.of(uri, resourceSet.getURIConverter())) {
+ scheduler.scheduleComputation(getRemoteResolveComputation(resourceSet, currentUri, diagnostic,
+ tspm));
+ }
+ }
+
+ public RemoteResolveComputation getRemoteResolveComputation(final SynchronizedResourceSet resourceSet,
+ final URI uri, final DiagnosticSupport diagnostic, final ThreadSafeProgressMonitor tspm) {
+ return new RemoteResolveComputation(scheduler, diagnostic, resourceSet, uri,
+ new FutureCallback<Object>() {
+ public void onSuccess(Object o) {
+ if (!ResolutionUtil.isInterruptedOrCanceled(tspm)) {
+ // do not report progress anymore when the task has been interrupted of canceled.
+ // It speeds up the cancellation.
+ tspm.worked(1);
+ }
+ }
+
+ public void onFailure(Throwable t) {
+ if (!ResolutionUtil.isInterruptedOrCanceled(tspm)) {
+ // do not report progress or errors anymore when the task has been interrupted of
+ // canceled. It speeds up the cancellation.
+ tspm.worked(1);
+ diagnostic.merge(BasicDiagnostic.toDiagnostic(t));
+ }
+ }
+ }, tspm);
+ }
+
+ public SynchronizedResourceSet getResourceSetForRemoteResolution(DiagnosticSupport diagnostic,
+ ThreadSafeProgressMonitor tspm) {
+ final SynchronizedResourceSet resourceSet = new SynchronizedResourceSet(
+ new RemoteMonitoredProxyCreationListener(tspm, this, diagnostic));
+ return resourceSet;
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResourceRemovedEvent.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResourceRemovedEvent.java
new file mode 100644
index 000000000..e117602fe
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResourceRemovedEvent.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Obeo.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.logical.resolver;
+
+import java.util.Set;
+
+/**
+ * Event indicating that model resources have been removed, which requires updating the graph of dependencies
+ * between model resources.
+ *
+ * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
+ */
+public class ResourceRemovedEvent<T> {
+
+ private final Set<T> elements;
+
+ public ResourceRemovedEvent(Set<T> elements) {
+ this.elements = elements;
+ }
+
+ public Set<T> getElements() {
+ return elements;
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResourceUnloader.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResourceUnloader.java
new file mode 100644
index 000000000..8a1e213d1
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ResourceUnloader.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Obeo.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.logical.resolver;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.emf.ecore.resource.Resource;
+
+/**
+ * Implementation of a Runnable that can be used to unload a given resource and make it garbage-collectable.
+ *
+ * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
+ */
+/* default */class ResourceUnloader implements Runnable {
+ /** The resource set from which to unload a resource. */
+ private final SynchronizedResourceSet resourceSet;
+
+ /** The resource to unload. */
+ private final Resource resource;
+
+ /** Monitor on which to report progress to the user. */
+ private final IProgressMonitor monitor;
+
+ /**
+ * Default constructor.
+ *
+ * @param resourceSet
+ * The resource set from which to unload a resource.
+ * @param resource
+ * The resource to unload.
+ * @param monitor
+ * Monitor on which to report progress to the user.
+ */
+ public ResourceUnloader(SynchronizedResourceSet resourceSet, Resource resource, IProgressMonitor monitor) {
+ this.resourceSet = resourceSet;
+ this.resource = resource;
+ this.monitor = monitor;
+ }
+
+ /** {@inheritDoc} */
+ public void run() {
+ resourceSet.unload(resource, monitor);
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/RevisionedURIConverter.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/RevisionedURIConverter.java
index 98045ba05..99ce3220e 100644
--- a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/RevisionedURIConverter.java
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/RevisionedURIConverter.java
@@ -86,13 +86,13 @@ public final class RevisionedURIConverter extends StorageURIConverter {
* see {@link #createInputStream(URI, Map)}
* @return <code>true</code> if there is an input stream accessible for the given uri, <code>false</code>
* otherwise.
+ * @throws IOException
+ * if an IO problem occurs.
* @see #createInputStream(URI, Map)
* @see #prefetchedStreams
*/
- /*
- * Suppressing the warning. It is the responsibility of the caller to then use #createInputStream(...)
- * somehow and close the stream then.
- */
+ // Suppressing the warning. It is the responsibility of the caller to then use #createInputStream(...)
+ // somehow and close the stream then.
@SuppressWarnings("resource")
public boolean prefetchStream(URI uri, Map<?, ?> options) throws IOException {
InputStream stream = createInputStream(uri, options);
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/SynchronizedResourceSet.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/SynchronizedResourceSet.java
index 9fdb5f2e1..2a1db6d07 100644
--- a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/SynchronizedResourceSet.java
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/SynchronizedResourceSet.java
@@ -42,6 +42,12 @@ class SynchronizedResourceSet extends ResourceSetImpl {
/** Associates URIs with their resources. */
private final ConcurrentHashMap<URI, Resource> uriCache;
+ /**
+ * Constructor.
+ *
+ * @param proxyListener
+ * The listener to notify of proxy creations.
+ */
public SynchronizedResourceSet(IProxyCreationListener proxyListener) {
this.uriCache = new ConcurrentHashMap<URI, Resource>();
this.resources = new SynchronizedResourcesEList<Resource>();
@@ -125,8 +131,7 @@ class SynchronizedResourceSet extends ResourceSetImpl {
* java.io.IOException)
*/
@Override
- protected void handleDemandLoadException(Resource resource, IOException exception)
- throws RuntimeException {
+ protected void handleDemandLoadException(Resource resource, IOException exception) {
try {
super.handleDemandLoadException(resource, exception);
} catch (RuntimeException e) {
@@ -135,7 +140,15 @@ class SynchronizedResourceSet extends ResourceSetImpl {
}
}
- public void unload(Resource resource, @SuppressWarnings("unused") IProgressMonitor monitor) {
+ /**
+ * Unload the given resource.
+ *
+ * @param resource
+ * Resource to unlod
+ * @param monitor
+ * Progress monito to use (currently unused)
+ */
+ public void unload(Resource resource, IProgressMonitor monitor) {
final URI uri = resource.getURI();
uriCache.remove(uri);
getResources().remove(resource);
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ThreadedModelResolver.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ThreadedModelResolver.java
index 6d9867222..330e5aa31 100644
--- a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ThreadedModelResolver.java
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/resolver/ThreadedModelResolver.java
@@ -8,85 +8,22 @@
* Contributors:
* Obeo - initial API and implementation
* Alexandra Buzila - Fixes for Bug 462938
- * Stefan Dirix - Bug 456699
*******************************************************************************/
package org.eclipse.emf.compare.ide.ui.internal.logical.resolver;
-import static com.google.common.collect.Sets.intersection;
-import static org.eclipse.emf.compare.ide.ui.internal.util.PlatformElementUtil.adaptAs;
-import static org.eclipse.emf.compare.ide.utils.ResourceUtil.createURIFor;
-import static org.eclipse.emf.compare.ide.utils.ResourceUtil.hasModelType;
+import com.google.common.eventbus.EventBus;
-import com.google.common.base.Function;
-import com.google.common.base.Predicate;
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.Sets;
-import com.google.common.util.concurrent.FutureCallback;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.common.util.concurrent.MoreExecutors;
-import com.google.common.util.concurrent.ThreadFactoryBuilder;
-
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.ReentrantLock;
-
-import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
-import org.eclipse.core.resources.IResourceChangeEvent;
-import org.eclipse.core.resources.IResourceChangeListener;
-import org.eclipse.core.resources.IResourceDelta;
-import org.eclipse.core.resources.IResourceDeltaVisitor;
-import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.resources.IStorage;
-import org.eclipse.core.resources.ResourcesPlugin;
-import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.OperationCanceledException;
-import org.eclipse.core.runtime.Path;
-import org.eclipse.core.runtime.Status;
-import org.eclipse.core.runtime.SubMonitor;
-import org.eclipse.emf.common.util.BasicDiagnostic;
-import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.URI;
-import org.eclipse.emf.compare.ide.internal.utils.ProxyNotifierParserPool.IProxyCreationListener;
-import org.eclipse.emf.compare.ide.ui.dependency.ModelDependencyProviderRegistry;
-import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIMessages;
-import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIPlugin;
-import org.eclipse.emf.compare.ide.ui.internal.preferences.EMFCompareUIPreferences;
-import org.eclipse.emf.compare.ide.ui.internal.util.ThreadSafeProgressMonitor;
import org.eclipse.emf.compare.ide.ui.logical.AbstractModelResolver;
import org.eclipse.emf.compare.ide.ui.logical.IModelResolver;
import org.eclipse.emf.compare.ide.ui.logical.IStorageProviderAccessor;
-import org.eclipse.emf.compare.ide.ui.logical.IStorageProviderAccessor.DiffSide;
import org.eclipse.emf.compare.ide.ui.logical.SynchronizationModel;
-import org.eclipse.emf.compare.ide.utils.ResourceUtil;
import org.eclipse.emf.compare.ide.utils.StorageTraversal;
-import org.eclipse.emf.compare.ide.utils.StorageURIConverter;
import org.eclipse.emf.compare.internal.utils.Graph;
import org.eclipse.emf.compare.internal.utils.ReadOnlyGraph;
-import org.eclipse.emf.ecore.EObject;
-import org.eclipse.emf.ecore.EReference;
-import org.eclipse.emf.ecore.EStructuralFeature;
-import org.eclipse.emf.ecore.InternalEObject;
-import org.eclipse.emf.ecore.resource.Resource;
-import org.eclipse.emf.ecore.resource.URIConverter;
-import org.eclipse.emf.ecore.util.EcoreUtil;
-import org.eclipse.jface.preference.IPreferenceStore;
/**
* This implementation of an {@link IModelResolver} will look up all of the models located in a set container
@@ -116,120 +53,7 @@ import org.eclipse.jface.preference.IPreferenceStore;
*/
public class ThreadedModelResolver extends AbstractModelResolver {
- /** This can be used in order to convert an Iterable of IStorages to an Iterable over the storage's URIs. */
- private static final Function<IStorage, URI> AS_URI = new Function<IStorage, URI>() {
- public URI apply(IStorage input) {
- if (input != null) {
- return createURIFor(input);
- }
- return null;
- }
- };
-
- /**
- * Keeps track of the discovered dependency graph for local resources.
- * <p>
- * Model resolvers are created from the extension point registry, so we know there will be a single
- * instance of our resolver for a single run of Eclipse (even across multiple comparisons). We also assume
- * that this graph won't turn to be a memory hog since we're only keeping track of URIs, and at the worst
- * no more URIs than there are resources in the user's workspace. We can thus keep this graph around to
- * avoid multiple crawlings of the same models. Team, as well as the EMFResourceMapping, tend to be
- * over-enthusiast with the resolution of model traversals. For example, a single
- * "right-click -> compare with -> commit..." with EGit ends up calling 8 distinct times for the resource
- * traversal of the selected resource.
- * </p>
- */
- private final Graph<URI> dependencyGraph;
-
- /**
- * We'll keep track of what's already been resolved to avoid duplicate jobs.
- */
- private Set<URI> resolvedResources;
-
- /**
- * Will keep track of any error or warning that can arise during the loading of the resources.
- */
- private BasicDiagnostic diagnostic;
-
- /**
- * Keeps track of the URIs which we are currently resolving (or which are queued for resolution).
- * <p>
- * This along with {@link #resolvedResources} will prevent multiple "duplicate" resolution threads to be
- * queued. We assume that this will be sufficient to prevent duplicates, and the resolution threads
- * themselves won't check whether their target has already been resolved before starting.
- * </p>
- */
- private final Set<URI> currentlyResolving;
-
- /** Thread pool for our resolving threads. */
- private ListeningExecutorService resolvingPool;
-
- /** Thread pool for our unloading threads. */
- private ListeningExecutorService unloadingPool;
-
- /**
- * An executor service will be used to shut down the {@link #unloadingPool} and the {@link #resolvingPool}
- * .
- */
- private ListeningExecutorService terminator;
-
- /** Tracks if shutdown of {@link #unloadingPool} and {@link #resolvingPool} is currently in progress. */
- private final AtomicBoolean shutdownInProgress;
-
- /**
- * This will lock will prevent concurrent modifications of this resolver's fields. Most notably,
- * {@link #currentlyResolving}, {@link #resolvedResources} and {@link #diagnostic} must not be accessed
- * concurrently by two threads at once.
- */
- private final ReentrantLock lock;
-
- /**
- * This resolver will not accept two model resolutions at once.
- * <p>
- * Any thread trying to call a model resolution process through either of the three "resolve*" APIs will
- * have to wait for this condition to be true before starting.
- * </p>
- */
- private final Condition notResolving;
-
- /** Condition to await for all current {@link ResourceResolver} threads to terminate. */
- private final Condition resolutionEnd;
-
- /**
- * This resolver will keep a resource listener over the workspace in order to keep its dependencies graph
- * in sync.
- * <p>
- * We're building the dependency graph on-demand and keep it around between invocations. This listener
- * will allow us to update the graph on-demand by keeping track of the removed and changed resources since
- * we were last called.
- * </p>
- */
- private ModelResourceListener resourceListener;
-
- /**
- * The manager for the dependency extension point.
- */
- private ModelDependencyProviderRegistry dependencyProviderRegistry;
-
- /** Default constructor. */
- public ThreadedModelResolver() {
- this.dependencyGraph = new Graph<URI>();
- this.lock = new ReentrantLock(true);
- this.notResolving = lock.newCondition();
- this.resolutionEnd = lock.newCondition();
- this.currentlyResolving = new HashSet<URI>();
- this.shutdownInProgress = new AtomicBoolean(false);
- this.dependencyProviderRegistry = getModelDependencyProviderRegistry();
- }
-
- /**
- * Returns the {@link ModelDependencyProviderRegistry} to be used.
- *
- * @return the {@link ModelDependencyProviderRegistry} to be used.
- */
- protected ModelDependencyProviderRegistry getModelDependencyProviderRegistry() {
- return EMFCompareIDEUIPlugin.getDefault().getModelDependencyProviderRegistry();
- }
+ private IResolutionContext context;
/**
* Convert the dependency graph to its read-only version.
@@ -237,92 +61,44 @@ public class ThreadedModelResolver extends AbstractModelResolver {
* @return a read-only version of the dependency graph associated to this model resolver.
*/
public ReadOnlyGraph<URI> getDependencyGraph() {
- return ReadOnlyGraph.toReadOnlyGraph(dependencyGraph);
+ return ReadOnlyGraph.toReadOnlyGraph(context.getGraph());
}
/**
- * Creates the thread pools of this resolver. We cannot keep pools between resolving call because in case
- * of cancellation, we have to shutdown the pool to exit early.
- */
- private void createThreadPools() {
- final int availableProcessors = Runtime.getRuntime().availableProcessors();
- ThreadFactory resolvingThreadFactory = new ThreadFactoryBuilder().setNameFormat(
- "EMFCompare-ResolvingThread-%d") //$NON-NLS-1$
- .build();
- this.resolvingPool = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(
- availableProcessors, resolvingThreadFactory));
- ThreadFactory unloadingThreadFactory = new ThreadFactoryBuilder().setNameFormat(
- "EMFCompare-UnloadingThread-%d") //$NON-NLS-1$
- .build();
- this.unloadingPool = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(
- availableProcessors, unloadingThreadFactory));
- }
-
- /**
- * {@inheritDoc}
+ * {@inheritDoc} When initialized, the ThreadedModelResolver will:
+ * <ol>
+ * <li>install a listener on the workspace to keep track of modified resources</li>
+ * <li>Register its {@link #graphUpdater} to its {@link #eventBus}</li>
+ * <li>initialize its {@link #scheduler}</li>
+ * </ol>
*/
@Override
public void initialize() {
- this.resourceListener = new ModelResourceListener();
- ResourcesPlugin.getWorkspace().addResourceChangeListener(resourceListener);
-
- this.terminator = MoreExecutors.listeningDecorator(Executors
- .newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(
- "EMFCompare-ThreadPoolShutdowner-%d").setPriority(Thread.MAX_PRIORITY).build())); //$NON-NLS-1$
+ super.initialize();
+ EventBus eventBus = new EventBus();
+ Graph<URI> graph = new Graph<URI>();
+ this.context = createContext(eventBus, graph);
+ context.initialize();
}
/** {@inheritDoc} */
@Override
public void dispose() {
- terminator.shutdown();
- ResourcesPlugin.getWorkspace().removeResourceChangeListener(resourceListener);
+ context.dispose();
super.dispose();
}
/**
- * Shutdown {@link #resolvingPool} and {@link #unloadingPool} and set these two fields to null.
- */
- private void shutdownPools() {
- if (!shutdownAndAwaitTermination(resolvingPool) || !shutdownAndAwaitTermination(unloadingPool)) {
- EMFCompareIDEUIPlugin.getDefault().log(IStatus.WARNING,
- "Thread pools have not been properly stopped"); //$NON-NLS-1$
- }
- resolvingPool = null;
- unloadingPool = null;
- }
-
- /**
- * Shuts down an {@link ExecutorService} in two phases, first by calling
- * {@link ExecutorService#shutdown() shutdown} to reject incoming tasks, and then calling
- * {@link ExecutorService#shutdownNow() shutdownNow}, if necessary, to cancel any lingering tasks. Returns
- * true if the pool has been properly shutdown, false otherwise.
- * <p>
- * Copy/pasted from {@link ExecutorService} javadoc.
+ * For testing purposes, this method is protected.
*
- * @param pool
- * the pool to shutdown
- * @return true if the pool has been properly shutdown, false otherwise.
+ * @param eventBus
+ * @param graph
+ * @return The resolution context to use.
*/
- private static boolean shutdownAndAwaitTermination(ExecutorService pool) {
- boolean ret = true;
- pool.shutdown(); // Disable new tasks from being submitted
- try {
- // Wait a while for existing tasks to terminate
- if (!pool.awaitTermination(5, TimeUnit.SECONDS)) {
- pool.shutdownNow(); // Cancel currently executing tasks
- // Wait a while for tasks to respond to being canceled
- if (!pool.awaitTermination(5, TimeUnit.SECONDS)) {
- ret = false;
- }
- }
- } catch (InterruptedException ie) {
- // (Re-)Cancel if current thread also interrupted
- pool.shutdownNow();
- // Preserve interrupt status
- Thread.currentThread().interrupt();
- ret = false;
- }
- return ret;
+ protected DefaultResolutionContext createContext(EventBus eventBus, Graph<URI> graph) {
+ return new DefaultResolutionContext(eventBus, graph,
+ new DependencyGraphUpdater<URI>(graph, eventBus), new ResourceComputationScheduler<URI>(),
+ new ModelResourceListener());
}
/** {@inheritDoc} */
@@ -339,54 +115,10 @@ public class ThreadedModelResolver extends AbstractModelResolver {
* potential unforeseen interactions.
* </p>
*/
- public StorageTraversal resolveLocalModel(IResource start, IProgressMonitor monitor)
+ public StorageTraversal resolveLocalModel(final IResource start, final IProgressMonitor monitor)
throws InterruptedException {
- if (!(start instanceof IFile)) {
- return new StorageTraversal(new LinkedHashSet<IStorage>());
- }
- IFile file = (IFile)start;
-
- ThreadSafeProgressMonitor subMonitor = null;
- lock.lockInterruptibly();
- try {
- subMonitor = new ThreadSafeProgressMonitor(SubMonitor.convert(monitor, 100));
- while (!currentlyResolving.isEmpty()) {
- notResolving.await();
- }
-
- setupResolving();
-
- if (getResolutionScope() != CrossReferenceResolutionScope.SELF) {
- final SynchronizedResourceSet resourceSet = new SynchronizedResourceSet(
- new MonitoredProxyCreationListener(subMonitor, false));
- updateDependencies(resourceSet, file, subMonitor);
- updateChangedResources(resourceSet, subMonitor);
- }
-
- while (!currentlyResolving.isEmpty()) {
- resolutionEnd.await();
- }
-
- if (subMonitor.isCanceled()) {
- throw new OperationCanceledException();
- }
-
- final Set<IStorage> traversalSet = resolveTraversal(file, Collections.<URI> emptySet());
- StorageTraversal traversal = new StorageTraversal(traversalSet, diagnostic);
-
- return traversal;
- } finally {
- try {
- finalizeResolving();
-
- if (subMonitor != null) {
- subMonitor.setWorkRemaining(0);
- }
- } finally {
- notResolving.signal();
- lock.unlock();
- }
- }
+ LocalModelResolution comp = new LocalModelResolution(context, monitor);
+ return comp.run(start);
}
/**
@@ -400,120 +132,8 @@ public class ThreadedModelResolver extends AbstractModelResolver {
*/
public SynchronizationModel resolveLocalModels(IResource left, IResource right, IResource origin,
IProgressMonitor monitor) throws InterruptedException {
-
- ThreadSafeProgressMonitor subMonitor = new ThreadSafeProgressMonitor(SubMonitor.convert(monitor, 100));
- try {
- if (!(left instanceof IFile && right instanceof IFile && (origin == null || origin instanceof IFile))) {
- return resolveNonFileLocalModels(left, right, origin, subMonitor);
- } else {
- return resolveFileLocalModel(left, right, origin, subMonitor);
- }
- } finally {
- subMonitor.setWorkRemaining(0);
- }
- }
-
- private SynchronizationModel resolveNonFileLocalModels(IResource left, IResource right, IResource origin,
- ThreadSafeProgressMonitor subMonitor) throws InterruptedException {
- // Sub-optimal implementation, we'll only try and resolve each side individually
- final StorageTraversal leftTraversal = resolveLocalModel(left, subMonitor);
- final StorageTraversal rightTraversal = resolveLocalModel(right, subMonitor);
- final StorageTraversal originTraversal;
- if (origin != null) {
- originTraversal = resolveLocalModel(origin, subMonitor);
- } else {
- originTraversal = new StorageTraversal(Sets.<IStorage> newLinkedHashSet());
- }
-
- return new SynchronizationModel(leftTraversal, rightTraversal, originTraversal);
- }
-
- private SynchronizationModel resolveFileLocalModel(IResource left, IResource right, IResource origin,
- ThreadSafeProgressMonitor monitor) throws InterruptedException {
-
- lock.lockInterruptibly();
- try {
- while (!currentlyResolving.isEmpty()) {
- notResolving.await();
- }
-
- setupResolving();
-
- if (getResolutionScope() != CrossReferenceResolutionScope.SELF) {
- final SynchronizedResourceSet resourceSet = new SynchronizedResourceSet(
- new MonitoredProxyCreationListener(monitor, false));
- updateDependencies(resourceSet, (IFile)left, monitor);
- updateDependencies(resourceSet, (IFile)right, monitor);
- if (origin instanceof IFile) {
- updateDependencies(resourceSet, (IFile)origin, monitor);
- }
- updateChangedResources(resourceSet, monitor);
- }
-
- final URI leftURI = createURIFor((IFile)left);
- final URI rightURI = createURIFor((IFile)right);
- final URI originURI;
- final Set<IFile> startingPoints;
- if (origin instanceof IFile) {
- startingPoints = ImmutableSet.of((IFile)left, (IFile)right, (IFile)origin);
- originURI = createURIFor((IFile)origin);
- } else {
- startingPoints = ImmutableSet.of((IFile)left, (IFile)right);
- originURI = null;
- }
-
- while (!currentlyResolving.isEmpty()) {
- resolutionEnd.await();
- }
-
- if (monitor.isCanceled()) {
- throw new OperationCanceledException();
- }
-
- final Set<IStorage> leftTraversal;
- final Set<IStorage> rightTraversal;
- final Set<IStorage> originTraversal;
- if (origin instanceof IFile) {
- leftTraversal = resolveTraversal((IFile)left, ImmutableSet.of(rightURI, originURI));
- rightTraversal = resolveTraversal((IFile)right, ImmutableSet.of(leftURI, originURI));
- originTraversal = resolveTraversal((IFile)origin, ImmutableSet.of(leftURI, rightURI));
- } else {
- leftTraversal = resolveTraversal((IFile)left, Collections.singleton(rightURI));
- rightTraversal = resolveTraversal((IFile)right, Collections.singleton(leftURI));
- originTraversal = Collections.emptySet();
- }
-
- /*
- * If one resource of the logical model was pointing to both (or "all three") of our starting
- * elements, we'll have way too many things in our traversal. We need to remove the intersection
- * before going any further.
- */
- Set<IStorage> intersection = intersection(leftTraversal, rightTraversal);
- if (!originTraversal.isEmpty()) {
- intersection = intersection(intersection, originTraversal);
- }
- logCoherenceThreats(Iterables.transform(startingPoints, AS_URI), Iterables.transform(
- intersection, AS_URI));
-
- final Set<IStorage> actualLeft = new LinkedHashSet<IStorage>(Sets.difference(leftTraversal,
- intersection));
- final Set<IStorage> actualRight = new LinkedHashSet<IStorage>(Sets.difference(rightTraversal,
- intersection));
- final Set<IStorage> actualOrigin = new LinkedHashSet<IStorage>(Sets.difference(originTraversal,
- intersection));
- final SynchronizationModel synchronizationModel = new SynchronizationModel(new StorageTraversal(
- actualLeft), new StorageTraversal(actualRight), new StorageTraversal(actualOrigin),
- diagnostic);
-
- return synchronizationModel;
- } finally {
- try {
- finalizeResolving();
- } finally {
- notResolving.signal();
- lock.unlock();
- }
- }
+ LocalModelsResolution comp = new LocalModelsResolution(context, left, right, origin, monitor);
+ return comp.run();
}
/**
@@ -525,1333 +145,10 @@ public class ThreadedModelResolver extends AbstractModelResolver {
* potential unforeseen interactions.
* </p>
*/
- public SynchronizationModel resolveModels(IStorageProviderAccessor storageAccessor, IStorage left,
- IStorage right, IStorage origin, IProgressMonitor monitor) throws InterruptedException {
-
- ThreadSafeProgressMonitor subMonitor = null;
- lock.lockInterruptibly();
- try {
- subMonitor = new ThreadSafeProgressMonitor(SubMonitor.convert(monitor, 100));
-
- while (!currentlyResolving.isEmpty()) {
- notResolving.await();
- }
-
- setupResolving();
-
- final IFile leftFile = adaptAs(left, IFile.class);
-
- final SynchronizationModel synchronizationModel;
- if (leftFile != null) {
- synchronizationModel = resolveModelsWithLocal(storageAccessor, leftFile, right, origin,
- subMonitor);
- } else {
- synchronizationModel = resolveRemoteModels(storageAccessor, left, right, origin, subMonitor);
- }
-
- return synchronizationModel;
- } finally {
- try {
- finalizeResolving();
-
- if (subMonitor != null) {
- subMonitor.setWorkRemaining(0);
- }
- } finally {
- notResolving.signal();
- lock.unlock();
- }
- }
- }
-
- /**
- * The 'left' model we've been fed is a local file. We'll assume that the whole 'left' side of this
- * comparison is local and resolve everything for that side as we would for local comparisons : update the
- * dependency graph according to our resource listener, lookup for cross-references to/from the left
- * resource according to the {@link #getResolutionScope() resolution scope}... Once we've resolved the
- * local traversal, we'll use that as a base to infer the two remote sides, then "augment" it with the
- * cross-references of the remote variants of these resources.
- *
- * @param storageAccessor
- * The accessor that can be used to retrieve synchronization information between our resources.
- * @param left
- * File corresponding to the left side of this comparison.
- * @param right
- * "starting point" of the traversal to resolve as the right logical model.
- * @param origin
- * "starting point" of the traversal to resolve as the origin logical model (common ancestor of
- * left and right). Can be <code>null</code>.
- * @param monitor
- * Monitor on which to report progress to the user.
- * @return The SynchronizationModel describing the traversals of all three sides of this logical model.
- * @throws InterruptedException
- * Thrown if the resolution is cancelled or interrupted one way or another.
- */
- private SynchronizationModel resolveModelsWithLocal(IStorageProviderAccessor storageAccessor, IFile left,
- IStorage right, IStorage origin, ThreadSafeProgressMonitor monitor) throws InterruptedException {
- // Update changes and compute dependencies for left
- // Then load the same set of resources for the remote sides, completing it top-down
-
- if (getResolutionScope() != CrossReferenceResolutionScope.SELF) {
- final SynchronizedResourceSet resourceSet = new SynchronizedResourceSet(
- new MonitoredProxyCreationListener(monitor, false));
- updateDependencies(resourceSet, left, monitor);
- updateChangedResources(resourceSet, monitor);
- }
-
- while (!currentlyResolving.isEmpty()) {
- resolutionEnd.await();
- }
- if (monitor.isCanceled()) {
- throw new OperationCanceledException();
- }
-
- final Set<IStorage> leftTraversal = resolveTraversal(left, Collections.<URI> emptySet());
-
- return resolveRemoteTraversals(storageAccessor, leftTraversal, right, origin, monitor);
- }
-
- /**
- * All three sides we've been fed are remote. We'll resolve all three with a simple a top-down algorithm
- * (detect only outgoing cross-references).
- *
- * @param storageAccessor
- * The accessor that can be used to retrieve synchronization information between our resources.
- * @param left
- * "starting point" of the traversal to resolve as the left logical model.
- * @param right
- * "starting point" of the traversal to resolve as the right logical model.
- * @param origin
- * "starting point" of the traversal to resolve as the origin logical model (common ancestor of
- * left and right). Can be <code>null</code>.
- * @param monitor
- * Monitor on which to report progress to the user.
- * @return The SynchronizationModel describing the traversals of all three sides of this logical model.
- * @throws InterruptedException
- * Thrown if the resolution is cancelled or interrupted one way or another.
- */
- private SynchronizationModel resolveRemoteModels(IStorageProviderAccessor storageAccessor, IStorage left,
- IStorage right, IStorage origin, ThreadSafeProgressMonitor monitor) throws InterruptedException {
- final Set<IStorage> leftTraversal = resolveRemoteTraversal(storageAccessor, left, Collections
- .<URI> emptySet(), DiffSide.SOURCE, monitor);
-
- return resolveRemoteTraversals(storageAccessor, leftTraversal, right, origin, monitor);
- }
-
- /**
- * Resolve the remote sides (right and origin, or right alone in case of two-way) of this comparison,
- * inferring a "starting traversal" from the left side.
- * <p>
- * Do note that {@code leftTraversal} <b>will be changed</b> as a result of this call if the right and/or
- * origin sides contain a reference to another resource that was not found from the left
- * cross-referencing, yet does exist in the left side.
- * </p>
- *
- * @param storageAccessor
- * The accessor that can be used to retrieve synchronization information between our resources.
- * @param leftTraversal
- * The already resolved left traversal, to be augmented if right and/or origin have some new
- * resources in their logical model.
- * @param right
- * "starting point" of the traversal to resolve as the right logical model.
- * @param origin
- * "starting point" of the traversal to resolve as the origin logical model (common ancestor of
- * left and right). Can be <code>null</code>.
- * @param monitor
- * Monitor on which to report progress to the user.
- * @return The SynchronizationModel describing the traversals of all three sides of this logical model.
- * @throws InterruptedException
- * Thrown if the resolution is cancelled or interrupted one way or another.
- */
- private SynchronizationModel resolveRemoteTraversals(IStorageProviderAccessor storageAccessor,
- Set<IStorage> leftTraversal, IStorage right, IStorage origin, ThreadSafeProgressMonitor monitor)
+ public SynchronizationModel resolveModels(final IStorageProviderAccessor storageAccessor,
+ final IStorage left, final IStorage right, final IStorage origin, final IProgressMonitor monitor)
throws InterruptedException {
- final Set<IStorage> rightTraversal = resolveRemoteTraversal(storageAccessor, right, Iterables
- .transform(leftTraversal, AS_URI), DiffSide.REMOTE, monitor);
- final Set<IStorage> differenceRightLeft = difference(rightTraversal, asURISet(leftTraversal));
- loadAdditionalRemoteStorages(storageAccessor, leftTraversal, rightTraversal, differenceRightLeft,
- monitor);
-
- final Set<IStorage> originTraversal;
- if (origin != null) {
- final Set<URI> unionLeftRight = Sets.newLinkedHashSet(Iterables.transform(Sets.union(
- leftTraversal, rightTraversal), AS_URI));
- originTraversal = resolveRemoteTraversal(storageAccessor, origin, unionLeftRight,
- DiffSide.ORIGIN, monitor);
- Set<IStorage> differenceOriginLeft = difference(originTraversal, asURISet(leftTraversal));
- Set<IStorage> differenceOriginRight = difference(originTraversal, asURISet(rightTraversal));
- Set<IStorage> additional = symmetricDifference(differenceOriginLeft, differenceOriginRight);
- loadAdditionalRemoteStorages(storageAccessor, leftTraversal, rightTraversal, originTraversal,
- additional, monitor);
- } else {
- originTraversal = Collections.emptySet();
- }
- final SynchronizationModel synchronizationModel = new SynchronizationModel(new StorageTraversal(
- leftTraversal), new StorageTraversal(rightTraversal), new StorageTraversal(originTraversal),
- diagnostic);
-
- return synchronizationModel;
- }
-
- /**
- * If we found some storages in the right traversal that were not part of the left traversal, we need to
- * check whether they exist in the left, since in such a case they must be considered as part of the same
- * logical model.
- * <p>
- * <b>Important</b> : note that the input {@code left} and {@code right} sets <b>will be modified</b> as a
- * result of this call if there are any additional storages to load on these sides.
- * </p>
- *
- * @param storageAccessor
- * The accessor that can be used to retrieve synchronization information between our resources.
- * @param left
- * Traversal of the left logical model.
- * @param right
- * Traversal of the right logical model.
- * @param additional
- * the addition storages we are to lookup in left.
- * @param monitor
- * Monitor on which to report progress to the user.
- * @return The set of all additional resources (both on left and right) that have been loaded as a result
- * of this call.
- * @throws InterruptedException
- * Thrown if the resolution is cancelled or interrupted one way or another.
- */
- private Set<IStorage> loadAdditionalRemoteStorages(IStorageProviderAccessor storageAccessor,
- Set<IStorage> left, Set<IStorage> right, Set<IStorage> additional,
- ThreadSafeProgressMonitor monitor) throws InterruptedException {
- /*
- * This loop will be extremely costly at best, but we hope the case to be sufficiently rare (and the
- * new resources well spread when it happens) not to pose an issue in the most frequent cases.
- */
-
- final Set<IStorage> additionalStorages = new LinkedHashSet<IStorage>();
- final Set<URI> additionalURIs = new LinkedHashSet<URI>();
- // Have we found new resources in the right as compared to the left?
- Set<IStorage> differenceRightLeft = additional;
- while (!differenceRightLeft.isEmpty()) {
- // There's at least one resource in the right that was not found in the left.
- /*
- * This might be a new resource added on the right side... but it might also be a cross-reference
- * that's been either removed from left or added in right. In this second case, we need the
- * resource to be present in both traversals to make sure we'll be able to properly detect
- * potential conflicts. However, since this resource could itself be a part of a larger logical
- * model, we need to start the resolving again with it.
- */
- final Set<IStorage> additionalLeft = findAdditionalRemoteTraversal(storageAccessor, left,
- differenceRightLeft, DiffSide.SOURCE, monitor);
- left.addAll(additionalLeft);
- for (IStorage storage : additionalLeft) {
- final URI newURI = AS_URI.apply(storage);
- if (additionalURIs.add(newURI)) {
- additionalStorages.add(storage);
- }
- }
- /*
- * have we only loaded the resources that were present in the right but not in the left, or have
- * we found even more?
- */
- final Set<IStorage> differenceAdditionalLeftRight = difference(additionalLeft, asURISet(right));
- // If so, we once more need to augment the right traversal
- final Set<IStorage> additionalRight = findAdditionalRemoteTraversal(storageAccessor, right,
- differenceAdditionalLeftRight, DiffSide.REMOTE, monitor);
- right.addAll(additionalRight);
- for (IStorage storage : additionalRight) {
- final URI newURI = AS_URI.apply(storage);
- if (additionalURIs.add(newURI)) {
- additionalStorages.add(storage);
- }
- }
- // Start this loop anew if we once again augmented the right further than what we had in left
- differenceRightLeft = difference(additionalRight, asURISet(left));
- }
- return additionalStorages;
- }
-
- /**
- * If we found some storages in the origin traversal that were part of neither the left nor the right
- * traversals, we need to check whether they exist in them, since in such a case they must be considered
- * as part of the same logical model.
- * <p>
- * <b>Important</b> : note that the input {@code left}, {@code right} and {@code origin} sets <b>will be
- * modified</b> as a result of this call if there are any additional storages to load on either side.
- * </p>
- *
- * @param storageAccessor
- * The accessor that can be used to retrieve synchronization information between our resources.
- * @param left
- * Traversal of the left logical model.
- * @param right
- * Traversal of the right logical model.
- * @param origin
- * Traversal of the origin logical model.
- * @param additional
- * the set of additional storages we are to lookup in right and left.
- * @param monitor
- * Monitor on which to report progress to the user.
- * @throws InterruptedException
- * Thrown if the resolution is cancelled or interrupted one way or another.
- */
- private void loadAdditionalRemoteStorages(IStorageProviderAccessor storageAccessor, Set<IStorage> left,
- Set<IStorage> right, Set<IStorage> origin, Set<IStorage> additional,
- ThreadSafeProgressMonitor monitor) throws InterruptedException {
- /*
- * This loop will be extremely costly at best, but we hope the case to be sufficiently rare (and the
- * new resources well spread when it happens) not to pose an issue in the most frequent cases.
- */
-
- Set<IStorage> additionalStorages = additional;
- while (!additionalStorages.isEmpty()) {
- // There's at least one resource that is in the origin set yet neither in left nor in right.
- final Set<IStorage> additionalLeftRightComparedToOrigin = loadAdditionalRemoteStorages(
- storageAccessor, left, right, additionalStorages, monitor);
- /*
- * Have we found even more resources to add to the traversal? If so, augment the origin
- * accordingly.
- */
- final Set<IStorage> additionalOrigin = findAdditionalRemoteTraversal(storageAccessor, origin,
- additionalLeftRightComparedToOrigin, DiffSide.ORIGIN, monitor);
- origin.addAll(additionalOrigin);
- // If we once again found new storages in the origin, restart the loop.
- final Set<IStorage> differenceOriginLeft = difference(additionalOrigin, asURISet(left));
- final Set<IStorage> differenceOriginRight = difference(additionalOrigin, asURISet(right));
- additionalStorages = symmetricDifference(differenceOriginRight, differenceOriginLeft);
-
- // Differences between left/right and origin could come from resources that are present in the
- // origin, but were deleted in one of the sides. As these resources already exist in the origin,
- // they
- // need to be removed from the additionalStorages
- additionalStorages.removeAll(origin);
- }
- }
-
- /**
- * Tries and resolve the given set of additional storages (as compared to {@code alreadyLoaded}) on the
- * given side.
- * <p>
- * If the storages from {@code additionalStorages} do not (or no longer) exist on the given side, this
- * will have no effect. Otherwise, they'll be loaded and resolved in order to determine whether they are
- * part of a larger model. Whether they're part of a larger model or not, they will be returned by this
- * method as long as they exist on the given side.
- * </p>
- *
- * @param storageAccessor
- * The accessor that can be used to retrieve synchronization information between our resources.
- * @param alreadyLoaded
- * All storages that have already been loaded on the given side. This will prevent us from
- * resolving the same model more than once.
- * @param additionalStorages
- * The set of additional storages we are to find and resolve on the given side.
- * @param side
- * Side on which we seek to load additional storages in the traversal.
- * @param monitor
- * Monitor on which to report progress to the user.
- * @return The set of additional storages that are to be added to the traversal of the given side.
- * @throws InterruptedException
- * Thrown if the resolution is cancelled or interrupted one way or another.
- */
- private Set<IStorage> findAdditionalRemoteTraversal(IStorageProviderAccessor storageAccessor,
- Set<IStorage> alreadyLoaded, Set<IStorage> additionalStorages, DiffSide side,
- ThreadSafeProgressMonitor monitor) throws InterruptedException {
- final SynchronizedResourceSet resourceSet = new SynchronizedResourceSet(
- new MonitoredProxyCreationListener(monitor, true));
- final StorageURIConverter converter = new RevisionedURIConverter(resourceSet.getURIConverter(),
- storageAccessor, side);
- resourceSet.setURIConverter(converter);
-
- resolvedResources = Sets
- .newLinkedHashSet(Iterables.transform(converter.getLoadedRevisions(), AS_URI));
-
- for (IStorage additional : additionalStorages) {
- final URI expectedURI = ResourceUtil.createURIFor(additional);
- demandRemoteResolve(resourceSet, expectedURI, monitor);
- }
-
- while (!currentlyResolving.isEmpty()) {
- resolutionEnd.await();
- }
-
- if (monitor.isCanceled()) {
- throw new OperationCanceledException();
- }
-
- resolvedResources = null;
-
- return converter.getLoadedRevisions();
- }
-
- /**
- * Returns the set of all elements that are contained neither in set1 nor in set2.
- *
- * @param set1
- * First of the two sets.
- * @param set2
- * Second of the two sets.
- * @return The set of all elements that are contained neither in set1 nor in set2.
- */
- private Set<IStorage> symmetricDifference(Set<IStorage> set1, Set<IStorage> set2) {
- final Set<URI> uris1 = Sets.newLinkedHashSet(Iterables.transform(set1, AS_URI));
- final Set<URI> uris2 = Sets.newLinkedHashSet(Iterables.transform(set2, AS_URI));
-
- final Set<IStorage> symmetricDifference = new LinkedHashSet<IStorage>();
- for (IStorage storage1 : set1) {
- if (!uris2.contains(AS_URI.apply(storage1))) {
- symmetricDifference.add(storage1);
- }
- }
- for (IStorage storage2 : set2) {
- if (!uris1.contains(AS_URI.apply(storage2))) {
- symmetricDifference.add(storage2);
- }
- }
- return symmetricDifference;
- }
-
- /**
- * Returns the set of all elements that are contained in {@code set1} but not in {@code set2}.
- *
- * @param set1
- * First of the two sets.
- * @param set2
- * Second of the two sets.
- * @return The set of all elements that are contained in {@code set1} but not in {@code set2}.
- */
- private Set<IStorage> difference(Set<IStorage> set1, Set<URI> set2) {
- final Set<IStorage> difference = new LinkedHashSet<IStorage>();
- for (IStorage storage1 : set1) {
- final URI uri = AS_URI.apply(storage1);
- if (!set2.contains(uri)) {
- difference.add(storage1);
- }
- }
- return difference;
- }
-
- private Set<URI> asURISet(Set<IStorage> storages) {
- final Set<URI> uris = new LinkedHashSet<URI>();
- for (IStorage storage : storages) {
- uris.add(AS_URI.apply(storage));
- }
- return uris;
- }
-
- /**
- * This should be call before starting any model resolution but it must not be call if another resolution
- * is already running (i.e. it must be call after an {@link #notResolving}.await()).
- */
- private void setupResolving() {
- createThreadPools();
- resolvedResources = new LinkedHashSet<URI>();
- diagnostic = new BasicDiagnostic(EMFCompareIDEUIPlugin.PLUGIN_ID, 0, null, new Object[0]);
- }
-
- /**
- * This is the counterpart of the {@link #setupResolving()} method. This should be called in a finally
- * block everywhere {@link #setupResolving()} is called.
- */
- private void finalizeResolving() {
- if (!shutdownInProgress.get()) {
- shutdownPools();
- }
-
- if (diagnostic.getSeverity() >= Diagnostic.ERROR) {
- // something bad (or a cancel request) happened during resolution, so we invalidate the
- // dependency graph to avoid weird behavior next time the resolution is called.
- dependencyGraph.clear();
- }
-
- resolvedResources = null;
- diagnostic = null;
- }
-
- /**
- * Checks the current state of our {@link #resourceListener} and updates the dependency graph for all
- * resources that have been changed since we last checked.
- *
- * @param resourceSet
- * The resource set in which to load our temporary resources.
- * @param monitor
- * Monitor on which to report progress to the user.
- */
- private void updateChangedResources(SynchronizedResourceSet resourceSet, ThreadSafeProgressMonitor monitor) {
- final Set<URI> removedURIs = Sets.difference(resourceListener.popRemovedURIs(), resolvedResources);
- final Set<URI> changedURIs = Sets.difference(resourceListener.popChangedURIs(), resolvedResources);
-
- dependencyGraph.removeAll(removedURIs);
-
- // We need to re-resolve the changed resources, along with their direct parents
- final Set<URI> recompute = new LinkedHashSet<URI>(changedURIs);
- final Multimap<URI, URI> parentToGrandParents = ArrayListMultimap.create();
- for (URI changed : changedURIs) {
- if (dependencyGraph.contains(changed)) {
- Set<URI> directParents = dependencyGraph.getDirectParents(changed);
- recompute.addAll(directParents);
- for (URI uri : directParents) {
- Set<URI> grandParents = dependencyGraph.getDirectParents(uri);
- parentToGrandParents.putAll(uri, grandParents);
- }
- }
- }
- dependencyGraph.removeAll(recompute);
-
- for (URI changed : recompute) {
- demandResolve(resourceSet, changed, monitor);
- }
-
- lock.lock();
- try {
- while (!currentlyResolving.isEmpty()) {
- resolutionEnd.await();
- }
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- } finally {
- lock.unlock();
- }
-
- // Re-connect changed resources parents' with their parents
- for (URI uri : parentToGrandParents.keySet()) {
- if (dependencyGraph.contains(uri)) {
- for (URI parent : parentToGrandParents.get(uri)) {
- demandResolve(resourceSet, parent, monitor);
- }
- }
- }
-
- lock.lock();
- try {
- while (!currentlyResolving.isEmpty()) {
- resolutionEnd.await();
- }
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Update the dependency graph to make sure that it contains the given file.
- * <p>
- * If the graph does not yet contain this file, we'll try and find cross-references outgoing from and/or
- * incoming to the given file, depending on the current {@link #getResolutionScope() resolution scope}.
- * </p>
- *
- * @param resourceSet
- * The resource set in which to load the temporary resources.
- * @param file
- * The file which we need to be present in the dependency graph.
- * @param monitor
- * Monitor on which to report progress to the user.
- */
- private void updateDependencies(SynchronizedResourceSet resourceSet, IFile file,
- ThreadSafeProgressMonitor monitor) {
- final URI expectedURI = createURIFor(file);
- if (!dependencyGraph.contains(expectedURI)) {
- final IResource startingPoint = getResolutionStartingPoint(file);
- final ModelResourceVisitor modelVisitor = new ModelResourceVisitor(resourceSet, monitor);
- try {
- startingPoint.accept(modelVisitor);
- } catch (CoreException e) {
- safeMergeDiagnostic(BasicDiagnostic.toDiagnostic(e));
- }
- }
- }
-
- /**
- * Returns the starting point for the resolution of the given file's logical model according to
- * {@link #getResolutionScope()}.
- *
- * @param file
- * The file which logical model we need to add to the current {@link #dependencyGraph}.
- * @return Starting point for this file's logical model resolution.
- * @see CrossReferenceResolutionScope
- */
- private IResource getResolutionStartingPoint(IFile file) {
- final IResource startingPoint;
- switch (getResolutionScope()) {
- case WORKSPACE:
- startingPoint = ResourcesPlugin.getWorkspace().getRoot();
- break;
- case PROJECT:
- startingPoint = file.getProject();
- break;
- case CONTAINER:
- startingPoint = file.getParent();
- break;
- case OUTGOING:
- // fall through, the difference between SELF and OUTGOING will only come later on
- case SELF:
- // fall through
- default:
- startingPoint = file;
- break;
- }
- return startingPoint;
- }
-
- /**
- * Tells this resolver how much of the dependency graph should be created at once. Note that this value
- * may change during a resolution, which sole "visible" effect would be to prevent resolution of further
- * outgoing references if the new value is "SELF".
- *
- * @return The current resolution scope.
- */
- private CrossReferenceResolutionScope getResolutionScope() {
- final IPreferenceStore store = EMFCompareIDEUIPlugin.getDefault().getPreferenceStore();
- if (store.getBoolean(EMFCompareUIPreferences.DISABLE_RESOLVERS_PREFERENCE)) {
- return CrossReferenceResolutionScope.SELF;
- }
- final String stringValue = store.getString(EMFCompareUIPreferences.RESOLUTION_SCOPE_PREFERENCE);
- return CrossReferenceResolutionScope.valueOf(stringValue);
- }
-
- private Set<IStorage> resolveTraversal(IFile file, Set<URI> bounds) {
- final URI baseUri = createURIFor(file);
- final Set<IStorage> traversalSet = new LinkedHashSet<IStorage>();
- for (URI uri : getUriAndDependentUrisFromDependencyProvider(baseUri, URIConverter.INSTANCE)) {
- final IFile toResolve = getFileAt(uri);
- final Iterable<URI> dependencies = getDependenciesOf(toResolve, bounds);
- for (URI dep : dependencies) {
- traversalSet.add(getFileAt(dep));
- }
- }
- return traversalSet;
- }
-
- private Set<URI> getUriAndDependentUrisFromDependencyProvider(URI uri, URIConverter uriConverter) {
- final Set<URI> dependencies = dependencyProviderRegistry.getDependencies(uri, uriConverter);
- return Sets.union(Collections.singleton(uri), dependencies);
- }
-
- private Set<IStorage> resolveRemoteTraversal(IStorageProviderAccessor storageAccessor, IStorage start,
- Iterable<URI> knownVariants, DiffSide side, ThreadSafeProgressMonitor monitor)
- throws InterruptedException {
- // we can't call ResourceUtil.createURIFor(start) if start is null, so
- if (start == null) {
- return Sets.newLinkedHashSet();
- }
- final SynchronizedResourceSet resourceSet = new SynchronizedResourceSet(
- new MonitoredProxyCreationListener(monitor, true));
- final StorageURIConverter converter = new RevisionedURIConverter(resourceSet.getURIConverter(),
- storageAccessor, side);
- resourceSet.setURIConverter(converter);
-
- resolvedResources = new LinkedHashSet<URI>();
-
- for (URI known : knownVariants) {
- demandRemoteResolve(resourceSet, known, monitor);
- }
-
- final URI startURI = ResourceUtil.createURIFor(start);
- demandRemoteResolve(resourceSet, startURI, monitor);
-
- while (!currentlyResolving.isEmpty()) {
- resolutionEnd.await();
- }
-
- if (monitor.isCanceled()) {
- throw new OperationCanceledException();
- }
-
- resolvedResources = null;
-
- return converter.getLoadedRevisions();
- }
-
- private Iterable<URI> getDependenciesOf(IFile file, Set<URI> bounds) {
- final URI expectedURI = ResourceUtil.createURIFor(file);
-
- final Iterable<URI> dependencies;
- switch (getResolutionScope()) {
- case WORKSPACE:
- dependencies = dependencyGraph.getSubgraphContaining(expectedURI, bounds);
- break;
- case PROJECT:
- final Set<URI> allDependencies = dependencyGraph.getSubgraphContaining(expectedURI, bounds);
- final IResource project = file.getProject();
- dependencies = Iterables.filter(allDependencies, isInContainer(project));
- break;
- case CONTAINER:
- final Set<URI> allDependencies1 = dependencyGraph.getSubgraphContaining(expectedURI, bounds);
- final IResource container = file.getParent();
- dependencies = Iterables.filter(allDependencies1, isInContainer(container));
- break;
- case OUTGOING:
- dependencies = dependencyGraph.getTreeFrom(expectedURI, bounds);
- break;
- case SELF:
- // fall through
- default:
- dependencies = Collections.singleton(expectedURI);
- break;
- }
- return dependencies;
- }
-
- /**
- * Returns the IFile located at the given URI.
- *
- * @param uri
- * URI we need the file for.
- * @return The IFile located at the given URI.
- */
- private IFile getFileAt(URI uri) {
- final StringBuilder path = new StringBuilder();
- List<String> segments = uri.segmentsList();
- if (uri.isPlatformResource()) {
- segments = segments.subList(1, segments.size());
- }
- for (String segment : segments) {
- path.append(URI.decode(segment)).append('/');
- }
- return ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(path.toString()));
- }
-
- /**
- * This predicate can be used to check wether a given URI points to a workspace resource contained in the
- * given container.
- *
- * @param container
- * The container in which we need the resources to be contained.
- * @return A ready to use predicate.
- */
- private Predicate<URI> isInContainer(final IResource container) {
- return new Predicate<URI>() {
- public boolean apply(URI input) {
- if (input != null) {
- final IFile pointedFile = getFileAt(input);
- if (pointedFile != null) {
- return container.getLocation().isPrefixOf(pointedFile.getLocation());
- }
- }
- return false;
- }
- };
- }
-
- /**
- * When executing local comparisons, we resolve the full logical model of both (or "all three of") the
- * compared files.
- * <p>
- * If there is one resource in the scope that references all of these starting points, then we'll have
- * perfectly identical logical models for all comparison sides. Because of that, we need to constrain the
- * logical model of each starting point to only parts that are not accessible from other starting points.
- * This might cause coherence issues as merging could thus "break" references from other files to our
- * compared ones.
- * </p>
- * <p>
- * This method will be used to browse the files that are removed from the logical model, and log a warning
- * for the files that are removed even though they are "parents" of one of the starting points.
- * </p>
- *
- * @param startingPoints
- * Starting points of the comparison.
- * @param removedFromModel
- * All files that have been removed from the comparison scope.
- */
- private void logCoherenceThreats(Iterable<URI> startingPoints, Iterable<URI> removedFromModel) {
- final Set<URI> coherenceThreats = new LinkedHashSet<URI>();
- for (URI start : startingPoints) {
- for (URI removed : removedFromModel) {
- if (dependencyGraph.hasChild(removed, start)) {
- coherenceThreats.add(removed);
- }
- }
- }
-
- if (!coherenceThreats.isEmpty()) {
- // FIXME: should be added to diagnostic instead
- final String message = EMFCompareIDEUIMessages.getString("ModelResolver.coherenceWarning"); //$NON-NLS-1$
- final String details = Iterables.toString(coherenceThreats);
- EMFCompareIDEUIPlugin.getDefault().getLog().log(
- new Status(IStatus.WARNING, EMFCompareIDEUIPlugin.PLUGIN_ID, message + '\n' + details));
- }
- }
-
- /**
- * Allows callers to launch the loading and resolution of the model pointed at by the given URI.
- * <p>
- * This will check whether the given storage isn't already being resolved, then submit a job to the
- * {@link #resolvingPool} to load and resolve the model in a separate thread.
- * </p>
- *
- * @param resourceSet
- * The resource set in which to load the resource.
- * @param uri
- * The uri we are to try and load as a model.
- * @param monitor
- * Monitor on which to report progress to the user.
- * @see ResourceResolver
- */
- protected void demandResolve(SynchronizedResourceSet resourceSet, URI uri,
- final ThreadSafeProgressMonitor monitor) {
- if (isInterruptedOrCanceled(monitor)) {
- demandResolvingAndUnloadingPoolShutdown();
- return;
- }
-
- lock.lock();
- try {
- monitor.setWorkRemaining(1000);
- for (URI currentUri : getUriAndDependentUrisFromDependencyProvider(uri, resourceSet
- .getURIConverter())) {
- if (resolvedResources.add(currentUri) && currentlyResolving.add(currentUri)) {
- ListenableFuture<?> future = resolvingPool.submit(new ResourceResolver(resourceSet,
- currentUri, monitor));
- Futures.addCallback(future, new ResolvingFutureCallback(monitor, currentUri));
- }
- }
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Allows callers to launch the loading and resolution of the model pointed at by the given URI, without
- * updating the {@link #dependencyGraph} along the way.
- * <p>
- * This will check whether the given storage isn't already being resolved, then submit a job to the
- * {@link #resolvingPool} to load and resolve the model in a separate thread.
- * </p>
- *
- * @param resourceSet
- * The resource set in which to load the resource.
- * @param uri
- * The uri we are to try and load as a model.
- * @param monitor
- * Monitor on which to report progress to the user.
- */
- protected void demandRemoteResolve(SynchronizedResourceSet resourceSet, URI uri,
- final ThreadSafeProgressMonitor monitor) {
- if (isInterruptedOrCanceled(monitor)) {
- demandResolvingAndUnloadingPoolShutdown();
- return;
- }
-
- lock.lock();
- try {
- monitor.setWorkRemaining(1000);
- for (URI currentUri : getUriAndDependentUrisFromDependencyProvider(uri, resourceSet
- .getURIConverter())) {
- if (resolvedResources.add(currentUri) && currentlyResolving.add(currentUri)) {
- ListenableFuture<?> future = resolvingPool.submit(new RemoteResourceResolver(resourceSet,
- currentUri, monitor));
- Futures.addCallback(future, new ResolvingFutureCallback(monitor, currentUri));
- }
- }
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Allows callers to launch the unloading of the given resource.
- * <p>
- * Do note that even though this is called "unload", we won't actually call {@link Resource#unload()} on
- * the given resource unless we deem it necessary (we only call if for UML because of the CacheAdapter)
- * for now. This will only remove the resource from its containing resource set so as to allow it to be
- * garbage collected.
- * </p>
- *
- * @param resourceSet
- * The resource set containing the resource to be unloaded.
- * @param resource
- * The resource to unload.
- * @param monitor
- * Monitor on which to report progress to the user.
- * @see ResourceUnloader
- */
- protected void demandUnload(SynchronizedResourceSet resourceSet, Resource resource,
- final ThreadSafeProgressMonitor monitor) {
-
- // Regardless of the amount of progress reported so far, use 0.1% of the space remaining in the
- // monitor to process the next node.
- monitor.setWorkRemaining(1000);
- ListenableFuture<?> future = unloadingPool
- .submit(new ResourceUnloader(resourceSet, resource, monitor));
- Futures.addCallback(future, new FutureCallback<Object>() {
- public void onSuccess(Object result) {
- if (!isInterruptedOrCanceled(monitor)) {
- monitor.worked(1);
- }
- }
-
- public void onFailure(Throwable t) {
- if (!isInterruptedOrCanceled(monitor)) {
- monitor.worked(1);
- safeMergeDiagnostic(BasicDiagnostic.toDiagnostic(t));
- }
- }
- });
- }
-
- /**
- * Thread safely merge the given diagnostic to the {@link #diagnostic} field.
- *
- * @param resourceDiagnostic
- * the diagnostic to be added to the global diagnostic.
- */
- private void safeMergeDiagnostic(Diagnostic resourceDiagnostic) {
- lock.lock();
- try {
- diagnostic.merge(resourceDiagnostic);
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Checks if the current thread is interrupted or if the given monitor has been canceled.
- *
- * @param monitor
- * the monitor to check
- * @return true if the current thread has been canceled, false otherwise.
- */
- private boolean isInterruptedOrCanceled(IProgressMonitor monitor) {
- return Thread.currentThread().isInterrupted() || monitor.isCanceled();
- }
-
- /**
- * If {@link #shutdownInProgress shutdown has not been requested before}, it submits a new task to
- * {@link #shutdownPools() shut down} {@link #resolvingPool} and {@link #unloadingPool}. Do nothing if
- * current thread already is interrupted.
- */
- private void demandResolvingAndUnloadingPoolShutdown() {
- if (!Thread.currentThread().isInterrupted()) {
- if (shutdownInProgress.compareAndSet(false, true)) {
- Runnable runnable = new Runnable() {
- public void run() {
- shutdownPools();
- }
- };
-
- ListenableFuture<?> listenableFuture = terminator.submit(runnable);
- Futures.addCallback(listenableFuture, new FutureCallback<Object>() {
- public void onSuccess(Object result) {
- shutdownInProgress.set(false);
- }
-
- public void onFailure(Throwable t) {
- shutdownInProgress.set(false);
- EMFCompareIDEUIPlugin.getDefault().log(t);
- }
- });
- }
- }
- }
-
- /**
- * This will remove the given uri from the {@link #currentlyResolving} set and signal to
- * {@link #resolutionEnd} if the set is empty afterward. This method must be call by every callback of
- * resolving tasks.
- *
- * @param uri
- * the uri to remove.
- */
- private void finalizeResolvingTask(URI uri) {
- lock.lock();
- try {
- currentlyResolving.remove(uri);
- if (currentlyResolving.isEmpty()) {
- resolutionEnd.signal();
- }
- } finally {
- lock.unlock();
- }
- }
-
- private class MonitoredProxyCreationListener implements IProxyCreationListener {
- private final ThreadSafeProgressMonitor monitor;
-
- private final boolean remote;
-
- public MonitoredProxyCreationListener(ThreadSafeProgressMonitor monitor, boolean remote) {
- this.monitor = monitor;
- this.remote = remote;
- }
-
- public void proxyCreated(Resource source, EObject eObject, EStructuralFeature eStructuralFeature,
- EObject proxy, int position) {
- final URI from = source.getURI();
- final URI to = ((InternalEObject)proxy).eProxyURI().trimFragment();
-
- if (getResolutionScope() != CrossReferenceResolutionScope.SELF && to.isPlatformResource()) {
- SynchronizedResourceSet resourceSet = (SynchronizedResourceSet)source.getResourceSet();
- if (remote) {
- demandRemoteResolve(resourceSet, to, monitor);
- } else {
- dependencyGraph.addChildren(from, Collections.singleton(to));
- if (eStructuralFeature instanceof EReference
- && ((EReference)eStructuralFeature).isContainment()) {
- dependencyGraph.addParentData(to, EcoreUtil.getURI(eObject));
- }
- demandResolve(resourceSet, to, monitor);
- }
- }
- }
- }
-
- /**
- * The callback for {@link ResourceResolver} and {@link RemoteResourceResolver} tasks. It will report
- * progress, log errors and finalize the resolving and as such, possibly signaling the end of the
- * resolution.
- *
- * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a>
- */
- private final class ResolvingFutureCallback implements FutureCallback<Object> {
-
- /** The monitor to which report progress. */
- private final IProgressMonitor monitor;
-
- private final URI uri;
-
- /**
- * @param monitor
- */
- private ResolvingFutureCallback(IProgressMonitor monitor, URI uri) {
- this.monitor = monitor;
- this.uri = uri;
- }
-
- public void onSuccess(Object result) {
- try {
- if (!isInterruptedOrCanceled(monitor)) {
- // do not report progress anymore when the task has been interrupted of canceled. It
- // speeds up the cancellation.
- monitor.worked(1);
- }
- } finally {
- finalizeResolvingTask(uri);
- }
- }
-
- public void onFailure(Throwable t) {
- try {
- if (!isInterruptedOrCanceled(monitor)) {
- // do not report progress or errors anymore when the task has been interrupted of
- // canceled. It speeds up the cancellation.
- monitor.worked(1);
- safeMergeDiagnostic(BasicDiagnostic.toDiagnostic(t));
- }
- } finally {
- finalizeResolvingTask(uri);
- }
- }
- }
-
- /**
- * Implements a runnable that will load the EMF resource pointed at by a given URI, then resolve all of
- * its cross-referenced resources and update the dependency graph accordingly.
- * <p>
- * Once done with the resolution, this thread will spawn an independent job to unload the resource.
- * </p>
- *
- * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
- */
- private class ResourceResolver implements Runnable {
- /** The resource set in which to load the resource. */
- private final SynchronizedResourceSet resourceSet;
-
- /** URI that needs to be loaded as an EMF model. */
- private final URI uri;
-
- /** Monitor on which to report progress to the user. */
- private final ThreadSafeProgressMonitor monitor;
-
- /**
- * Default constructor.
- *
- * @param resourceSet
- * The resource set in which to load the resource.
- * @param uri
- * URI that needs to be loaded as an EMF model.
- * @param monitor
- * Monitor on which to report progress to the user.
- */
- public ResourceResolver(SynchronizedResourceSet resourceSet, URI uri,
- ThreadSafeProgressMonitor monitor) {
- this.resourceSet = resourceSet;
- this.uri = uri;
- this.monitor = monitor;
- }
-
- /** {@inheritDoc} */
- public void run() {
- if (isInterruptedOrCanceled(monitor)) {
- demandResolvingAndUnloadingPoolShutdown();
- return;
- }
-
- final Resource resource = resourceSet.loadResource(uri);
- Diagnostic resourceDiagnostic = EcoreUtil.computeDiagnostic(resource, true);
- if (resourceDiagnostic.getSeverity() >= Diagnostic.WARNING) {
- safeMergeDiagnostic(resourceDiagnostic);
- }
- dependencyGraph.add(uri);
- demandUnload(resourceSet, resource, monitor);
- }
- }
-
- /**
- * Implements a runnable that will load the given EMF resource, then launch the resolution of all
- * cross-references of this resource in separate threads. This will not update the dependency graph.
- *
- * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
- */
- private class RemoteResourceResolver implements Runnable {
- /** The resource set in which to load the resource. */
- private final SynchronizedResourceSet resourceSet;
-
- /** URI that needs to be loaded as an EMF model. */
- private final URI uri;
-
- /** Monitor on which to report progress to the user. */
- private final ThreadSafeProgressMonitor monitor;
-
- /**
- * Constructs a resolver to load a resource from its URI.
- *
- * @param resourceSet
- * The resource set in which to load the resource.
- * @param uri
- * The URI that needs to be loaded as an EMF model.
- * @param monitor
- * Monitor on which to report progress to the user.
- */
- public RemoteResourceResolver(SynchronizedResourceSet resourceSet, URI uri,
- ThreadSafeProgressMonitor monitor) {
- this.resourceSet = resourceSet;
- this.uri = uri;
- this.monitor = monitor;
- }
-
- /** {@inheritDoc} */
- public void run() {
- if (isInterruptedOrCanceled(monitor)) {
- demandResolvingAndUnloadingPoolShutdown();
- return;
- }
-
- final Resource resource = resourceSet.loadResource(uri);
- Diagnostic resourceDiagnostic = EcoreUtil.computeDiagnostic(resource, true);
- if (resourceDiagnostic.getSeverity() >= Diagnostic.WARNING) {
- safeMergeDiagnostic(resourceDiagnostic);
- }
- demandUnload(resourceSet, resource, monitor);
- }
- }
-
- /**
- * Implementation of a Runnable that can be used to unload a given resource and make it
- * garbage-collectable.
- *
- * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
- */
- private static class ResourceUnloader implements Runnable {
- /** The resource set from which to unload a resource. */
- private final SynchronizedResourceSet resourceSet;
-
- /** The resource to unload. */
- private final Resource resource;
-
- /** Monitor on which to report progress to the user. */
- private final IProgressMonitor monitor;
-
- /**
- * Default constructor.
- *
- * @param resourceSet
- * The resource set from which to unload a resource.
- * @param resource
- * The resource to unload.
- * @param monitor
- * Monitor on which to report progress to the user.
- */
- public ResourceUnloader(SynchronizedResourceSet resourceSet, Resource resource,
- IProgressMonitor monitor) {
- this.resourceSet = resourceSet;
- this.resource = resource;
- this.monitor = monitor;
- }
-
- /** {@inheritDoc} */
- public void run() {
- resourceSet.unload(resource, monitor);
- }
- }
-
- /**
- * This implementation of a resource visitor will allow us to browse a given hierarchy and resolve the
- * models files in contains, as determined by {@link ThreadedModelResolver#MODEL_CONTENT_TYPES}.
- *
- * @author <a href="mailto:laurent.goubet@obeo.fr">laurent Goubet</a>
- * @see ThreadedModelResolver#hasModelType(IFile)
- */
- private class ModelResourceVisitor implements IResourceVisitor {
- /** Resource set in which to load the model files this visitor will find. */
- private final SynchronizedResourceSet resourceSet;
-
- /** Monitor on which to report progress to the user. */
- private final ThreadSafeProgressMonitor monitor;
-
- /**
- * Default constructor.
- *
- * @param resourceSet
- * The resource set in which this visitor will load the model files it finds.
- * @param monitor
- * Monitor on which to report progress to the user.
- */
- public ModelResourceVisitor(SynchronizedResourceSet resourceSet, ThreadSafeProgressMonitor monitor) {
- this.resourceSet = resourceSet;
- this.monitor = monitor;
- }
-
- /** {@inheritDoc} */
- public boolean visit(IResource resource) throws CoreException {
- if (isInterruptedOrCanceled(monitor)) {
- demandResolvingAndUnloadingPoolShutdown();
- // cancel the visit
- throw new OperationCanceledException();
- }
-
- if (resource instanceof IFile) {
- final IFile file = (IFile)resource;
- if (hasModelType(file)) {
- final URI expectedURI = ResourceUtil.createURIFor(file);
- demandResolve(resourceSet, expectedURI, monitor);
- }
- return false;
- }
- return true;
- }
- }
-
- /**
- * This will listen to workspace changes and react to all changes on "model" resources as determined by
- * {@link ThreadedModelResolver#MODEL_CONTENT_TYPES}.
- *
- * @author <a href="mailto:laurent.goubet@obeo.fr">laurent Goubet</a>
- * @see ThreadedModelResolver#hasModelType(IFile)
- */
- private static class ModelResourceListener implements IResourceChangeListener {
- /** Keeps track of the URIs that need to be reparsed when next we need the dependencies graph . */
- protected final Set<URI> changedURIs;
-
- /** Tracks the files that have been removed. */
- protected final Set<URI> removedURIs;
-
- /** Prevents concurrent access to the two internal sets. */
- protected final ReentrantLock internalLock;
-
- /** Initializes this listener. */
- public ModelResourceListener() {
- this.changedURIs = new LinkedHashSet<URI>();
- this.removedURIs = new LinkedHashSet<URI>();
- this.internalLock = new ReentrantLock();
- }
-
- /** {@inheritDoc} */
- public void resourceChanged(IResourceChangeEvent event) {
- final IResourceDelta delta = event.getDelta();
- if (delta == null) {
- return;
- }
-
- /*
- * We must block any and all threads from using the two internal sets through either
- * popChangedURIs or popRemovedURIs while we are parsing a resource delta. This particular locking
- * is here to avoid such misuses.
- */
- internalLock.lock();
- try {
- delta.accept(new ModelResourceDeltaVisitor());
- } catch (CoreException e) {
- EMFCompareIDEUIPlugin.getDefault().log(e);
- } finally {
- internalLock.unlock();
- }
- }
-
- /**
- * Retrieves the set of all changed URIs since we last updated the dependencies graph, and clears it
- * for subsequent calls.
- *
- * @return The set of all changed URIs since we last updated the dependencies graph.
- */
- public Set<URI> popChangedURIs() {
- final Set<URI> changed;
- internalLock.lock();
- try {
- changed = ImmutableSet.copyOf(changedURIs);
- changedURIs.clear();
- } finally {
- internalLock.unlock();
- }
- return changed;
- }
-
- /**
- * Retrieves the set of all removed URIs since we last updated the dependencies graph, and clears it
- * for subsequent calls.
- *
- * @return The set of all removed URIs since we last updated the dependencies graph.
- */
- public Set<URI> popRemovedURIs() {
- final Set<URI> removed;
- internalLock.lock();
- try {
- removed = ImmutableSet.copyOf(removedURIs);
- removedURIs.clear();
- } finally {
- internalLock.unlock();
- }
- return removed;
- }
-
- /**
- * Visits a resource delta to collect the changed and removed files' URIs.
- *
- * @author <a href="mailto:laurent.goubet@obeo.fr">laurent Goubet</a>
- */
- private class ModelResourceDeltaVisitor implements IResourceDeltaVisitor {
- /**
- * {@inheritDoc}
- *
- * @see org.eclipse.core.resources.IResourceDeltaVisitor#visit(org.eclipse.core.resources.IResourceDelta)
- */
- public boolean visit(IResourceDelta delta) throws CoreException {
- /*
- * Note : the lock (#lock) must be acquired by the current thread _before_ calling #accept()
- * on this visitor.
- */
- if (delta.getFlags() == IResourceDelta.MARKERS
- || delta.getResource().getType() != IResource.FILE) {
- return true;
- }
-
- final IFile file = (IFile)delta.getResource();
- final URI fileURI = createURIFor(file);
- // We can't check the content type of a removed resource
- if (delta.getKind() == IResourceDelta.REMOVED) {
- removedURIs.add(fileURI);
- changedURIs.remove(fileURI);
- } else if (hasModelType(file)) {
- if ((delta.getKind() & IResourceDelta.CHANGED) != 0) {
- changedURIs.add(fileURI);
- // Probably can't happen, but let's stay on the safe side
- removedURIs.remove(fileURI);
- } else if ((delta.getKind() & IResourceDelta.ADDED) != 0) {
- // If a previously removed resource is changed, we can assume it's been re-added
- if (removedURIs.remove(fileURI)) {
- changedURIs.add(fileURI);
- }
- }
- }
-
- return true;
- }
- }
+ ModelsResolution comp = new ModelsResolution(context, monitor, storageAccessor, left, right, origin);
+ return comp.run();
}
}

Back to the top