diff options
author | Stefan Dirix | 2015-05-13 09:27:15 +0000 |
---|---|---|
committer | Stefan Dirix | 2015-05-21 13:52:00 +0000 |
commit | d8b87cecb1a4c85496d780c2b350e0ec938488c2 (patch) | |
tree | 72949695aefaccf40060b2e4dc5c5b2f3fdc0fe5 /plugins | |
parent | 53aaaadcc4b23a5520ff32c3a6627be4ea726ed8 (diff) | |
download | org.eclipse.emf.compare-d8b87cecb1a4c85496d780c2b350e0ec938488c2.tar.gz org.eclipse.emf.compare-d8b87cecb1a4c85496d780c2b350e0ec938488c2.tar.xz org.eclipse.emf.compare-d8b87cecb1a4c85496d780c2b350e0ec938488c2.zip |
[466607] Enhance EMFModelProvider to consider multiple resources at once
The EMFModelProvider now overrides the getMappings(Resource[] ...)
method to allow for optimized results.
Depending on the EMF Compare resolution settings the logical model can
not be computed as a whole when only looking at single resources. The
getMappings(Resource[] ...) method now uses the already available
multi-resource input to combine overlapping logical models to a single
model.
Since the EMFModelProvider is for example used during merging, the
optimized results avoid redundant comparisons and mergings and therefore
save time and computation power.
Includes testcases.
Bug: 466607
Signed-off-by: Stefan Dirix <sdirix@eclipsesource.com>
Change-Id: Id928bc1a05e353bf2b721243c264d09de07eebc3
Diffstat (limited to 'plugins')
13 files changed, 815 insertions, 44 deletions
diff --git a/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/modelprovider/EMFModelProviderTest.java b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/modelprovider/EMFModelProviderTest.java new file mode 100644 index 000000000..ae9d08354 --- /dev/null +++ b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/modelprovider/EMFModelProviderTest.java @@ -0,0 +1,523 @@ +/******************************************************************************* + * 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.tests.logical.modelprovider; + +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IStorage; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.resources.mapping.IModelProviderDescriptor; +import org.eclipse.core.resources.mapping.ModelProvider; +import org.eclipse.core.resources.mapping.RemoteResourceMappingContext; +import org.eclipse.core.resources.mapping.ResourceMapping; +import org.eclipse.core.resources.mapping.ResourceTraversal; +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.internal.EMFCompareIDEUIPlugin; +import org.eclipse.emf.compare.ide.ui.internal.logical.EMFModelProvider; +import org.eclipse.emf.compare.ide.ui.internal.logical.resolver.CrossReferenceResolutionScope; +import org.eclipse.emf.compare.ide.ui.internal.preferences.EMFCompareUIPreferences; +import org.eclipse.emf.compare.ide.ui.tests.CompareTestCase; +import org.eclipse.emf.compare.ide.ui.tests.workspace.TestProject; +import org.eclipse.jface.preference.IPreferenceStore; +import org.junit.AfterClass; +import org.junit.Test; +import org.osgi.framework.Bundle; + +/** + * Tests the EMFModelProvider to return a minimum number of {@link ResourceMapping}s in cases where it is not + * straightforward (e.g. resolution scope set to "workspace") but still doable since it has all required input + * resources. + * + * @author Stefan Dirix <sdirix@eclipsesource.com> + */ +@SuppressWarnings({"nls", "restriction" }) +public class EMFModelProviderTest extends CompareTestCase { + + /** + * Path to the test data. + */ + private static final String TEST_DATA_PATH = "src/org/eclipse/emf/compare/ide/ui/tests/logical/modelprovider/data/"; + + /** + * The bundle containing this test. + */ + private static final String TEST_BUNDLE = "org.eclipse.emf.compare.ide.ui.tests"; + + /** + * Name of test project 1. + */ + private static final String PROJECT_NAME_1 = "project1"; + + /** + * Name of test project 2. + */ + private static final String PROJECT_NAME_2 = "project2"; + + /** + * Name of test project 3. + */ + private static final String PROJECT_NAME_3 = "project3"; + + /** + * Reset the resolution scope preference. + */ + @AfterClass + public static void setDefaultResolutionScope() { + final IPreferenceStore store = EMFCompareIDEUIPlugin.getDefault().getPreferenceStore(); + store.setToDefault(EMFCompareUIPreferences.RESOLUTION_SCOPE_PREFERENCE); + } + + /** + * Delete created projects. + */ + @AfterClass + public static void deleteProjects() throws CoreException { + deleteProject(PROJECT_NAME_1); + deleteProject(PROJECT_NAME_2); + deleteProject(PROJECT_NAME_3); + } + + /** + * Deletes the project wit the given {@code name}. + * + * @param name + * The name of the project which shall be deleted. + */ + private static void deleteProject(String name) throws CoreException { + final IProject project = new TestProject(name).getProject(); + project.delete(true, true, new NullProgressMonitor()); + } + + /** + * Sets the resolution scope to the given preference. + * + * @param preference + * The value to which the resolution scope preference is set. + */ + private void setResolutionScope(String preference) { + final IPreferenceStore store = EMFCompareIDEUIPlugin.getDefault().getPreferenceStore(); + store.setValue(EMFCompareUIPreferences.RESOLUTION_SCOPE_PREFERENCE, preference); + } + + /** + * Creates the projects and test files for case1. + * + * @return The created test files. + */ + private IFile[] createProjectFilesCase1() throws CoreException, IOException, URISyntaxException { + IProject project1 = new TestProject(PROJECT_NAME_1).getProject(); + IProject project2 = new TestProject(PROJECT_NAME_2).getProject(); + IProject project3 = new TestProject(PROJECT_NAME_3).getProject(); + IFile file1 = addToProject(project1, "case1/file1.ecore", ""); + IFile file2 = addToProject(project2, "case1/file2.ecore", ""); + IFile file3 = addToProject(project3, "case1/file3.ecore", ""); + return new IFile[] {file1, file2, file3 }; + } + + /** + * Creates the projects and test files for case2. + * + * @return The created test files. + */ + private IFile[] createProjectFilesCase2() throws CoreException, IOException, URISyntaxException { + IProject project1 = new TestProject(PROJECT_NAME_1).getProject(); + IProject project2 = new TestProject(PROJECT_NAME_2).getProject(); + IProject project3 = new TestProject(PROJECT_NAME_3).getProject(); + IFile file1 = addToProject(project1, "case2/file1.ecore", ""); + IFile file2 = addToProject(project2, "case2/file2.ecore", ""); + IFile file3 = addToProject(project3, "case2/file3.ecore", ""); + return new IFile[] {file1, file2, file3 }; + } + + /** + * Creates the projects and test files for case3. + * + * @return The created test files. + */ + private IFile[] createProjectFilesCase3() throws CoreException, IOException, URISyntaxException { + IProject project1 = new TestProject(PROJECT_NAME_1).getProject(); + IFile file1 = addToProject(project1, "case3/file1.ecore", ""); + IFile file2 = addToProject(project1, "case3/file2.ecore", "/a/"); + IFile file3 = addToProject(project1, "case3/file3.ecore", "/a/"); + return new IFile[] {file1, file2, file3 }; + } + + /** + * Tests the EMFModelProvider with a requirements row: + * {@code project1/file1 -> project2/file2 -> project3/file3} and resolution scope + * {@link CrossReferenceResolutionScope#OUTGOING}. + */ + @Test + public void testResourceMappingCase1_Outgoing() throws CoreException, IOException, URISyntaxException { + setResolutionScope(CrossReferenceResolutionScope.OUTGOING.toString()); + final IFile[] files = createProjectFilesCase1(); + + final ResourceMapping[] singleResourceMappings = collectSingleResourceMappings(files); + // {file1,file2,file3}, {file2,file3}, {file3} + assertEquals(3, singleResourceMappings.length); + + final ResourceMapping[] combinedResourceMappings = getCombinedResourceMappings(files); + // EMFModelProvider can optimize to {file1,file2,file3} + assertEquals(1, combinedResourceMappings.length); + } + + /** + * Tests the EMFModelProvider with a requirements row: + * {@code project1/file1 -> project2/file2 -> project3/file3} and resolution scope + * {@link CrossReferenceResolutionScope#CONTAINER}. + */ + @Test + public void testResourceMappingCase1_Container() throws CoreException, IOException, URISyntaxException { + setResolutionScope(CrossReferenceResolutionScope.CONTAINER.toString()); + final IFile[] files = createProjectFilesCase1(); + + final ResourceMapping[] singleResourceMappings = collectSingleResourceMappings(files); + // {file1}, {file2}, {file3} + assertEquals(3, singleResourceMappings.length); + + final ResourceMapping[] combinedResourceMappings = getCombinedResourceMappings(files); + // EMFModelProvider can not optimize and returns the same + assertEquals(3, combinedResourceMappings.length); + } + + /** + * Tests the EMFModelProvider with a requirements row: + * {@code project1/file1 -> project2/file2 -> project3/file3} and resolution scope + * {@link CrossReferenceResolutionScope#PROJECT}. + */ + @Test + public void testResourceMappingCase1_Project() throws CoreException, IOException, URISyntaxException { + setResolutionScope(CrossReferenceResolutionScope.PROJECT.toString()); + final IFile[] files = createProjectFilesCase1(); + + final ResourceMapping[] singleResourceMappings = collectSingleResourceMappings(files); + // {file1}, {file2}, {file3} + assertEquals(3, singleResourceMappings.length); + + final ResourceMapping[] combinedResourceMappings = getCombinedResourceMappings(files); + // EMFModelProvider can not optimize and returns the same + assertEquals(3, combinedResourceMappings.length); + } + + /** + * Tests the EMFModelProvider with a requirements row: + * {@code project1/file1 -> project2/file2 -> project3/file3} and resolution scope + * {@link CrossReferenceResolutionScope#WORKSPACE}. + */ + @Test + public void testResourceMappingCase1_Workspace() throws CoreException, IOException, URISyntaxException { + setResolutionScope(CrossReferenceResolutionScope.WORKSPACE.toString()); + final IFile[] files = createProjectFilesCase1(); + + final ResourceMapping[] singleResourceMappings = collectSingleResourceMappings(files); + // {file1,file2,file3} + assertEquals(1, singleResourceMappings.length); + + final ResourceMapping[] combinedResourceMappings = getCombinedResourceMappings(files); + // The mapping can not be optimized further + assertEquals(1, combinedResourceMappings.length); + } + + /** + * Tests the EMFModelProvider with a requirements fork: {@code project1/file1 -> project3/file3} & + * {@code project2/file2 -> project3/file3} and resolution scope + * {@link CrossReferenceResolutionScope#OUTGOING}. + */ + @Test + public void testResourceMappingCase2_Outgoing() throws CoreException, IOException, URISyntaxException { + setResolutionScope(CrossReferenceResolutionScope.OUTGOING.toString()); + final IFile[] files = createProjectFilesCase2(); + + final ResourceMapping[] singleResourceMappings = collectSingleResourceMappings(files); + // {file1,file2,file3}, {file3} + assertEquals(2, singleResourceMappings.length); + + final ResourceMapping[] combinedResourceMappings = getCombinedResourceMappings(files); + // EMFModelProvider can optimize to {file1,file2,file3} + assertEquals(1, combinedResourceMappings.length); + } + + /** + * Tests the EMFModelProvider with a requirements fork: {@code project1/file1 -> project3/file3} & + * {@code project2/file2 -> project3/file3} and resolution scope + * {@link CrossReferenceResolutionScope#CONTAINER}. + */ + @Test + public void testResourceMappingCase2_Container() throws CoreException, IOException, URISyntaxException { + setResolutionScope(CrossReferenceResolutionScope.CONTAINER.toString()); + final IFile[] files = createProjectFilesCase2(); + + final ResourceMapping[] singleResourceMappings = collectSingleResourceMappings(files); + // {file1}, {file2}, {file3} + assertEquals(3, singleResourceMappings.length); + + final ResourceMapping[] combinedResourceMappings = getCombinedResourceMappings(files); + // EMFModelProvider can not optimize and returns the same + assertEquals(3, combinedResourceMappings.length); + } + + /** + * Tests the EMFModelProvider with a requirements fork: {@code project1/file1 -> project3/file3} & + * {@code project2/file2 -> project3/file3} and resolution scope + * {@link CrossReferenceResolutionScope#PROJECT}. + */ + @Test + public void testResourceMappingCase2_Project() throws CoreException, IOException, URISyntaxException { + setResolutionScope(CrossReferenceResolutionScope.PROJECT.toString()); + final IFile[] files = createProjectFilesCase2(); + + final ResourceMapping[] singleResourceMappings = collectSingleResourceMappings(files); + // {file1}, {file2}, {file3} + assertEquals(3, singleResourceMappings.length); + + final ResourceMapping[] combinedResourceMappings = getCombinedResourceMappings(files); + // EMFModelProvider can not optimize and returns the same + assertEquals(3, combinedResourceMappings.length); + } + + /** + * Tests the EMFModelProvider with a requirements fork: {@code project1/file1 -> project3/file3} & + * {@code project2/file2 -> project3/file3} and resolution scope + * {@link CrossReferenceResolutionScope#WORKSPACE}. + */ + @Test + public void testResourceMappingCase2_Workspace() throws CoreException, IOException, URISyntaxException { + setResolutionScope(CrossReferenceResolutionScope.WORKSPACE.toString()); + final IFile[] files = createProjectFilesCase2(); + + final ResourceMapping[] singleResourceMappings = collectSingleResourceMappings(files); + // {file1,file2,file3} + assertEquals(1, singleResourceMappings.length); + + final ResourceMapping[] combinedResourceMappings = getCombinedResourceMappings(files); + // The mapping can not be optimized further + assertEquals(1, combinedResourceMappings.length); + } + + /** + * Tests the EMFModelProvider with a requirements row: {@code file1 -> a/file2 -> a/file3} and resolution + * scope {@link CrossReferenceResolutionScope#OUTGOING}. + */ + @Test + public void testResourceMappingCase3_Outgoing() throws CoreException, IOException, URISyntaxException { + setResolutionScope(CrossReferenceResolutionScope.OUTGOING.toString()); + final IFile[] files = createProjectFilesCase3(); + + final ResourceMapping[] singleResourceMappings = collectSingleResourceMappings(files); + // {file1,file2,file3}, {file2,file3}, {file3} + assertEquals(3, singleResourceMappings.length); + + final ResourceMapping[] combinedResourceMappings = getCombinedResourceMappings(files); + // EMFModelProvider can optimize to {file1,file2,file3} + assertEquals(1, combinedResourceMappings.length); + } + + /** + * Tests the EMFModelProvider with a requirements row: {@code file1 -> a/file2 -> a/file3} and resolution + * scope {@link CrossReferenceResolutionScope#CONTAINER}. + */ + @Test + public void testResourceMappingCase3_Container() throws CoreException, IOException, URISyntaxException { + setResolutionScope(CrossReferenceResolutionScope.CONTAINER.toString()); + final IFile[] files = createProjectFilesCase3(); + + final ResourceMapping[] singleResourceMappings = collectSingleResourceMappings(files); + // {file1}, {file2,file3} + assertEquals(2, singleResourceMappings.length); + + final ResourceMapping[] combinedResourceMappings = getCombinedResourceMappings(files); + // EMFModelProvider can optimize to {file1,file2,file3} + assertEquals(1, combinedResourceMappings.length); + } + + /** + * Tests the EMFModelProvider with a requirements row: {@code file1 -> a/file2 -> a/file3} and resolution + * scope {@link CrossReferenceResolutionScope#PROJECT}. + */ + @Test + public void testResourceMappingCase3_Project() throws CoreException, IOException, URISyntaxException { + setResolutionScope(CrossReferenceResolutionScope.PROJECT.toString()); + final IFile[] files = createProjectFilesCase3(); + + final ResourceMapping[] singleResourceMappings = collectSingleResourceMappings(files); + // {file1,file2,file3} + assertEquals(1, singleResourceMappings.length); + + final ResourceMapping[] combinedResourceMappings = getCombinedResourceMappings(files); + // The mapping can not be optimized further + assertEquals(1, combinedResourceMappings.length); + } + + /** + * Tests the EMFModelProvider with a requirements row: {@code file1 -> a/file2 -> a/file3} and resolution + * scope {@link CrossReferenceResolutionScope#WORKSPACE}. + */ + @Test + public void testResourceMappingCase3_Workspace() throws CoreException, IOException, URISyntaxException { + setResolutionScope(CrossReferenceResolutionScope.WORKSPACE.toString()); + final IFile[] files = createProjectFilesCase3(); + + final ResourceMapping[] singleResourceMappings = collectSingleResourceMappings(files); + // {file1,file2,file3} + assertEquals(1, singleResourceMappings.length); + + final ResourceMapping[] combinedResourceMappings = getCombinedResourceMappings(files); + // The mapping can not be optimized further + assertEquals(1, combinedResourceMappings.length); + } + + /** + * Collects all resource mappings of the given files by calling + * {@link EMFModelProvider#getMappings(IResource, org.eclipse.core.resources.mapping.ResourceMappingContext, IProgressMonitor)} + * on each of them and combining the results. This is how the default implementation within the + * ModelProvider class is implemented. + * + * @param files + * The files for which the resource mappings shall be calculated. + * @return The combined result of determining the resource mapping for each given file separately. + */ + private ResourceMapping[] collectSingleResourceMappings(IFile... files) throws CoreException { + final EMFModelProvider emfModelProvider = getEMFModelProvider(); + Set<ResourceMapping> mappings = new HashSet<ResourceMapping>(); + for (int i = 0; i < files.length; i++) { + IResource resource = files[i]; + ResourceMapping[] resourceMappings = emfModelProvider.getMappings(resource, new StubContext(), + new NullProgressMonitor()); + if (resourceMappings.length > 0) { + mappings.addAll(Arrays.asList(resourceMappings)); + } + } + return mappings.toArray(new ResourceMapping[mappings.size()]); + } + + /** + * Returns the result of calling + * {@link EMFModelProvider#getTraversals(ResourceMapping[], org.eclipse.core.resources.mapping.ResourceMappingContext, IProgressMonitor)} + * to determine the ResourceMappings. + * + * @param files + * The files for which the resource mappings shall be determined. + * @return The resource mappings for the given file when {@link EMFModelProvider} is given a chance to + * optimize. + */ + private ResourceMapping[] getCombinedResourceMappings(IFile... files) throws CoreException { + final EMFModelProvider emfModelProvider = getEMFModelProvider(); + return emfModelProvider.getMappings(files, new StubContext(), new NullProgressMonitor()); + } + + private EMFModelProvider getEMFModelProvider() throws CoreException { + final IModelProviderDescriptor[] descriptors = ModelProvider.getModelProviderDescriptors(); + for (IModelProviderDescriptor descriptor : descriptors) { + if (descriptor.getModelProvider() instanceof EMFModelProvider) { + return (EMFModelProvider)descriptor.getModelProvider(); + } + } + return null; + } + + /** + * Copies the file located in {@link #TEST_DATA_PATH} + {@code filePath} to the given + * {@code destinationPath} in {@code iProject}. + * + * @param iProject + * The {@link IProject} to which the file is added. + * @param filePath + * The path relative to {@link #TEST_DATA_PATH} where the file is originally located. + * @param destinationPath + * The path in the {@code iProject} to which the file will be copied. + * @return The newly created {@link IFile}. + */ + private IFile addToProject(IProject iProject, String filePath, String destinationPath) + throws IOException, URISyntaxException, CoreException { + final Bundle bundle = Platform.getBundle(TEST_BUNDLE); + final URI fileUri = getFileUri(bundle.getEntry(TEST_DATA_PATH + filePath)); + + final File file = project.getOrCreateFile(iProject, destinationPath + fileUri.lastSegment()); + + copyFile(toFile(fileUri), file); + + return project.getIFile(iProject, file); + } + + 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()); + } + + /** + * Stub for a {@link RemoteResourceMappingContext}. The class is used to trigger the creation of a proper + * comparison scope within the {@link EMFModelProvider}. + */ + private class StubContext extends RemoteResourceMappingContext { + + @Override + public IStorage fetchBaseContents(IFile file, IProgressMonitor monitor) throws CoreException { + return null; + } + + @Override + public IResource[] fetchMembers(IContainer container, IProgressMonitor monitor) throws CoreException { + return new IResource[0]; + } + + @Override + public IStorage fetchRemoteContents(IFile file, IProgressMonitor monitor) throws CoreException { + return null; + } + + @Override + public IProject[] getProjects() { + return ResourcesPlugin.getWorkspace().getRoot().getProjects(); + } + + @Override + public boolean hasLocalChange(IResource resource, IProgressMonitor monitor) throws CoreException { + return true; + } + + @Override + public boolean hasRemoteChange(IResource resource, IProgressMonitor monitor) throws CoreException { + return true; + } + + @Override + public boolean isThreeWay() { + return false; + } + + @Override + public void refresh(ResourceTraversal[] traversals, int flags, IProgressMonitor monitor) + throws CoreException { + } + + } +} diff --git a/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/modelprovider/data/case1/file1.ecore b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/modelprovider/data/case1/file1.ecore new file mode 100644 index 000000000..fdc0ea7cb --- /dev/null +++ b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/modelprovider/data/case1/file1.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="_A" name="File1_A"> + <eSubpackages href="../project2/file2.ecore#_B"/> +</ecore:EPackage>
\ No newline at end of file diff --git a/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/modelprovider/data/case1/file2.ecore b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/modelprovider/data/case1/file2.ecore new file mode 100644 index 000000000..537d7623e --- /dev/null +++ b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/modelprovider/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="../project3/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/modelprovider/data/case1/file3.ecore b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/modelprovider/data/case1/file3.ecore new file mode 100644 index 000000000..07e473eeb --- /dev/null +++ b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/modelprovider/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/logical/modelprovider/data/case2/file1.ecore b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/modelprovider/data/case2/file1.ecore new file mode 100644 index 000000000..373b2df6e --- /dev/null +++ b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/modelprovider/data/case2/file1.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="_A" name="File1_A"> + <eSubpackages href="../project3/file3.ecore#_C"/> +</ecore:EPackage>
\ No newline at end of file diff --git a/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/modelprovider/data/case2/file2.ecore b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/modelprovider/data/case2/file2.ecore new file mode 100644 index 000000000..537d7623e --- /dev/null +++ b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/modelprovider/data/case2/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="../project3/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/modelprovider/data/case2/file3.ecore b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/modelprovider/data/case2/file3.ecore new file mode 100644 index 000000000..07e473eeb --- /dev/null +++ b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/modelprovider/data/case2/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/logical/modelprovider/data/case3/file1.ecore b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/modelprovider/data/case3/file1.ecore new file mode 100644 index 000000000..a21e37c62 --- /dev/null +++ b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/modelprovider/data/case3/file1.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="_A" name="File1_A"> + <eSubpackages href="a/file2.ecore#_B"/> +</ecore:EPackage>
\ No newline at end of file diff --git a/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/modelprovider/data/case3/file2.ecore b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/modelprovider/data/case3/file2.ecore new file mode 100644 index 000000000..531c77313 --- /dev/null +++ b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/modelprovider/data/case3/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/modelprovider/data/case3/file3.ecore b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/modelprovider/data/case3/file3.ecore new file mode 100644 index 000000000..07e473eeb --- /dev/null +++ b/plugins/org.eclipse.emf.compare.ide.ui.tests/src/org/eclipse/emf/compare/ide/ui/tests/logical/modelprovider/data/case3/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 81deaa288..d8c01aea8 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 @@ -8,6 +8,7 @@ * Contributors: * Obeo - initial API and implementation * Philip Langer - adds test classes + * Stefan Dirix - add EMFModelProviderTest *******************************************************************************/ package org.eclipse.emf.compare.ide.ui.tests.suite; @@ -18,6 +19,7 @@ import junit.textui.TestRunner; import org.eclipse.emf.compare.ComparePackage; import org.eclipse.emf.compare.ide.ui.tests.compareconfiguration.EMFCompareConfigurationTest; import org.eclipse.emf.compare.ide.ui.tests.contentmergeviewer.notloadedfragment.NotLoadedFragmentItemTest; +import org.eclipse.emf.compare.ide.ui.tests.logical.modelprovider.EMFModelProviderTest; import org.eclipse.emf.compare.ide.ui.tests.logical.resolver.DependencyGraphUpdaterTest; import org.eclipse.emf.compare.ide.ui.tests.logical.resolver.GraphResolutionTest; import org.eclipse.emf.compare.ide.ui.tests.logical.resolver.ResolutionEventsTest; @@ -39,19 +41,12 @@ import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; @RunWith(Suite.class) -@SuiteClasses({EMFCompareConfigurationTest.class, - DependenciesTest.class, - MergeActionTest.class, - PseudoConflictsMergeActionTest.class, - BugsTestSuite.class, - NavigatableTest.class, - NotLoadedFragmentNodeTest.class, - NotLoadedFragmentItemTest.class, - ResolutionEventsTest.class, - ResourceComputationSchedulerTest.class, - ThreadedModelResolverGraphTest.class, - ThreadedModelResolverWithCustomDependencyProviderTest.class, - DependencyGraphUpdaterTest.class, GraphResolutionTest.class }) +@SuiteClasses({EMFCompareConfigurationTest.class, DependenciesTest.class, MergeActionTest.class, + PseudoConflictsMergeActionTest.class, BugsTestSuite.class, NavigatableTest.class, + NotLoadedFragmentNodeTest.class, NotLoadedFragmentItemTest.class, ResolutionEventsTest.class, + ResourceComputationSchedulerTest.class, ThreadedModelResolverGraphTest.class, + ThreadedModelResolverWithCustomDependencyProviderTest.class, DependencyGraphUpdaterTest.class, + GraphResolutionTest.class, EMFModelProviderTest.class }) public class AllTests { /** * Launches the test with the given arguments. diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/ComparisonScopeBuilder.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/ComparisonScopeBuilder.java index bb2850367..0bad985bb 100644 --- a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/ComparisonScopeBuilder.java +++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/ComparisonScopeBuilder.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2013, 2015 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 @@ -7,6 +7,7 @@ * * Contributors: * Obeo - initial API and implementation + * Stefan Dirix - bug 466607 *******************************************************************************/ package org.eclipse.emf.compare.ide.ui.internal.logical; @@ -184,41 +185,36 @@ public final class ComparisonScopeBuilder { * one. Can be <code>null</code>. * @param monitor * The monitor on which to report progress information to the user. - * @return The newly created SynchronizationModel, <code>null</code> in case of user interruption. + * @return The newly created SynchronizationModel. + * @throws InterruptedException + * In case of user interruption. */ /* package */SynchronizationModel buildSynchronizationModel(ITypedElement left, ITypedElement right, - ITypedElement origin, IProgressMonitor monitor) { + ITypedElement origin, IProgressMonitor monitor) throws InterruptedException { if (LOGGER.isDebugEnabled()) { LOGGER.debug("buildSynchronizationModel - START"); //$NON-NLS-1$ } SubMonitor subMonitor = SubMonitor.convert(monitor, 100); subMonitor.subTask(EMFCompareIDEUIMessages.getString("EMFSynchronizationModel.resolving")); //$NON-NLS-1$ - try { - final SynchronizationModel syncModel; - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("buildSynchronizationModel - Creating sync model"); //$NON-NLS-1$ - } - if (storageAccessor != null) { - syncModel = createSynchronizationModel(storageAccessor, left, right, origin, subMonitor - .newChild(90)); - } else { - syncModel = createSynchronizationModel(left, right, origin, subMonitor.newChild(90)); - } - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("buildSynchronizationModel - Minimizing model"); //$NON-NLS-1$ - } - minimizer.minimize(syncModel, subMonitor.newChild(10)); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("buildSynchronizationModel - FINISH NORMALLY"); //$NON-NLS-1$ - } - return syncModel; - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("buildSynchronizationModel - FINISH ABNORMALLY"); //$NON-NLS-1$ - } - return null; + + final SynchronizationModel syncModel; + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("buildSynchronizationModel - Creating sync model"); //$NON-NLS-1$ + } + if (storageAccessor != null) { + syncModel = createSynchronizationModel(storageAccessor, left, right, origin, subMonitor + .newChild(90)); + } else { + syncModel = createSynchronizationModel(left, right, origin, subMonitor.newChild(90)); + } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("buildSynchronizationModel - Minimizing model"); //$NON-NLS-1$ + } + minimizer.minimize(syncModel, subMonitor.newChild(10)); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("buildSynchronizationModel - FINISH NORMALLY"); //$NON-NLS-1$ } + return syncModel; } /** diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/EMFModelProvider.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/EMFModelProvider.java index bcde040fd..441098895 100644 --- a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/EMFModelProvider.java +++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/EMFModelProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2012, 2015 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,15 +7,27 @@ * * Contributors: * Obeo - initial API and implementation + * Stefan Dirix - bug 466607 *******************************************************************************/ package org.eclipse.emf.compare.ide.ui.internal.logical; +import com.google.common.base.Joiner; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.concurrent.TimeUnit; import org.apache.log4j.Logger; @@ -32,6 +44,8 @@ import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIPlugin; +import org.eclipse.emf.compare.ide.ui.internal.logical.resolver.CrossReferenceResolutionScope; +import org.eclipse.emf.compare.ide.ui.internal.logical.resolver.ResolutionUtil; import org.eclipse.emf.compare.ide.ui.logical.IModelMinimizer; import org.eclipse.emf.compare.ide.ui.logical.IModelResolver; import org.eclipse.emf.compare.ide.ui.logical.IStorageProvider; @@ -132,6 +146,62 @@ public class EMFModelProvider extends ModelProvider { return super.getMappings(resource, context, monitor); } + @Override + public ResourceMapping[] getMappings(IResource[] resources, ResourceMappingContext context, + IProgressMonitor monitor) throws CoreException { + if (LOGGER.isInfoEnabled()) { + final Joiner joiner = Joiner.on(",").skipNulls(); //$NON-NLS-1$ + final String resourceList = joiner.join(resources); + LOGGER.info("getMappings() - START for " + resourceList); //$NON-NLS-1$ + } + + final List<ResourceMapping> mappings = new ArrayList<ResourceMapping>(); + + // collect all IFiles + final List<IFile> files = new ArrayList<IFile>(); + final List<IResource> remainingResources = new ArrayList<IResource>(); + + for (IResource resource : resources) { + if (resource instanceof IFile) { + files.add((IFile)resource); + } else { + remainingResources.add(resource); + } + } + + try { + final Map<SynchronizationModel, IFile> syncModels = computeLogicalModels(files, context, monitor); + for (Map.Entry<SynchronizationModel, IFile> entry : syncModels.entrySet()) { + final ResourceMapping mapping = new EMFResourceMapping(entry.getValue(), context, entry + .getKey(), PROVIDER_ID); + mappings.add(mapping); + } + + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + if (LOGGER.isInfoEnabled()) { + LOGGER.info("getMappings() - interrupt exception: fallback to super."); //$NON-NLS-1$ + } + return super.getMappings(resources, context, monitor); + } + + if (!remainingResources.isEmpty()) { + if (LOGGER.isInfoEnabled()) { + final Joiner joiner = Joiner.on(",").skipNulls(); //$NON-NLS-1$ + final String resourceList = joiner.join(remainingResources); + LOGGER.info("getMappings() - not all resources were handled. fallback to super for: " + resourceList); //$NON-NLS-1$ + } + mappings.addAll(Arrays.asList(super.getMappings(remainingResources + .toArray(new IResource[remainingResources.size()]), context, monitor))); + } else { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("getMappings() - FINISH NORMALLY"); //$NON-NLS-1$ + } + } + + return mappings.toArray(new ResourceMapping[mappings.size()]); + } + /** * Clears the caches of this provider. */ @@ -182,8 +252,13 @@ public class EMFModelProvider extends ModelProvider { } syncModel = computeLogicalModel(file, context, monitor); if (syncModel != null) { - for (IResource res : syncModel.getResources()) { - resourceMappingCache.put(res, syncModel); + if (ResolutionUtil.getResolutionScope().equals(CrossReferenceResolutionScope.WORKSPACE)) { + // logical model will be the same for all resources contained in the model + for (IResource res : syncModel.getResources()) { + resourceMappingCache.put(res, syncModel); + } + } else { + resourceMappingCache.put(file, syncModel); } } } else if (LOGGER.isDebugEnabled()) { @@ -274,6 +349,146 @@ public class EMFModelProvider extends ModelProvider { } /** + * Resolve the logical model(s) of the given files. + * <p> + * When model resolution setting is not set to "workspace" the + * {@link #computeLogicalModel(IFile, ResourceMappingContext, IProgressMonitor)} method can not guarantee + * to compute the whole logical model for the given file. But there are actually use cases in which the + * client already knows which files constitute the logical model, see for example + * {@link #getMappings(IResource[], ResourceMappingContext, IProgressMonitor)}. + * </p> + * <p> + * This method tries to combine the logical models of the given {@code files} if applicable. Depending of + * the given {@code files} the whole logical model may be resolved independent of the workspace resolution + * settings. + * </p> + * + * @param files + * The {@link IFile}s for which the logical model(s) is to be computed. + * @param context + * The resource mapping context. + * @param monitor + * Used to display progress information to the user. + * @return The computed logical models each mapped to the first file from which they were resolved. The + * logical models are disjoint. + * @throws CoreException + * If we cannot retrieve the content of a resource for some reason. + * @throws InterruptedException + * If the user interrupts the resolving. + */ + Map<SynchronizationModel, IFile> computeLogicalModels(Collection<IFile> files, + ResourceMappingContext context, IProgressMonitor monitor) throws CoreException, + InterruptedException { + if (LOGGER.isDebugEnabled()) { + final Joiner joiner = Joiner.on(",").skipNulls(); //$NON-NLS-1$ + final String fileList = joiner.join(files); + LOGGER.debug("computeLogicalModels() - START with " + fileList); //$NON-NLS-1$ + } + final Map<SynchronizationModel, IFile> syncModels = new LinkedHashMap<SynchronizationModel, IFile>(); + + for (IFile file : files) { + final SynchronizationModel currentSyncModel = getOrComputeLogicalModel(file, context, monitor); + + if (currentSyncModel == null) { + // skip file + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("computeLogicalModels() - Could not determine logical model for \"" + file + "\". SKIP file."); //$NON-NLS-1$ //$NON-NLS-2$ + } + continue; + } + + // subsets can be shortcutted + boolean combineModels = false; + + final List<SynchronizationModel> toCombine = new LinkedList<SynchronizationModel>(); + + for (SynchronizationModel syncModel : syncModels.keySet()) { + + if (!Collections.disjoint(syncModel.getResources(), currentSyncModel.getResources())) { + toCombine.add(syncModel); + + if (!syncModel.getResources().containsAll(currentSyncModel.getResources())) { + // the model is not a subset -> deactivate shortcut + combineModels = true; + } + } + } + + // if there are no models to combine, add this model to the result set + if (toCombine.isEmpty()) { + syncModels.put(currentSyncModel, file); + } + + // when a model can be added to multiple other models, all these models must be combined + if (combineModels) { + final Iterator<SynchronizationModel> it = toCombine.iterator(); + final SynchronizationModel firstToCombine = it.next(); + final IFile value = syncModels.get(firstToCombine); + + SynchronizationModel combinedModel = combineModels(currentSyncModel, firstToCombine); + syncModels.remove(firstToCombine); + + while (it.hasNext()) { + final SynchronizationModel currentModel = it.next(); + combinedModel = combineModels(combinedModel, currentModel); + syncModels.remove(currentModel); + } + + syncModels.put(combinedModel, value); + } + } + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("computeLogicalModels() - FINISH with " + syncModels.size() + " models"); //$NON-NLS-1$ //$NON-NLS-2$ + } + return syncModels; + } + + /** + * Combines the given {@link SynchronizationModel}s to a new model. + * + * @param modelA + * The model to combine. + * @param modelB + * The model to combine. + * @return A new {@link SynchronizationModel} which contains the combination of {@link StorageTraversal}s + * of the given models. + */ + private SynchronizationModel combineModels(SynchronizationModel modelA, SynchronizationModel modelB) { + Set<IStorage> left = new HashSet<IStorage>(); + Set<IStorage> right = new HashSet<IStorage>(); + Set<IStorage> origin = new HashSet<IStorage>(); + + StorageTraversal leftTraversal = null; + StorageTraversal rightTraversal = null; + StorageTraversal originTraversal = null; + + if (modelA.getLeftTraversal() != null) { + left.addAll(modelA.getLeftTraversal().getStorages()); + if (modelB != null && modelB.getLeftTraversal() != null) { + left.addAll(modelB.getLeftTraversal().getStorages()); + } + leftTraversal = new StorageTraversal(left); + } + if (modelA.getRightTraversal() != null) { + right.addAll(modelA.getRightTraversal().getStorages()); + if (modelB != null && modelB.getRightTraversal() != null) { + right.addAll(modelB.getRightTraversal().getStorages()); + } + rightTraversal = new StorageTraversal(right); + } + if (modelA.getOriginTraversal() != null) { + origin.addAll(modelA.getOriginTraversal().getStorages()); + if (modelB != null && modelB.getOriginTraversal() != null) { + right.addAll(modelB.getOriginTraversal().getStorages()); + } + originTraversal = new StorageTraversal(origin); + } + + return new SynchronizationModel(leftTraversal, rightTraversal, originTraversal); + } + + /** * Browses the given traversal in order to create a typed element for the given side of the comparison. * * @param traversal |