/* * Copyright (c) 2013, 2015, 2016 Eike Stepper (Berlin, Germany) 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: * Eike Stepper - initial API and implementation */ package org.eclipse.emf.cdo.compare; import org.eclipse.emf.cdo.CDOElement; import org.eclipse.emf.cdo.CDOObject; import org.eclipse.emf.cdo.common.id.CDOID; import org.eclipse.emf.cdo.common.id.CDOIDUtil; import org.eclipse.emf.cdo.common.model.EMFUtil; import org.eclipse.emf.cdo.util.CDOUtil; import org.eclipse.net4j.util.ReflectUtil; import org.eclipse.emf.common.notify.Notifier; import org.eclipse.emf.common.notify.impl.AdapterImpl; import org.eclipse.emf.common.util.Monitor; import org.eclipse.emf.compare.CompareFactory; import org.eclipse.emf.compare.Comparison; import org.eclipse.emf.compare.EMFCompare; import org.eclipse.emf.compare.EMFCompare.Builder; import org.eclipse.emf.compare.Match; import org.eclipse.emf.compare.conflict.IConflictDetector; import org.eclipse.emf.compare.diff.IDiffEngine; import org.eclipse.emf.compare.equi.IEquiEngine; import org.eclipse.emf.compare.match.DefaultComparisonFactory; import org.eclipse.emf.compare.match.DefaultEqualityHelperFactory; import org.eclipse.emf.compare.match.DefaultMatchEngine; import org.eclipse.emf.compare.match.IComparisonFactory; import org.eclipse.emf.compare.match.IEqualityHelperFactory; import org.eclipse.emf.compare.match.IMatchEngine; import org.eclipse.emf.compare.match.eobject.IEObjectMatcher; import org.eclipse.emf.compare.match.eobject.IdentifierEObjectMatcher; import org.eclipse.emf.compare.match.impl.MatchEngineFactoryRegistryImpl; import org.eclipse.emf.compare.postprocessor.IPostProcessor; import org.eclipse.emf.compare.req.IReqEngine; import org.eclipse.emf.compare.scope.IComparisonScope; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.util.InternalEList; import com.google.common.base.Function; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import java.lang.reflect.Method; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; /** * A class with many overridable factory methods that help to create EMF {@link Comparison comparisons}. * * @author Eike Stepper */ public class CDOCompare { public Comparison compare(IComparisonScope scope) { Function<EObject, String> idFunction = createIDFunction(); IEObjectMatcher matcher = createMatcher(idFunction); IEqualityHelperFactory equalityHelperFactory = createEqualityHelperFactory(); IComparisonFactory comparisonFactory = createComparisonFactory(equalityHelperFactory); EMFCompare comparator = createComparator(matcher, comparisonFactory); Comparison comparison = comparator.compare(scope); comparison.eAdapters().add(new ComparisonScopeAdapter(scope)); return comparison; } protected CDOIDFunction createIDFunction() { return new CDOIDFunction(); } protected IdentifierEObjectMatcher createMatcher(Function<EObject, String> idFunction) { return new CDOMatcher(idFunction); } protected IEqualityHelperFactory createEqualityHelperFactory() { return new DefaultEqualityHelperFactory(); } protected IComparisonFactory createComparisonFactory(IEqualityHelperFactory equalityHelperFactory) { return new DefaultComparisonFactory(equalityHelperFactory); } protected EMFCompare createComparator(IEObjectMatcher matcher, IComparisonFactory comparisonFactory) { Builder builder = EMFCompare.builder(); IMatchEngine.Factory.Registry matchEngineFactoryRegistry = createMatchEngineFactoryRegistry(matcher, comparisonFactory); if (matchEngineFactoryRegistry != null) { builder.setMatchEngineFactoryRegistry(matchEngineFactoryRegistry); } IDiffEngine diffEngine = createDiffEngine(); if (diffEngine != null) { builder.setDiffEngine(diffEngine); } IReqEngine reqEngine = createRequirementEngine(); if (reqEngine != null) { builder.setRequirementEngine(reqEngine); } IEquiEngine equiEngine = createEquivalenceEngine(); if (equiEngine != null) { builder.setEquivalenceEngine(equiEngine); } IPostProcessor.Descriptor.Registry<?> registry = createPostProcessorRegistry(); if (registry != null) { builder.setPostProcessorRegistry(registry); } IConflictDetector conflictDetector = createConflictDetector(); if (conflictDetector != null) { builder.setConflictDetector(conflictDetector); } return builder.build(); } protected IMatchEngine.Factory.Registry createMatchEngineFactoryRegistry(IEObjectMatcher matcher, IComparisonFactory comparisonFactory) { IMatchEngine.Factory.Registry registry = new MatchEngineFactoryRegistryImpl(); registry.add(new CDOMatchEngine.Factory(matcher, comparisonFactory)); return registry; } protected IDiffEngine createDiffEngine() { return null; } protected IReqEngine createRequirementEngine() { return null; } protected IEquiEngine createEquivalenceEngine() { return null; } protected IPostProcessor.Descriptor.Registry<?> createPostProcessorRegistry() { return null; } protected IConflictDetector createConflictDetector() { return null; } public static IComparisonScope getScope(Comparison comparison) { ComparisonScopeAdapter adapter = EMFUtil.getAdapter(comparison, ComparisonScopeAdapter.class); if (adapter == null) { return null; } return adapter.getScope(); } /** * An {@link CDOIDFunction ID function} that considers the {@link CDOID}s of {@link CDOObject objects}. * * @author Eike Stepper */ public static class CDOIDFunction implements Function<EObject, String> { public String apply(EObject o) { CDOObject object = CDOUtil.getCDOObject(o); CDOID id = object.cdoID(); StringBuilder builder = new StringBuilder(); CDOIDUtil.write(builder, id); return builder.toString(); } } /** * A {@link IEObjectMatcher matcher} that treats {@link Resource resources} as {@link EObject EObjects}. * * @author Eike Stepper * @since 4.3 */ public static class CDOMatcher extends IdentifierEObjectMatcher { private static final Method GETPARENTEOBJECT_METHOD; private final Function<EObject, String> idComputation; public CDOMatcher(Function<EObject, String> idComputation) { super(idComputation); this.idComputation = idComputation; } @Override protected EObject getParentEObject(EObject eObject) { return CDOElement.getParentOf(eObject); } @Override protected Set<Match> matchPerId(Iterator<? extends EObject> leftEObjects, Iterator<? extends EObject> rightEObjects, Iterator<? extends EObject> originEObjects, List<EObject> leftEObjectsNoID, List<EObject> rightEObjectsNoID, List<EObject> originEObjectsNoID) { if (GETPARENTEOBJECT_METHOD == null) { return matchPerIdCompatibility(leftEObjects, rightEObjects, originEObjects, leftEObjectsNoID, rightEObjectsNoID, originEObjectsNoID); } return super.matchPerId(leftEObjects, rightEObjects, originEObjects, leftEObjectsNoID, rightEObjectsNoID, originEObjectsNoID); } private Set<Match> matchPerIdCompatibility(Iterator<? extends EObject> leftEObjects, Iterator<? extends EObject> rightEObjects, Iterator<? extends EObject> originEObjects, List<EObject> leftEObjectsNoID, List<EObject> rightEObjectsNoID, List<EObject> originEObjectsNoID) { final List<EObject> leftEObjectsNoID1 = leftEObjectsNoID; final List<EObject> rightEObjectsNoID1 = rightEObjectsNoID; final List<EObject> originEObjectsNoID1 = originEObjectsNoID; final Set<Match> matches = Sets.newLinkedHashSet(); // This lookup map will be used by iterations on right and origin to find the match in which they // should add themselves final Map<String, Match> idToMatch = Maps.newHashMap(); // We will try and mimic the structure of the input model. // These map do not need to be ordered, we only need fast lookup. final Map<EObject, Match> leftEObjectsToMatch = Maps.newHashMap(); final Map<EObject, Match> rightEObjectsToMatch = Maps.newHashMap(); final Map<EObject, Match> originEObjectsToMatch = Maps.newHashMap(); // We'll only iterate once on each of the three sides, building the matches as we go while (leftEObjects.hasNext()) { final EObject left = leftEObjects.next(); final String identifier = idComputation.apply(left); if (identifier != null) { final Match match = CompareFactory.eINSTANCE.createMatch(); match.setLeft(left); // Can we find a parent? Assume we're iterating in containment order final EObject parentEObject = getParentEObject(left); final Match parent = leftEObjectsToMatch.get(parentEObject); if (parent != null) { ((InternalEList<Match>)parent.getSubmatches()).addUnique(match); } else { matches.add(match); } idToMatch.put(identifier, match); leftEObjectsToMatch.put(left, match); } else { leftEObjectsNoID1.add(left); } } while (rightEObjects.hasNext()) { final EObject right = rightEObjects.next(); // Do we have an existing match? final String identifier = idComputation.apply(right); if (identifier != null) { Match match = idToMatch.get(identifier); if (match != null) { match.setRight(right); rightEObjectsToMatch.put(right, match); } else { // Otherwise, create and place it. match = CompareFactory.eINSTANCE.createMatch(); match.setRight(right); // Can we find a parent? final EObject parentEObject = getParentEObject(right); final Match parent = rightEObjectsToMatch.get(parentEObject); if (parent != null) { ((InternalEList<Match>)parent.getSubmatches()).addUnique(match); } else { matches.add(match); } rightEObjectsToMatch.put(right, match); idToMatch.put(identifier, match); } } else { rightEObjectsNoID1.add(right); } } while (originEObjects.hasNext()) { final EObject origin = originEObjects.next(); // Do we have an existing match? final String identifier = idComputation.apply(origin); if (identifier != null) { Match match = idToMatch.get(identifier); if (match != null) { match.setOrigin(origin); originEObjectsToMatch.put(origin, match); } else { // Otherwise, create and place it. match = CompareFactory.eINSTANCE.createMatch(); match.setOrigin(origin); // Can we find a parent? final EObject parentEObject = getParentEObject(origin); final Match parent = originEObjectsToMatch.get(parentEObject); if (parent != null) { ((InternalEList<Match>)parent.getSubmatches()).addUnique(match); } else { matches.add(match); } idToMatch.put(identifier, match); originEObjectsToMatch.put(origin, match); } } else { originEObjectsNoID1.add(origin); } } return matches; } static { Method method = null; try { method = ReflectUtil.getMethod(IdentifierEObjectMatcher.class, "getParentEObject", EObject.class); } catch (Throwable ex) { //$FALL-THROUGH$ } GETPARENTEOBJECT_METHOD = method; } } /** * A {@link DefaultMatchEngine match engine} that treats {@link Resource resources} as {@link EObject EObjects}. * * @author Eike Stepper */ public static class CDOMatchEngine extends DefaultMatchEngine { CDOMatchEngine(IEObjectMatcher matcher, IComparisonFactory comparisonFactory) { super(matcher, comparisonFactory); } @Override protected void match(Comparison comparison, IComparisonScope scope, final Notifier left, final Notifier right, final Notifier origin, Monitor monitor) { // Omit special treatment of Resources (and ResourceSets). Just match EObjects. match(comparison, scope, (EObject)left, (EObject)right, (EObject)origin, monitor); } /** * Creates {@link CDOMatchEngine match engine} instances. * * @author Eike Stepper */ public static class Factory implements IMatchEngine.Factory { private final IMatchEngine matchEngine; private int ranking; public Factory(IEObjectMatcher matcher, IComparisonFactory comparisonFactory) { matchEngine = createMatchEngine(matcher, comparisonFactory); } protected Factory(IMatchEngine matchEngine) { this.matchEngine = matchEngine; } protected CDOMatchEngine createMatchEngine(IEObjectMatcher matcher, IComparisonFactory comparisonFactory) { return new CDOMatchEngine(matcher, comparisonFactory); } public IMatchEngine getMatchEngine() { return matchEngine; } public int getRanking() { return ranking; } public void setRanking(int ranking) { this.ranking = ranking; } public boolean isMatchEngineFactoryFor(IComparisonScope scope) { return scope instanceof CDOComparisonScope; } } } /** * @author Eike Stepper */ private static final class ComparisonScopeAdapter extends AdapterImpl { private IComparisonScope scope; public ComparisonScopeAdapter(IComparisonScope scope) { this.scope = scope; } public final IComparisonScope getScope() { return scope; } @Override public boolean isAdapterForType(Object type) { return type == ComparisonScopeAdapter.class; } } }