[456699] Add Model Resolving Integration Extension Point

Adds an extension point to EMF Compare which allows to integrate special
model resolving behavior into EMF Compare. The extension point can be
used when dependencies between resources exist by design but do not
manifest in the resources themselves.

The extension point is used for integrating the Papyrus ModelSet
approach where the .di file is used as "master" file without actually
linking to other files.

Includes testcases.

Bug: 456699
Also-by: Philip Langer <planger@eclipsesource.com>
Signed-off-by: Stefan Dirix <sdirix@eclipsesource.com>
Change-Id: Ia0104462a5f2737c8890d92a9e94ab8675db2773
diff --git a/plugins/org.eclipse.emf.compare.diagram.ide.ui.papyrus/META-INF/MANIFEST.MF b/plugins/org.eclipse.emf.compare.diagram.ide.ui.papyrus/META-INF/MANIFEST.MF
index 7b3e312..8868bf0 100644
--- a/plugins/org.eclipse.emf.compare.diagram.ide.ui.papyrus/META-INF/MANIFEST.MF
+++ b/plugins/org.eclipse.emf.compare.diagram.ide.ui.papyrus/META-INF/MANIFEST.MF
@@ -17,7 +17,8 @@
  org.eclipse.papyrus.infra.core;bundle-version="0.9.1",
  org.eclipse.papyrus.infra.onefile;bundle-version="0.9.1",
  org.eclipse.papyrus.infra.gmfdiag.css;bundle-version="1.0.0",
- org.eclipse.papyrus.uml.tools;bundle-version="1.0.0"
+ org.eclipse.papyrus.uml.tools;bundle-version="1.0.0",
+ org.eclipse.papyrus.infra.emf;bundle-version="1.0.0"
 Bundle-RequiredExecutionEnvironment: J2SE-1.5
 Import-Package: com.google.common.collect;version="[11.0.0,16.0.0)"
 Export-Package: org.eclipse.emf.compare.diagram.ide.ui.papyrus.internal.logical.view,
diff --git a/plugins/org.eclipse.emf.compare.diagram.ide.ui.papyrus/plugin.xml b/plugins/org.eclipse.emf.compare.diagram.ide.ui.papyrus/plugin.xml
index 87b978b..616dc2e 100644
--- a/plugins/org.eclipse.emf.compare.diagram.ide.ui.papyrus/plugin.xml
+++ b/plugins/org.eclipse.emf.compare.diagram.ide.ui.papyrus/plugin.xml
@@ -2,7 +2,7 @@
 <?eclipse version="3.4"?>
 
 <!--
- Copyright (c) 2013, 2014 Obeo.
+ Copyright (c) 2013, 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
@@ -10,6 +10,7 @@
  
  Contributors:
      Obeo - initial API and implementation
+     Stefan Dirix - Bug 456699
 -->
 
 <plugin>
@@ -32,4 +33,10 @@
             ranking="20">
       </handler>
    </extension>
+   <extension
+         point="org.eclipse.emf.compare.ide.ui.modelDependencyProvider">
+      <dependency
+            class="org.eclipse.emf.compare.diagram.ide.ui.papyrus.dependency.PapyrusDependencyProvider">
+      </dependency>
+   </extension>
 </plugin>
diff --git a/plugins/org.eclipse.emf.compare.diagram.ide.ui.papyrus/src/org/eclipse/emf/compare/diagram/ide/ui/papyrus/dependency/PapyrusDependencyProvider.java b/plugins/org.eclipse.emf.compare.diagram.ide.ui.papyrus/src/org/eclipse/emf/compare/diagram/ide/ui/papyrus/dependency/PapyrusDependencyProvider.java
new file mode 100644
index 0000000..bc5cbdb
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.diagram.ide.ui.papyrus/src/org/eclipse/emf/compare/diagram/ide/ui/papyrus/dependency/PapyrusDependencyProvider.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * Copyright (c) 2015 EclipseSource Muenchen GmbH 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:
+ *     Stefan Dirix - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.diagram.ide.ui.papyrus.dependency;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.compare.diagram.ide.ui.papyrus.util.ModelExtensionUtil;
+import org.eclipse.emf.compare.ide.ui.dependency.IDependencyProvider;
+import org.eclipse.emf.ecore.resource.URIConverter;
+
+/**
+ * A client of the EMF Compare Dependency extension point providing a lightweight integration of the Papyrus
+ * ModelSet approach with the EMF Model Resolution Strategy.
+ * 
+ * @author Stefan Dirix <sdirix@eclipsesource.com>
+ */
+public class PapyrusDependencyProvider implements IDependencyProvider {
+
+	/**
+	 * File extensions registered in Papyrus.
+	 */
+	private List<String> fileExtensions;
+
+	/**
+	 * Constructs and initializes the PapyrusDependencyIdentifier.
+	 */
+	public PapyrusDependencyProvider() {
+		fileExtensions = new ArrayList<String>(ModelExtensionUtil.getRegisteredFileExtensions());
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	public boolean apply(URI uri) {
+		return fileExtensions.contains(uri.fileExtension());
+	}
+
+	/**
+	 * {@inheritDoc} Checks the Papyrus model extension point and tries to determine all dependencies from the
+	 * registered information.
+	 */
+	public Set<URI> getDependencies(URI uri) {
+		final Set<URI> dependencies = new LinkedHashSet<URI>();
+		for (String fileExtension : fileExtensions) {
+			URI dependencyURI = uri.trimFileExtension().appendFileExtension(fileExtension);
+			if (URIConverter.INSTANCE.exists(dependencyURI, null)) {
+				dependencies.add(dependencyURI);
+			}
+		}
+		return dependencies;
+	}
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/CompareTestCase.java b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/CompareTestCase.java
index 39e1f31..41c8064 100644
--- a/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/CompareTestCase.java
+++ b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/CompareTestCase.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (C) 2013, 2014 Obeo and others
+ * Copyright (C) 2013, 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
@@ -13,7 +13,11 @@
 import static org.eclipse.emf.ecore.util.EcoreUtil.getAllProperContents;
 import static org.junit.Assert.assertTrue;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.IOException;
+import java.nio.channels.FileChannel;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -260,4 +264,18 @@
 		}
 		project.getProject().refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor());
 	}
