| /* |
| * Copyright (c) 2013 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; |
| } |
| } |
| } |