blob: 87ac0b6817a0e226dcfc4a51812782abc3a3ef84 [file] [log] [blame]
/*******************************************************************************
* 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());
}
}
}
}
}