+
+	protected static void copyFile(File source, File dest) throws IOException {
+		FileChannel sourceChannel = null;
+		FileChannel destChannel = null;
+		FileInputStream fileInputStream = new FileInputStream(source);
+		sourceChannel = fileInputStream.getChannel();
+		FileOutputStream fileOutputStream = new FileOutputStream(dest);
+		destChannel = fileOutputStream.getChannel();
+		destChannel.transferFrom(sourceChannel, 0, sourceChannel.size());
+		sourceChannel.close();
+		destChannel.close();
+		fileInputStream.close();
+		fileOutputStream.close();
+	}
 }
diff --git a/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/resolver/ThreadedModelResolverWithCustomDependencyProviderTest.java b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/resolver/ThreadedModelResolverWithCustomDependencyProviderTest.java
new file mode 100644
index 0000000..97aabcd
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/resolver/ThreadedModelResolverWithCustomDependencyProviderTest.java
@@ -0,0 +1,312 @@
+/*******************************************************************************
+ * Copyright (c) 2015 EclipseSource Muenchen GmbH 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
+ *     Stefan Dirix - handle space in file url
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.tests.logical.resolver;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IStorage;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.FileLocator;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.compare.ide.ui.dependency.ModelDependencyProviderRegistry;
+import org.eclipse.emf.compare.ide.ui.internal.logical.resolver.ThreadedModelResolver;
+import org.eclipse.emf.compare.ide.ui.logical.SynchronizationModel;
+import org.eclipse.emf.compare.ide.ui.tests.CompareTestCase;
+import org.eclipse.emf.compare.ide.ui.tests.workspace.TestProject;
+import org.eclipse.emf.compare.ide.utils.StorageTraversal;
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+
+/**
+ * Tests the integration of the modelDependencyProvider extension in the {@link ThreadedModelResolver}.
+ * <p>
+ * The integration is tested by having a model set consisting of three files (file1.ecore, file2.ecore, and
+ * file3.ecore), which are -- for the sake of the test -- always specified as dependency by a dependency
+ * provider. We run the {@link ThreadedModelResolver} with and without this dependency provider and assert
+ * that the files aren't or are part of the resulting storage traversal that is resolved.
+ * </p>
+ * <p>
+ * More concretely, file2.ecore has a real dependency to file3.ecore, whereas file1.ecore has no dependency
+ * neither to file2.ecore nor file3.ecore. Thus, calling the {@link ThreadedModelResolver} with file1.ecore
+ * would not include file2.ecore and file3.ecore without a custom dependency provider. Therefore, we test the
+ * integration by adding a dependency provider which always adds file1.ecore and file2.ecore to the
+ * dependencies. As a result, calling the {@link ThreadedModelResolver} with the custom dependency provider
+ * with each of the files, file1.ecore, file2.ecore, or file3.ecore should always result into a storage
+ * traversal of all three files.
+ * </p>
+ * 
+ * @author Philip Langer <planger@eclipsesource.com>
+ */
+@SuppressWarnings({"nls", "restriction" })
+public class ThreadedModelResolverWithCustomDependencyProviderTest extends CompareTestCase {
+
+	private static final String TEST_BUNDLE = "org.eclipse.emf.compare.ide.ui.tests";
+
+	private static final String TEST_DATA_PATH = "src/org/eclipse/emf/compare/ide/ui/tests/logical/resolver/data/case1/";
+
+	private static final String MODEL_FILE1 = "file1.ecore";
+
+	private static final String MODEL_FILE2 = "file2.ecore";
+
+	private static final String MODEL_FILE3 = "file3.ecore";
+
+	private IProgressMonitor monitor = new NullProgressMonitor();
+
+	@Test
+	public void testResolveLocalModelFromFile1WithoutDependencyProvider() throws IOException, CoreException,
+			URISyntaxException, InterruptedException {
+		final ModelSet modelSet = createProjectWithModelSet();
+		final ModelDependencyProviderRegistry emptyRegistry = new ModelDependencyProviderRegistry();
+		final ThreadedModelResolver resolver = createModelResolver(emptyRegistry);
+
+		final StorageTraversal traversal = resolver.resolveLocalModel(modelSet.file1, monitor);
+
+		assertEquals(1, traversal.getStorages().size());
+		assertContainsFile(traversal, modelSet.file1);
+	}
+
+	@Test
+	public void testResolveLocalModelFromFile2WithoutDependencyProvider() throws IOException, CoreException,
+			URISyntaxException, InterruptedException {
+		final ModelSet modelSet = createProjectWithModelSet();
+		final ModelDependencyProviderRegistry emptyRegistry = new ModelDependencyProviderRegistry();
+		final ThreadedModelResolver resolver = createModelResolver(emptyRegistry);
+
+		final StorageTraversal traversal = resolver.resolveLocalModel(modelSet.file2, monitor);
+
+		assertEquals(2, traversal.getStorages().size());
+		assertContainsFile(traversal, modelSet.file2);
+		assertContainsFile(traversal, modelSet.file3);
+	}
+
+	@Test
+	public void testResolveLocalModelFromFile1WithDependencyProvider() throws IOException, CoreException,
+			URISyntaxException, InterruptedException {
+		final ModelSet modelSet = createProjectWithModelSet();
+		final ModelDependencyProviderRegistry registry = createRegistryWithCustomResolver();
+		final ThreadedModelResolver resolver = createModelResolver(registry);
+
+		final StorageTraversal traversal = resolver.resolveLocalModel(modelSet.file1, monitor);
+
+		assertContainsAllFiles(traversal, modelSet);
+	}
+
+	@Test
+	public void testResolveLocalModelFromFile2WithDependencyProvider() throws IOException, CoreException,
+			URISyntaxException, InterruptedException {
+		final ModelSet modelSet = createProjectWithModelSet();
+		final ModelDependencyProviderRegistry registry = createRegistryWithCustomResolver();
+		final ThreadedModelResolver resolver = createModelResolver(registry);
+
+		final StorageTraversal traversal = resolver.resolveLocalModel(modelSet.file2, monitor);
+
+		assertContainsAllFiles(traversal, modelSet);
+	}
+
+	@Test
+	public void testResolveLocalModelsTwoWayFromFile1WithoutDependencyProvider() throws IOException,
+			CoreException, URISyntaxException, InterruptedException {
+		final ModelSet leftModelSet = createProjectWithModelSet("Left");
+		final ModelSet rightModelSet = createProjectWithModelSet("Right");
+		final ModelDependencyProviderRegistry emptyRegistry = new ModelDependencyProviderRegistry();
+		final ThreadedModelResolver resolver = createModelResolver(emptyRegistry);
+
+		final SynchronizationModel synchronizationModel = resolver.resolveLocalModels(leftModelSet.file1,
+				rightModelSet.file1, null, monitor);
+
+		assertEquals(1, synchronizationModel.getLeftTraversal().getStorages().size());
+		assertContainsFile(synchronizationModel.getLeftTraversal(), leftModelSet.file1);
+		assertEquals(1, synchronizationModel.getRightTraversal().getStorages().size());
+		assertContainsFile(synchronizationModel.getRightTraversal(), rightModelSet.file1);
+		assertTrue(synchronizationModel.getOriginTraversal().getStorages().isEmpty());
+	}
+
+	@Test
+	public void testResolveLocalModelsThreeWayFromFile1WithoutDependencyProvider() throws IOException,
+			CoreException, URISyntaxException, InterruptedException {
+		final ModelSet leftModelSet = createProjectWithModelSet("Left");
+		final ModelSet rightModelSet = createProjectWithModelSet("Right");
+		final ModelSet originModelSet = createProjectWithModelSet("Origin");
+		final ModelDependencyProviderRegistry emptyRegistry = new ModelDependencyProviderRegistry();
+		final ThreadedModelResolver resolver = createModelResolver(emptyRegistry);
+
+		final SynchronizationModel synchronizationModel = resolver.resolveLocalModels(leftModelSet.file1,
+				rightModelSet.file1, originModelSet.file1, monitor);
+
+		assertEquals(1, synchronizationModel.getLeftTraversal().getStorages().size());
+		assertContainsFile(synchronizationModel.getLeftTraversal(), leftModelSet.file1);
+		assertEquals(1, synchronizationModel.getRightTraversal().getStorages().size());
+		assertContainsFile(synchronizationModel.getRightTraversal(), rightModelSet.file1);
+		assertEquals(1, synchronizationModel.getOriginTraversal().getStorages().size());
+		assertContainsFile(synchronizationModel.getOriginTraversal(), originModelSet.file1);
+	}
+
+	@Test
+	public void testResolveLocalModelsTwoWayFromFile1WithDependencyProvider() throws IOException,
+			CoreException, URISyntaxException, InterruptedException {
+		final ModelSet leftModelSet = createProjectWithModelSet("Left");
+		final ModelSet rightModelSet = createProjectWithModelSet("Right");
+		final ModelDependencyProviderRegistry registry = createRegistryWithCustomResolver();
+		final ThreadedModelResolver resolver = createModelResolver(registry);
+
+		final SynchronizationModel synchronizationModel = resolver.resolveLocalModels(leftModelSet.file1,
+				rightModelSet.file1, null, monitor);
+
+		assertContainsAllFiles(synchronizationModel.getLeftTraversal(), leftModelSet);
+		assertContainsAllFiles(synchronizationModel.getRightTraversal(), rightModelSet);
+		assertTrue(synchronizationModel.getOriginTraversal().getStorages().isEmpty());
+	}
+
+	@Test
+	public void testResolveLocalModelsThreeWayFromFile1WithDependencyProvider() throws IOException,
+			CoreException, URISyntaxException, InterruptedException {
+		final ModelSet leftModelSet = createProjectWithModelSet("Left");
+		final ModelSet rightModelSet = createProjectWithModelSet("Right");
+		final ModelSet originModelSet = createProjectWithModelSet("Origin");
+		final ModelDependencyProviderRegistry registry = createRegistryWithCustomResolver();
+		final ThreadedModelResolver resolver = createModelResolver(registry);
+
+		final SynchronizationModel synchronizationModel = resolver.resolveLocalModels(leftModelSet.file1,
+				rightModelSet.file1, originModelSet.file1, monitor);
+
+		assertContainsAllFiles(synchronizationModel.getLeftTraversal(), leftModelSet);
+		assertContainsAllFiles(synchronizationModel.getRightTraversal(), rightModelSet);
+		assertContainsAllFiles(synchronizationModel.getOriginTraversal(), originModelSet);
+	}
+
+	private void assertContainsAllFiles(StorageTraversal traversal, ModelSet modelSet) {
+		assertEquals(3, traversal.getStorages().size());
+		assertContainsFile(traversal, modelSet.file1);
+		assertContainsFile(traversal, modelSet.file3);
+		assertContainsFile(traversal, modelSet.file2);
+	}
+
+	private void assertContainsFile(StorageTraversal traversal, final IFile iFile) {
+		Iterables.any(traversal.getStorages(), containsFile(iFile));
+	}
+
+	private static Predicate<IStorage> containsFile(final IFile iFile) {
+		return new Predicate<IStorage>() {
+			public boolean apply(IStorage input) {
+				return iFile.equals(input.getFullPath());
+			}
+		};
+	}
+
+	private ThreadedModelResolver createModelResolver(final ModelDependencyProviderRegistry reg) {
+		ThreadedModelResolver resolver = new ThreadedModelResolver() {
+			@Override
+			protected ModelDependencyProviderRegistry getModelDependencyProviderRegistry() {
+				if (reg == null) {
+					return new ModelDependencyProviderRegistry();
+				} else {
+					return reg;
+				}
+			}
+		};
+		resolver.initialize();
+		return resolver;
+	}
+
+	/**
+	 * Creates a registry with a resolver that always adds two files of the model set (file1.ecore and
+	 * file2.ecore) to the dependencies. The third file, file3.ecore, will be resolved by the
+	 * {@link ThreadedModelResolver}, because file2.ecore has a dependency to file3.ecore.
+	 * 
+	 * @return The registry containing the fixed model set dependency provider.
+	 */
+	private ModelDependencyProviderRegistry createRegistryWithCustomResolver() {
+		final ModelDependencyProviderRegistry registry = new ModelDependencyProviderRegistry() {
+			@Override
+			public Set<URI> getDependencies(URI uri) {
+				final String uriString = uri.toPlatformString(false);
+				final String baseUriString = uriString.substring(0, uriString.lastIndexOf("/"));
+				final String file1UriString = baseUriString + "/" + MODEL_FILE1;
+				final String file2UriString = baseUriString + "/" + MODEL_FILE2;
+				final URI file1Uri = URI.createPlatformResourceURI(file1UriString, false);
+				final URI file2Uri = URI.createPlatformResourceURI(file2UriString, false);
+				final LinkedHashSet<URI> dependencies = Sets.newLinkedHashSet();
+				dependencies.add(file1Uri);
+				dependencies.add(file2Uri);
+				return dependencies;
+			}
+		};
+		return registry;
+	}
+
+	private ModelSet createProjectWithModelSet() throws CoreException, IOException, URISyntaxException {
+		return createProjectWithModelSet("Project");
+	}
+
+	private ModelSet createProjectWithModelSet(String name) throws CoreException, IOException,
+			URISyntaxException {
+		final IProject iProject = new TestProject(name).getProject();
+		final File file1 = project.getOrCreateFile(iProject, MODEL_FILE1);
+		final File file2 = project.getOrCreateFile(iProject, MODEL_FILE2);
+		final File file3 = project.getOrCreateFile(iProject, MODEL_FILE3);
+		final Bundle bundle = Platform.getBundle(TEST_BUNDLE);
+		final URI file1Uri = getFileUri(bundle.getEntry(TEST_DATA_PATH + MODEL_FILE1));
+		final URI file2Uri = getFileUri(bundle.getEntry(TEST_DATA_PATH + MODEL_FILE2));
+		final URI file3Uri = getFileUri(bundle.getEntry(TEST_DATA_PATH + MODEL_FILE3));
+
+		copyFile(toFile(file1Uri), file1);
+		copyFile(toFile(file2Uri), file2);
+		copyFile(toFile(file3Uri), file3);
+
+		final IFile iFile1 = project.getIFile(iProject, file1);
+		final IFile iFile2 = project.getIFile(iProject, file2);
+		final IFile iFile3 = project.getIFile(iProject, file3);
+
+		return new ModelSet(iFile1, iFile2, iFile3);
+	}
+
+	private URI getFileUri(final URL bundleUrl) throws IOException {
+		URL fileLocation = FileLocator.toFileURL(bundleUrl);
+		return URI.createFileURI(fileLocation.getPath());
+	}
+
+	private File toFile(final URI fileUri) throws URISyntaxException {
+		return new File(fileUri.toFileString());
+	}
+
+	private class ModelSet {
+		protected IFile file1;
+
+		protected IFile file2;
+
+		protected IFile file3;
+
+		public ModelSet(IFile file1, IFile file2, IFile file3) {
+			this.file1 = file1;
+			this.file2 = file2;
+			this.file3 = file3;
+		}
+	}
+
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/resolver/data/case1/file1.ecore b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/resolver/data/case1/file1.ecore
new file mode 100644
index 0000000..959ad8a
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/resolver/data/case1/file1.ecore
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ecore:EPackage xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore"
+    xmi:id="_A" name="File1_A"/>
diff --git a/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/resolver/data/case1/file2.ecore b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/resolver/data/case1/file2.ecore
new file mode 100644
index 0000000..531c773
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/resolver/data/case1/file2.ecore
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ecore:EPackage xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore"
+    xmi:id="_B" name="File2_B">
+  <eSubpackages href="file3.ecore#_C"/>
+</ecore:EPackage>
diff --git a/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/resolver/data/case1/file3.ecore b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/resolver/data/case1/file3.ecore
new file mode 100644
index 0000000..07e473e
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/resolver/data/case1/file3.ecore
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ecore:EPackage xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore" xmi:id="_C" name="File3_C">
+</ecore:EPackage>
diff --git a/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/suite/AllTests.java b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/suite/AllTests.java
index 4bb2eaa..b15d999 100644
--- a/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/suite/AllTests.java
+++ b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/suite/AllTests.java
@@ -17,6 +17,7 @@
 import org.eclipse.emf.compare.ComparePackage;
 import org.eclipse.emf.compare.ide.ui.tests.contentmergeviewer.notloadedfragment.NotLoadedFragmentItemTest;
 import org.eclipse.emf.compare.ide.ui.tests.logical.resolver.ThreadedModelResolverGraphTest;
+import org.eclipse.emf.compare.ide.ui.tests.logical.resolver.ThreadedModelResolverWithCustomDependencyProviderTest;
 import org.eclipse.emf.compare.ide.ui.tests.structuremergeviewer.NavigatableTest;
 import org.eclipse.emf.compare.ide.ui.tests.structuremergeviewer.actions.MergeActionTest;
 import org.eclipse.emf.compare.ide.ui.tests.structuremergeviewer.actions.PseudoConflictsMergeActionTest;
@@ -34,7 +35,8 @@
 @RunWith(Suite.class)
 @SuiteClasses({DependenciesTest.class, MergeActionTest.class, PseudoConflictsMergeActionTest.class,
 		BugsTestSuite.class, NavigatableTest.class, NotLoadedFragmentNodeTest.class,
-		NotLoadedFragmentItemTest.class, ThreadedModelResolverGraphTest.class })
+		NotLoadedFragmentItemTest.class, ThreadedModelResolverGraphTest.class,
+		ThreadedModelResolverWithCustomDependencyProviderTest.class })
 public class AllTests {
 	/**
 	 * Launches the test with the given arguments.
diff --git a/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/unit/TestBug459131.java b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/unit/TestBug459131.java
index 66ace37..66a7d7d 100644
--- a/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/unit/TestBug459131.java
+++ b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/unit/TestBug459131.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2015 Obeo.
+ * 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
@@ -7,6 +7,7 @@
  * 
  * Contributors:
  *     Obeo - initial API and implementation
+ *     Philip Langer - refactorings
  *******************************************************************************/
 package org.eclipse.emf.compare.ide.ui.tests.unit;
 
@@ -14,11 +15,7 @@
 import static org.junit.Assert.assertTrue;
 
 import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
 import java.net.URL;
-import java.nio.channels.FileChannel;
 
 import org.eclipse.core.resources.IFile;
 import org.eclipse.core.resources.IProject;
@@ -147,18 +144,4 @@
 		assertTrue(rightTraversal.getStorages().contains(iRightFile3));
 
 	}
