| /******************************************************************************* |
| * Copyright (c) 2012, 2016 Obeo and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Obeo - initial API and implementation |
| * Stefan Dirix - Fixes for Bugs 452147 and 453218 |
| *******************************************************************************/ |
| package org.eclipse.emf.compare.equi; |
| |
| import java.util.LinkedHashSet; |
| import java.util.Set; |
| import java.util.function.Predicate; |
| |
| import org.apache.log4j.Logger; |
| import org.eclipse.emf.common.util.Monitor; |
| import org.eclipse.emf.compare.CompareFactory; |
| import org.eclipse.emf.compare.Comparison; |
| import org.eclipse.emf.compare.ComparisonCanceledException; |
| import org.eclipse.emf.compare.Diff; |
| import org.eclipse.emf.compare.DifferenceKind; |
| import org.eclipse.emf.compare.EMFCompareMessages; |
| import org.eclipse.emf.compare.Equivalence; |
| import org.eclipse.emf.compare.FeatureMapChange; |
| import org.eclipse.emf.compare.Match; |
| import org.eclipse.emf.compare.ReferenceChange; |
| import org.eclipse.emf.compare.internal.utils.ComparisonUtil; |
| import org.eclipse.emf.compare.utils.IEqualityHelper; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.EReference; |
| import org.eclipse.emf.ecore.EStructuralFeature; |
| import org.eclipse.emf.ecore.util.FeatureMap; |
| |
| /** |
| * The requirements engine is in charge of actually computing the equivalences between the differences. |
| * <p> |
| * This default implementation aims at being generic enough to be used for any model, whatever the metamodel. |
| * However, specific requirements might be necessary. |
| * </p> |
| * TODO document available extension possibilities. |
| * |
| * @author <a href="mailto:cedric.notot@obeo.fr">Cedric Notot</a> |
| */ |
| public class DefaultEquiEngine implements IEquiEngine { |
| |
| /** The logger. */ |
| private static final Logger LOGGER = Logger.getLogger(DefaultEquiEngine.class); |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.compare.equi.IEquiEngine#computeEquivalences(org.eclipse.emf.compare.Comparison, |
| * org.eclipse.emf.common.util.Monitor) |
| */ |
| public void computeEquivalences(Comparison comparison, Monitor monitor) { |
| long start = System.currentTimeMillis(); |
| if (LOGGER.isDebugEnabled()) { |
| LOGGER.debug(String.format("detect equivalences - START")); //$NON-NLS-1$ |
| } |
| monitor.subTask(EMFCompareMessages.getString("DefaultEquiEngine.monitor.eq")); //$NON-NLS-1$ |
| for (Diff difference : comparison.getDifferences()) { |
| if (monitor.isCanceled()) { |
| throw new ComparisonCanceledException(); |
| } |
| checkForEquivalences(comparison, difference); |
| } |
| if (LOGGER.isInfoEnabled()) { |
| LOGGER.info(String.format("detect equivalences - END - Took %d ms", Long.valueOf(System //$NON-NLS-1$ |
| .currentTimeMillis() - start))); |
| } |
| } |
| |
| /** |
| * Checks the potential equivalence from the given <code>difference</code>. |
| * |
| * @param comparison |
| * The comparison this engine is expected to complete. |
| * @param difference |
| * The difference that is to be checked |
| */ |
| protected void checkForEquivalences(Comparison comparison, Diff difference) { |
| if (difference instanceof ReferenceChange) { |
| ReferenceChange referenceChange = (ReferenceChange)difference; |
| EReference reference = referenceChange.getReference(); |
| EReference eOpposite = reference.getEOpposite(); |
| |
| // If reference change on an opposite reference |
| if (eOpposite != null && !eOpposite.isContainer() && !eOpposite.isDerived()) { |
| checkForEquivalences(comparison, referenceChange); |
| } |
| } else if (difference instanceof FeatureMapChange) { |
| FeatureMapChange featureMapChange = (FeatureMapChange)difference; |
| // Case of a FeatureMap, it may be linked with FeatureMap-derived references. |
| checkForEquivalences(comparison, featureMapChange); |
| } |
| } |
| |
| /** |
| * Checks the potential equivalence from the given <code>difference</code>. |
| * |
| * @param comparison |
| * The comparison this engine is expected to complete. |
| * @param referenceChange |
| * The difference that is to be checked |
| */ |
| protected void checkForEquivalences(final Comparison comparison, final ReferenceChange referenceChange) { |
| Equivalence equivalence = referenceChange.getEquivalence(); |
| if (equivalence == null) { |
| // If no equivalence, create one |
| equivalence = CompareFactory.eINSTANCE.createEquivalence(); |
| |
| // Add the current difference to the equivalence |
| equivalence.getDifferences().add(referenceChange); |
| |
| /* |
| * Add the difference where the value is the object containing the current difference, which is |
| * contained by the value of the current difference, where the reference is linked to the opposite |
| * one |
| */ |
| final Match valueMatch = comparison.getMatch(referenceChange.getValue()); |
| final EReference eOpposite = referenceChange.getReference().getEOpposite(); |
| |
| final Object referenceContainer = ComparisonUtil.getExpectedSide(referenceChange.getMatch(), |
| referenceChange.getSource(), false); |
| final Object referenceValue = ComparisonUtil.getExpectedSide(valueMatch, |
| referenceChange.getSource(), false); |
| final boolean valueIsContainer = referenceContainer == referenceValue |
| && referenceContainer != null; |
| |
| if (eOpposite != null && valueMatch != null) { |
| final Predicate<? super Diff> candidateFilter = new Predicate<Diff>() { |
| public boolean test(Diff input) { |
| if (input instanceof ReferenceChange |
| && ((ReferenceChange)input).getReference() == eOpposite) { |
| final Match candidateMatch = comparison |
| .getMatch(((ReferenceChange)input).getValue()); |
| |
| final boolean sameMatch = candidateMatch == referenceChange.getMatch(); |
| final boolean oneIsMany = referenceChange.getReference().isMany() |
| || eOpposite.isMany(); |
| |
| return sameMatch && (oneIsMany || !valueIsContainer); |
| } |
| return false; |
| } |
| }; |
| final Iterable<Diff> candidates = valueMatch.getDifferences().stream() |
| .filter(candidateFilter)::iterator; |
| |
| for (Diff candidate : candidates) { |
| equivalence.getDifferences().add(candidate); |
| } |
| |
| addChangesFromOrigin(comparison, referenceChange, equivalence); |
| } |
| if (equivalence.getDifferences().size() > 1) { |
| comparison.getEquivalences().add(equivalence); |
| } |
| } |
| } |
| |
| /** |
| * Add to the given <code>equivalence</code> potential changes based on the same reference as the given |
| * <code>diff</code>. |
| * |
| * @param comparison |
| * The comparison which enable to know from which side come the business model objects owning |
| * the differences. |
| * @param diff |
| * The current difference. |
| * @param equivalence |
| * The current equivalence attached to the difference. |
| */ |
| private void addChangesFromOrigin(Comparison comparison, ReferenceChange diff, Equivalence equivalence) { |
| if (!diff.getReference().isMany()) { |
| final EObject originContainer = ComparisonUtil.getExpectedSide(diff.getMatch(), diff.getSource(), |
| false); |
| final Match valueMatch = comparison.getMatch(diff.getValue()); |
| if (originContainer != null) { |
| for (Diff candidate : comparison.getDifferences(originContainer)) { |
| if (!(candidate instanceof ReferenceChange)) { |
| continue; |
| } |
| final ReferenceChange candidateRC = (ReferenceChange)candidate; |
| |
| final boolean sameReference = diff.getReference() |
| .equals(candidateRC.getReference().getEOpposite()); |
| |
| final boolean sameContainer = originContainer == ComparisonUtil |
| .getExpectedSide(candidate.getMatch(), candidate.getSource(), false); |
| final boolean containerIsValue = originContainer == ComparisonUtil |
| .getExpectedSide(valueMatch, candidate.getSource(), false); |
| |
| if (sameReference |
| && (candidateRC.getReference().isMany() || !sameContainer || !containerIsValue)) { |
| equivalence.getDifferences().add(candidate); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Checks the potential equivalence from the given <code>difference</code>. |
| * |
| * @param comparison |
| * The comparison this engine is expected to complete. |
| * @param featureMapChange |
| * The difference that is to be checked |
| * @since 3.2 |
| */ |
| protected void checkForEquivalences(final Comparison comparison, |
| final FeatureMapChange featureMapChange) { |
| Equivalence equivalence = featureMapChange.getEquivalence(); |
| if (equivalence == null) { |
| final Set<Diff> differences = new LinkedHashSet<Diff>(); |
| differences.addAll(featureMapChange.getMatch().getDifferences()); |
| |
| final Object featureMapEntry = featureMapChange.getValue(); |
| final Object entryValue = ((FeatureMap.Entry)featureMapEntry).getValue(); |
| |
| if (entryValue instanceof EObject) { |
| addMatchDifferences(comparison.getMatch((EObject)entryValue), comparison, differences); |
| } |
| |
| final EStructuralFeature entryKey = ((FeatureMap.Entry)featureMapEntry).getEStructuralFeature(); |
| final Set<ReferenceChange> equivalentDiffs = new LinkedHashSet<>(); |
| final IEqualityHelper equalityHelper = comparison.getEqualityHelper(); |
| Iterable<ReferenceChange> refChanges = differences.stream() |
| .filter(ReferenceChange.class::isInstance).map(ReferenceChange.class::cast)::iterator; |
| for (ReferenceChange refChange : refChanges) { |
| // The current diff has the same ref & value than the Map Entry of the FeatureMapChange. |
| boolean sameValue = equalityHelper.matchingValues(refChange.getValue(), entryValue); |
| boolean sameSource = featureMapChange.getSource() == refChange.getSource(); |
| boolean sameReference = refChange.getReference() == entryKey; |
| boolean sameMove = refChange.getKind() == DifferenceKind.MOVE |
| && featureMapChange.getKind() == DifferenceKind.MOVE; |
| |
| if (sameSource && sameValue && (sameReference || sameMove)) { |
| equivalentDiffs.add(refChange); |
| if (equivalence == null && refChange.getEquivalence() != null) { |
| equivalence = refChange.getEquivalence(); |
| } |
| } |
| } |
| |
| if (!equivalentDiffs.isEmpty()) { |
| if (equivalence == null) { |
| equivalence = CompareFactory.eINSTANCE.createEquivalence(); |
| comparison.getEquivalences().add(equivalence); |
| } |
| // Add the current difference to the equivalence |
| equivalence.getDifferences().add(featureMapChange); |
| // Add the MapFeature-derived references to the equivalence |
| equivalence.getDifferences().addAll(equivalentDiffs); |
| } |
| } |
| } |
| |
| /** |
| * Add the differences of the given match. |
| * |
| * @param match |
| * The match to deal with, can be <code>null</code> |
| * @param comparison |
| * The comparison |
| * @param differences |
| * The set of differences to add to, its content will be modified |
| */ |
| private void addMatchDifferences(final Match match, final Comparison comparison, |
| final Set<Diff> differences) { |
| if (match != null) { |
| differences.addAll(match.getDifferences()); |
| if (match.getLeft() != null) { |
| final Match leftParentMatch = comparison.getMatch(match.getLeft().eContainer()); |
| if (leftParentMatch != null) { |
| differences.addAll(leftParentMatch.getDifferences()); |
| } |
| } |
| if (match.getRight() != null) { |
| final Match rightParentMatch = comparison.getMatch(match.getRight().eContainer()); |
| if (rightParentMatch != null) { |
| differences.addAll(rightParentMatch.getDifferences()); |
| } |
| } |
| } |
| } |
| } |