-
-	private static void copyFile(File source, File dest) throws IOException {
-		FileChannel sourceChannel = null;
-		FileChannel destChannel = null;
-		FileInputStream fileInputStream = new FileInputStream(source);
-		sourceChannel = fileInputStream.getChannel();
-		FileOutputStream fileOutputStream = new FileOutputStream(dest);
-		destChannel = fileOutputStream.getChannel();
-		destChannel.transferFrom(sourceChannel, 0, sourceChannel.size());
-		sourceChannel.close();
-		destChannel.close();
-		fileInputStream.close();
-		fileOutputStream.close();
-	}
 }
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/META-INF/MANIFEST.MF b/plugins/org.eclipse.emf.compare.ide.ui/META-INF/MANIFEST.MF
index 95a8837..2ab9c35 100644
--- a/plugins/org.eclipse.emf.compare.ide.ui/META-INF/MANIFEST.MF
+++ b/plugins/org.eclipse.emf.compare.ide.ui/META-INF/MANIFEST.MF
@@ -24,7 +24,8 @@
 Bundle-RequiredExecutionEnvironment: J2SE-1.5
 Bundle-ActivationPolicy: lazy
 Bundle-Localization: plugin
-Export-Package: org.eclipse.emf.compare.ide.ui.internal;x-internal:=true,
+Export-Package: org.eclipse.emf.compare.ide.ui.dependency;x-friends:="org.eclipse.emf.compare.diagram.ide.ui.papyrus",
+ org.eclipse.emf.compare.ide.ui.internal;x-internal:=true,
  org.eclipse.emf.compare.ide.ui.internal.configuration;x-internal:=true,
  org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer;x-internal:=true,
  org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.accessor;x-internal:=true,
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/plugin.xml b/plugins/org.eclipse.emf.compare.ide.ui/plugin.xml
index 1e01b9a..43548b8 100644
--- a/plugins/org.eclipse.emf.compare.ide.ui/plugin.xml
+++ b/plugins/org.eclipse.emf.compare.ide.ui/plugin.xml
@@ -2,7 +2,7 @@
 <?eclipse version="3.4"?>
 
 <!--
- Copyright (c) 2012, 2014 Obeo.
+ Copyright (c) 2012, 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
@@ -10,11 +10,13 @@
  
  Contributors:
      Obeo - initial API and implementation
+     Stefan Dirix - Bug 456699
 -->
 
 <plugin>
    <extension-point id="modelResolvers" name="Model Resolvers" schema="schema/modelResolvers.exsd"/>
    <extension-point id="logicalModelViewHandlers" name="Logical Model View Handlers" schema="schema/logicalModelViewHandlers.exsd"/>
+   <extension-point id="modelDependencyProvider" name="Model Dependency Provider" schema="schema/modelDependencyProvider.exsd"/>
    
    <extension
          point="org.eclipse.compare.structureMergeViewers">
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/schema/modelDependencyProvider.exsd b/plugins/org.eclipse.emf.compare.ide.ui/schema/modelDependencyProvider.exsd
new file mode 100644
index 0000000..323f2f8
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/schema/modelDependencyProvider.exsd
@@ -0,0 +1,119 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!-- Schema file written by PDE -->
+<schema targetNamespace="org.eclipse.emf.compare.ide.ui" xmlns="http://www.w3.org/2001/XMLSchema">
+<annotation>
+      <appinfo>
+         <meta.schema plugin="org.eclipse.emf.compare.ide.ui" id="modelDependencyProvider" name="Model Dependency Provider"/>
+      </appinfo>
+      <documentation>
+         This extension point allows to add custom dependencies for model resources to the default model dependency resolution.
+
+This can be used when the default model resolution of EMF Compare, that is following incoming and outgoing references, is insufficient for certain model types. Thus, with this extension point, clients may add custom dependencies to be considered during the model resolution of certain model resources, which otherwise would not have been detected as being dependent.
+      </documentation>
+   </annotation>
+
+   <element name="extension">
+      <annotation>
+         <appinfo>
+            <meta.element />
+         </appinfo>
+      </annotation>
+      <complexType>
+         <sequence>
+            <element ref="dependency" minOccurs="0" maxOccurs="unbounded"/>
+         </sequence>
+         <attribute name="point" type="string" use="required">
+            <annotation>
+               <documentation>
+
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="id" type="string">
+            <annotation>
+               <documentation>
+
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="name" type="string">
+            <annotation>
+               <documentation>
+
+               </documentation>
+               <appinfo>
+                  <meta.attribute translatable="true"/>
+               </appinfo>
+            </annotation>
+         </attribute>
+      </complexType>
+   </element>
+
+   <element name="dependency">
+      <annotation>
+         <documentation>
+            This element allows to specify additional dependencies of the given URI. This can be used when dependencies exist by design but do not manifest in the files themselves.
+         </documentation>
+      </annotation>
+      <complexType>
+         <attribute name="class" type="string" use="required">
+            <annotation>
+               <documentation>
+                  The fully qualified name of a class that implements org.eclipse.emf.compare.ide.ui.dependency.IDependencyProvider
+               </documentation>
+               <appinfo>
+                  <meta.attribute kind="java" basedOn=":org.eclipse.emf.compare.ide.ui.dependency.IDependencyProvider"/>
+               </appinfo>
+            </annotation>
+         </attribute>
+      </complexType>
+   </element>
+
+   <annotation>
+      <appinfo>
+         <meta.section type="since"/>
+      </appinfo>
+      <documentation>
+         4.1
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appinfo>
+         <meta.section type="examples"/>
+      </appinfo>
+      <documentation>
+         An example for adding a model dependency provider through this extension point can be found in org.eclipse.emf.compare.diagram.ide.ui.papyrus/plugin.xml.
+
+&lt;extension point=&quot;org.eclipse.emf.compare.ide.ui.modelDependencyProvider&quot;&gt;
+   &lt;dependency
+         class=&quot;org.eclipse.emf.compare.diagram.ide.ui.papyrus.dependency.PapyrusDependencyProvider&quot;&gt;
+   &lt;/dependency&gt;
+&lt;/extension&gt;
+      </documentation>
+   </annotation>
+
+
+   <annotation>
+      <appinfo>
+         <meta.section type="implementation"/>
+      </appinfo>
+      <documentation>
+         See org.eclipse.emf.compare.diagram.ide.ui.papyrus/plugin.xml for an existing implementation.
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appinfo>
+         <meta.section type="copyright"/>
+      </appinfo>
+      <documentation>
+         Copyright (c) 2015 EclipseSource Muenchen GmbH 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
+      </documentation>
+   </annotation>
+
+</schema>
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/dependency/DependencyProviderDescriptor.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/dependency/DependencyProviderDescriptor.java
new file mode 100644
index 0000000..e759213
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/dependency/DependencyProviderDescriptor.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * Copyright (c) 2015 EclipseSource Muenchen GmbH 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:
+ *     Stefan Dirix - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.dependency;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIMessages;
+import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIPlugin;
+
+/**
+ * This class is used for information flow between {@link ModelDependencyProviderRegistryListener} and
+ * {@link ModelDependencyProviderRegistry} and managing the creation of {@link IDependencyProvider} instances.
+ * 
+ * @author Stefan Dirix <sdirix@eclipsesource.com>
+ */
+public class DependencyProviderDescriptor {
+
+	/** Underlying {@link IConfigurationElement} describing this dependency provider. */
+	private final IConfigurationElement configurationElement;
+
+	/**
+	 * Name of the configuration property that can be used to retrieve the qualified class name of this
+	 * dependency provider.
+	 */
+	private final String attributeClassName;
+
+	/** Don't log the same error multiple times. */
+	private boolean logOnce;
+
+	/**
+	 * Default constructor.
+	 * 
+	 * @param attributeName
+	 *            The name of the configuration attribute responsible for the registered
+	 *            {@link IDependencyProvider}.
+	 * @param configurationElement
+	 *            The {@link IConfigurationElement} containing all relevant extension information.
+	 */
+	public DependencyProviderDescriptor(String attributeName, IConfigurationElement configurationElement) {
+		this.attributeClassName = attributeName;
+		this.configurationElement = configurationElement;
+	}
+
+	/**
+	 * Returns the {@link IDependencyProvider}.
+	 * 
+	 * @return The newly created {@link IDependencyProvider}.
+	 */
+	public IDependencyProvider getDependencyProvider() {
+		try {
+			final IDependencyProvider provider = (IDependencyProvider)configurationElement
+					.createExecutableExtension(attributeClassName);
+			return provider;
+		} catch (CoreException e) {
+			if (!logOnce) {
+				logOnce = true;
+				final String className = configurationElement.getAttribute(attributeClassName);
+				final String message = EMFCompareIDEUIMessages.getString(
+						"ModelDependencyProviderRegistry.invalidModelDependency", className); //$NON-NLS-1$
+				final IStatus status = new Status(IStatus.ERROR, configurationElement.getDeclaringExtension()
+						.getContributor().getName(), message, e);
+				EMFCompareIDEUIPlugin.getDefault().getLog().log(status);
+			}
+		}
+		return null;
+	}
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/dependency/IDependencyProvider.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/dependency/IDependencyProvider.java
new file mode 100644
index 0000000..f1e71b0
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/dependency/IDependencyProvider.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (c) 2015 EclipseSource Muenchen GmbH 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:
+ *     Stefan Dirix - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.dependency;
+
+import com.google.common.annotations.Beta;
+
+import java.util.Set;
+
+import org.eclipse.emf.common.util.URI;
+
+/**
+ * Contract for clients of the org.eclipse.emf.ecompare.ide.ui.modelDependencies extension point.
+ * 
+ * @author Stefan Dirix <sdirix@eclipsesource.com>
+ * @since 4.1
+ */
+@Beta
+public interface IDependencyProvider {
+
+	/**
+	 * Specifies whether this {@link IDependencyProvider} can determine dependencies of the given {@code uri}.
+	 * 
+	 * @param uri
+	 *            The {@link URI} for which additional dependencies may be determined.
+	 * @return {@code true} if the {@link IDependencyProvider} can provide dependencies for the given
+	 *         {@code uri}, {@code false} otherwise.
+	 */
+	boolean apply(URI uri);
+
+	/**
+	 * Determines the dependencies of the given {@code uri}.
+	 * 
+	 * @param uri
+	 *            The {@link URI} for which additional dependencies may be determined.
+	 * @return The set of dependencies of the given {@code uri}. If no dependency is determined an empty set
+	 *         is returned.
+	 */
+	Set<URI> getDependencies(URI uri);
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/dependency/ModelDependencyProviderRegistry.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/dependency/ModelDependencyProviderRegistry.java
new file mode 100644
index 0000000..c553649
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/dependency/ModelDependencyProviderRegistry.java
@@ -0,0 +1,96 @@
+/*******************************************************************************
+ * Copyright (c) 2015 EclipseSource Muenchen GmbH 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:
+ *     Stefan Dirix - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.dependency;
+
+import static com.google.common.base.Predicates.notNull;
+import static com.google.common.collect.Iterables.addAll;
+
+import com.google.common.collect.Iterables;
+
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.emf.common.util.URI;
+
+/**
+ * The registry managing the registered dependency extension point information.
+ * 
+ * @author Stefan Dirix <sdirix@eclipsesource.com>
+ * @since 4.1
+ */
+public class ModelDependencyProviderRegistry {
+
+	/** Keeps track of the extensions providing model resolvers. */
+	private final Map<String, DependencyProviderDescriptor> registeredDescriptors;
+
+	/**
+	 * Constructs and initialized this registry.
+	 */
+	public ModelDependencyProviderRegistry() {
+		registeredDescriptors = new LinkedHashMap<String, DependencyProviderDescriptor>();
+	}
+
+	/**
+	 * Returns the set of all {@link URI URIs} that are determined as a dependency by the registered
+	 * dependency providers. If multiple providers declare dependencies the results are combined.
+	 * 
+	 * @param uri
+	 *            The {@link URI} for which the dependencies are to be determined.
+	 * @return The set of dependencies of {@code uri}. If {@code uri} has no dependency, the returned set is
+	 *         empty.
+	 */
+	public Set<URI> getDependencies(URI uri) {
+		final Set<URI> uris = new LinkedHashSet<URI>();
+		for (DependencyProviderDescriptor descriptor : registeredDescriptors.values()) {
+			IDependencyProvider provider = descriptor.getDependencyProvider();
+			if (provider != null && provider.apply(uri)) {
+				Collection<URI> dependencies = provider.getDependencies(uri);
+				if (dependencies != null) {
+					addAll(uris, Iterables.filter(dependencies, notNull()));
+				}
+			}
+		}
+		return uris;
+	}
+
+	/**
+	 * Adds the given {@link DependencyProviderDescriptor} to this registry, using the given {@code className}
+	 * as the identifier.
+	 * 
+	 * @param className
+	 *            The identifier for the given {@link DependencyProviderDescriptor}.
+	 * @param descriptor
+	 *            The {@link DependencyProviderDescriptor} which is to be added to this registry.
+	 */
+	public void addProvider(String className, DependencyProviderDescriptor descriptor) {
+		registeredDescriptors.put(className, descriptor);
+	}
+
+	/**
+	 * Removes the {@link DependencyProviderDescriptor} and its managed {@link IDependencyProvider} identified
+	 * by the given {@code className} from this registry.
+	 * 
+	 * @param className
+	 *            Identifier of the provider we are to remove from this registry.
+	 * @return The removed {@link DependencyProviderDescriptor}, if any.
+	 */
+	public DependencyProviderDescriptor removeProvider(String className) {
+		return registeredDescriptors.remove(className);
+	}
+
+	/** Clears out all registered providers from this registry. */
+	public void clear() {
+		registeredDescriptors.clear();
+	}
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/dependency/ModelDependencyProviderRegistryListener.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/dependency/ModelDependencyProviderRegistryListener.java
new file mode 100644
index 0000000..b00275e
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/dependency/ModelDependencyProviderRegistryListener.java
@@ -0,0 +1,92 @@
+/*******************************************************************************
+ * Copyright (c) 2015 EclipseSource Muenchen GmbH 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:
+ *     Stefan Dirix - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.dependency;
+
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.ILog;
+import org.eclipse.emf.compare.rcp.extension.AbstractRegistryEventListener;
+
+/**
+ * This listener will react to changes against the model dependency extension point, allowing us to be in sync
+ * with plugin activation and deactivation.
+ * 
+ * @author Stefan Dirix <sdirix@eclipsesource.com>
+ */
+public class ModelDependencyProviderRegistryListener extends AbstractRegistryEventListener {
+
+	/**
+	 * The name of the dependency element.
+	 */
+	private static final String DEPENDENCY_ELEMENT_NAME = "dependency"; //$NON-NLS-1$
+
+	/**
+	 * The name of the class attribute of the dependency element.
+	 */
+	private static final String ATTRIBUTE_CLASS = "class"; //$NON-NLS-1$
+
+	/**
+	 * The registry which will actually hold all information.
+	 */
+	private final ModelDependencyProviderRegistry registry;
+
+	/**
+	 * Initialize a registry event listener for our handlers.
+	 * 
+	 * @param pluginID
+	 *            ID of the plugin contributing the extension point to monitor.
+	 * @param extensionPointID
+	 *            Actual id of the extension point to monitor.
+	 * @param log
+	 *            Log in which errors/warning should be logged.
+	 * @param registry
+	 *            The actual store of handlers this registry will alter.
+	 */
+	public ModelDependencyProviderRegistryListener(String pluginID, String extensionPointID, ILog log,
+			ModelDependencyProviderRegistry registry) {
+		super(pluginID, extensionPointID, log);
+		this.registry = registry;
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
+	protected boolean validateExtensionElement(IConfigurationElement element) {
+		if (DEPENDENCY_ELEMENT_NAME.equals(element.getName())) {
+			final String className = element.getAttribute(ATTRIBUTE_CLASS);
+			return className != null && className.trim().length() > 0;
+		}
+		return false;
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
+	protected boolean addedValid(IConfigurationElement element) {
+		final String className = element.getAttribute(ATTRIBUTE_CLASS);
+		final DependencyProviderDescriptor descriptor = new DependencyProviderDescriptor(ATTRIBUTE_CLASS,
+				element);
+		registry.addProvider(className, descriptor);
+		return true;
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
+	protected boolean removedValid(IConfigurationElement element) {
+		final String className = element.getAttribute(ATTRIBUTE_CLASS);
+		registry.removeProvider(className);
+		return true;
+	}
+
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/EMFCompareIDEUIPlugin.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/EMFCompareIDEUIPlugin.java
index 2fc7a17..9d25158 100644
--- a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/EMFCompareIDEUIPlugin.java
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/EMFCompareIDEUIPlugin.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2012, 2014 Obeo.
+ * Copyright (c) 2012, 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
@@ -7,6 +7,7 @@
  * 
  * Contributors:
  *     Obeo - initial API and implementation
+ *     Stefan Dirix - Bug 456699
  *******************************************************************************/
 package org.eclipse.emf.compare.ide.ui.internal;
 
@@ -19,6 +20,8 @@
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.core.runtime.Status;
+import org.eclipse.emf.compare.ide.ui.dependency.ModelDependencyProviderRegistry;
+import org.eclipse.emf.compare.ide.ui.dependency.ModelDependencyProviderRegistryListener;
 import org.eclipse.emf.compare.ide.ui.internal.editor.PropertySheetAdapterFactory;
 import org.eclipse.emf.compare.ide.ui.internal.logical.resolver.registry.ModelResolverRegistry;
 import org.eclipse.emf.compare.ide.ui.internal.logical.resolver.registry.ModelResolverRegistryListener;
@@ -48,6 +51,12 @@
 	/** Logical Model Editors Handlers extension point. */
 	private static final String LOGICAL_MODEL_VIEW_HANDLERS_PPID = "logicalModelViewHandlers"; //$NON-NLS-1$
 
+	/** Model dependency providers extension point. */
+	private static final String MODEL_DEPENDENCY_PROVIDER_PPID = "modelDependencyProvider"; //$NON-NLS-1$
+
+	/** keep track of resources that should be freed when exiting. */
+	private static Map<String, Image> resourcesMapper = new HashMap<String, Image>();
+
 	/** Listener for the model resolver extension point. */
 	private AbstractRegistryEventListener modelResolverRegistryListener;
 
@@ -60,8 +69,11 @@
 	/** Registry of Logical Model View Handlers. */
 	private LogicalModelViewHandlerRegistry logicalModelViewHandlerRegistry;
 
-	/** keep track of resources that should be freed when exiting. */
-	private static Map<String, Image> resourcesMapper = new HashMap<String, Image>();
+	/** Listener for the model dependency provider extension point. */
+	private AbstractRegistryEventListener modelDependencyProviderRegistryListener;
+
+	/** Registry of model dependency providers. */
+	private ModelDependencyProviderRegistry modelDependencyProviderRegistry;
 
 	/** Default constructor. */
 	public EMFCompareIDEUIPlugin() {
@@ -78,14 +90,22 @@
 		super.start(context);
 		plugin = this;
 
-		final IExtensionRegistry globalRegistry = Platform.getExtensionRegistry();
+		modelDependencyProviderRegistry = new ModelDependencyProviderRegistry();
 		modelResolverRegistry = new ModelResolverRegistry();
+		logicalModelViewHandlerRegistry = new LogicalModelViewHandlerRegistry();
+
+		final IExtensionRegistry globalRegistry = Platform.getExtensionRegistry();
+
+		modelDependencyProviderRegistryListener = new ModelDependencyProviderRegistryListener(PLUGIN_ID,
+				MODEL_DEPENDENCY_PROVIDER_PPID, getLog(), modelDependencyProviderRegistry);
+		globalRegistry.addListener(modelDependencyProviderRegistryListener);
+		modelDependencyProviderRegistryListener.readRegistry(globalRegistry);
+
 		modelResolverRegistryListener = new ModelResolverRegistryListener(PLUGIN_ID, MODEL_RESOLVER_PPID,
 				getLog(), modelResolverRegistry);
 		globalRegistry.addListener(modelResolverRegistryListener);
 		modelResolverRegistryListener.readRegistry(globalRegistry);
 
-		logicalModelViewHandlerRegistry = new LogicalModelViewHandlerRegistry();
 		logicalModelViewHandlerRegistryListener = new LogicalModelViewHandlerRegistryListener(PLUGIN_ID,
 				LOGICAL_MODEL_VIEW_HANDLERS_PPID, getLog(), logicalModelViewHandlerRegistry);
 		globalRegistry.addListener(logicalModelViewHandlerRegistryListener);
@@ -106,6 +126,8 @@
 		logicalModelViewHandlerRegistry.clear();
 		globalRegistry.removeListener(modelResolverRegistryListener);
 		modelResolverRegistry.clear();
+		globalRegistry.removeListener(modelDependencyProviderRegistryListener);
+		modelDependencyProviderRegistry.clear();
 		plugin = null;
 		super.stop(context);
 	}
@@ -194,6 +216,15 @@
 	}
 
 	/**
+	 * Returns the registry containing all known dependency providers.
+	 * 
+	 * @return The registry containing all known dependency providers.
+	 */
+	public ModelDependencyProviderRegistry getModelDependencyProviderRegistry() {
+		return modelDependencyProviderRegistry;
+	}
+
+	/**
 	 * Log an {@link Exception} in the {@link #getLog() current logger}.
 	 * 
 	 * @param e
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/ide_ui_messages.properties b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/ide_ui_messages.properties
index 3545620..b506e6d 100644
--- a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/ide_ui_messages.properties
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/ide_ui_messages.properties
@@ -8,6 +8,7 @@
 # Contributors:
 #     Obeo - initial API and implementation
 #     Philip Langer - log entries for model merge
+#     Stefan Dirix - Bug 456699
 ################################################################################
 ## ! note ! double the apostrophes if you need one in the printed String
 EMFSynchronizationModel.resolving = Creating EMF Synchronization Model
@@ -18,6 +19,8 @@
 ModelResolverRegistry.invalidResolver = Model resolver ''{0}'' could not be instantiated.
 ModelResolverRegistry.invalidRanking = Ranking of resolver ''{0}'' was not a valid integer : ''{1}''.
 
+ModelDependencyProviderRegistry.invalidModelDependency = Dependency provider ''{0}'' could not be instantiated.
+
 ModelResolver.coherenceWarning = Some models were accessible from multiple compared files and have been removed from the scope. This could break their references to the compared models as a result of merge operations.
 
 resource.not.serializable = Resource is not serializable.
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 fd2c64b..91803bd 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,6 +8,7 @@
  * 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;
 
@@ -63,6 +64,7 @@
 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;
@@ -203,6 +205,11 @@
 	 */
 	private ModelResourceListener resourceListener;
 
+	/**
+	 * The manager for the dependency extension point.
+	 */
+	private ModelDependencyProviderRegistry dependencyProviderRegistry;
+
 	/** Default constructor. */
 	public ThreadedModelResolver() {
 		this.dependencyGraph = new Graph<URI>();
@@ -211,6 +218,16 @@
 		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();
 	}
 
 	/**
@@ -326,6 +343,7 @@
 		if (!(start instanceof IFile)) {
 			return new StorageTraversal(new LinkedHashSet<IStorage>());
 		}
+		IFile file = (IFile)start;
 
 		ThreadSafeProgressMonitor subMonitor = null;
 		lock.lockInterruptibly();
@@ -340,7 +358,7 @@
 			if (getResolutionScope() != CrossReferenceResolutionScope.SELF) {
 				final SynchronizedResourceSet resourceSet = new SynchronizedResourceSet(
 						new MonitoredProxyCreationListener(subMonitor, false));
-				updateDependencies(resourceSet, (IFile)start, subMonitor);
+				updateDependencies(resourceSet, file, subMonitor);
 				updateChangedResources(resourceSet, subMonitor);
 			}
 
@@ -352,7 +370,7 @@
 				throw new OperationCanceledException();
 			}
 
-			final Set<IStorage> traversalSet = resolveTraversal((IFile)start, Collections.<URI> emptySet());
+			final Set<IStorage> traversalSet = resolveTraversal(file, Collections.<URI> emptySet());
 			StorageTraversal traversal = new StorageTraversal(traversalSet, diagnostic);
 
 			return traversal;
@@ -798,7 +816,9 @@
 			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
+			// 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);
 		}
@@ -1086,13 +1106,21 @@
 	}
 
 	private Set<IStorage> resolveTraversal(IFile file, Set<URI> bounds) {
-		final Set<IStorage> traversal = new LinkedHashSet<IStorage>();
-
-		final Iterable<URI> dependencies = getDependenciesOf(file, bounds);
-		for (URI uri : dependencies) {
-			traversal.add(getFileAt(uri));
+		final URI baseUri = createURIFor(file);
+		final Set<IStorage> traversalSet = new LinkedHashSet<IStorage>();
+		for (URI uri : getUriAndDependentUrisFromDependencyProvider(baseUri)) {
+			final IFile toResolve = getFileAt(uri);
+			final Iterable<URI> dependencies = getDependenciesOf(toResolve, bounds);
+			for (URI dep : dependencies) {
+				traversalSet.add(getFileAt(dep));
+			}
 		}
-		return traversal;
+		return traversalSet;
+	}
+
+	private Set<URI> getUriAndDependentUrisFromDependencyProvider(URI uri) {
+		final Set<URI> dependencies = dependencyProviderRegistry.getDependencies(uri);
+		return Sets.union(Collections.singleton(uri), dependencies);
 	}
 
 	private Set<IStorage> resolveRemoteTraversal(IStorageProviderAccessor storageAccessor, IStorage start,
@@ -1264,13 +1292,13 @@
 
 		lock.lock();
 		try {
-			if (resolvedResources.add(uri) && currentlyResolving.add(uri)) {
-				// 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 = resolvingPool.submit(new ResourceResolver(resourceSet, uri,
-						monitor));
-				Futures.addCallback(future, new ResolvingFutureCallback(monitor, uri));
+			monitor.setWorkRemaining(1000);
+			for (URI currentUri : getUriAndDependentUrisFromDependencyProvider(uri)) {
+				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();
@@ -1301,13 +1329,13 @@
 
 		lock.lock();
 		try {
-			if (resolvedResources.add(uri) && currentlyResolving.add(uri)) {
-				// 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 = resolvingPool.submit(new RemoteResourceResolver(resourceSet,
-						uri, monitor));
-				Futures.addCallback(future, new ResolvingFutureCallback(monitor, uri));
+			monitor.setWorkRemaining(1000);
+			for (URI currentUri : getUriAndDependentUrisFromDependencyProvider(uri)) {
+				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();