/******************************************************************************* * 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 * Michael Borkowski - bug 467576 *******************************************************************************/ package org.eclipse.emf.compare.utils; import static com.google.common.base.Predicates.and; import static org.eclipse.emf.compare.DifferenceKind.ADD; import static org.eclipse.emf.compare.DifferenceKind.DELETE; import static org.eclipse.emf.compare.DifferenceSource.LEFT; import static org.eclipse.emf.compare.DifferenceSource.RIGHT; import static org.eclipse.emf.compare.utils.EMFComparePredicates.CONTAINMENT_REFERENCE_CHANGE; import static org.eclipse.emf.compare.utils.EMFComparePredicates.ofKind; import static org.eclipse.emf.compare.utils.EMFComparePredicates.onFeature; import static org.eclipse.emf.compare.utils.EMFComparePredicates.valueIs; import static org.eclipse.emf.compare.utils.ReferenceUtil.getAsList; import com.google.common.collect.Iterables; import java.util.List; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.compare.AttributeChange; import org.eclipse.emf.compare.Comparison; import org.eclipse.emf.compare.Diff; import org.eclipse.emf.compare.DifferenceKind; import org.eclipse.emf.compare.DifferenceSource; import org.eclipse.emf.compare.Match; import org.eclipse.emf.compare.ReferenceChange; import org.eclipse.emf.compare.util.CompareSwitch; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecore.util.EcoreUtil; /** * This utility class holds methods that will be used by the diff and merge processes. * * @author <a href="mailto:cedric.notot@obeo.fr">Cedric Notot</a> */ public final class MatchUtil { /** * Utility classes don't need a default constructor. */ private MatchUtil() { // Hides default constructor } /** * Get the object which is the origin value from the given matching <code>object</code>. * * @param comparison * The comparison. * @param object * The given object. * @return The origin value. */ public static EObject getOriginObject(Comparison comparison, EObject object) { EObject result = null; Match match = comparison.getMatch(object); if (match != null) { if (comparison.isThreeWay()) { result = match.getOrigin(); } else { if (object == match.getLeft()) { result = match.getRight(); } else { result = match.getLeft(); } } } return result; } /** * This will be used whenever we check for conflictual MOVEs in order to determine whether we have a * pseudo conflict or a real conflict. * <p> * Namely, this will retrieve the value of the given {@code feature} on the right and left sides of the * given {@code match}, then check whether the two given values are on the same index. * </p> * <p> * Note that no sanity checks will be made on either the match's sides or the feature. * </p> * * @param match * Match for which we need to check a feature. * @param feature * The feature which values we need to check. * @param value1 * First of the two values which index we are to compare. * @param value2 * Second of the two values which index we are to compare. * @return {@code true} if the two given values are located at the same index in the given feature's * values list, {@code false} otherwise. * @since 3.4 */ public static boolean matchingIndices(Match match, EStructuralFeature feature, Object value1, Object value2) { boolean matching = false; if (feature.isMany()) { // FIXME the detection _will_ fail for non-unique lists with multiple identical values... int leftIndex = computeIndex(match, feature, value1, LEFT); int rightIndex = computeIndex(match, feature, value2, RIGHT); matching = leftIndex == rightIndex; } else { matching = true; } return matching; } /** * Compute the index of an object in the list of elements of a given match+feature on a given side. This * index is computed without taking objects that have a diff into account, except if this diff is an ADD. * * @param match * The match * @param feature * The structural feature * @param value * The object the index of which must be computed * @param side * The side on which to compute the index * @return The index of the given object. * @since 3.4 */ public static int computeIndex(Match match, EStructuralFeature feature, Object value, DifferenceSource side) { Comparison comparison = match.getComparison(); int result = -1; @SuppressWarnings("unchecked") final List<Object> sideValues = (List<Object>)ReferenceUtil .safeEGet(MatchUtil.getMatchedObject(match, side), feature); for (int i = 0; i < sideValues.size(); i++) { final Object sideObject = sideValues.get(i); if (comparison.getEqualityHelper().matchingValues(sideObject, value)) { break; } else if ((hasDiff(match, feature, sideObject) && match.getOrigin() != null) || hasDeleteDiff(match, feature, sideObject)) { // Do not increment. } else { result++; } } return result; } /** * Checks whether the given {@code value} has been deleted from the given {@code feature} of {@code match} * . * * @param match * The match which differences we'll check. * @param feature * The feature on which we expect a difference. * @param value * The value we expect to have been removed from {@code feature}. * @return <code>true</code> if there is such a Diff on {@code match}, <code>false</code> otherwise. * @since 3.4 */ @SuppressWarnings("unchecked") public static boolean hasDeleteDiff(Match match, EStructuralFeature feature, Object value) { Comparison comparison = match.getComparison(); final Object expectedValue; if (value instanceof EObject && comparison.isThreeWay()) { final Match valueMatch = comparison.getMatch((EObject)value); if (valueMatch != null) { expectedValue = valueMatch.getOrigin(); } else { expectedValue = value; } } else { expectedValue = value; } return Iterables.any(match.getDifferences(), and(onFeature(feature.getName()), valueIs(expectedValue), ofKind(DELETE))); } /** * Checks whether the given {@code match} presents a difference of any kind on the given {@code feature}'s * {@code value}. * * @param match * The match which differences we'll check. * @param feature * The feature on which we expect a difference. * @param value * The value we expect to have changed inside {@code feature}. * @return <code>true</code> if there is such a Diff on {@code match}, <code>false</code> otherwise. * @since 3.4 */ public static boolean hasDiff(Match match, EStructuralFeature feature, Object value) { return Iterables.any(match.getDifferences(), and(onFeature(feature.getName()), valueIs(value))); } /** * From a given mono-valued reference change, get the origin value. * * @param comparison * The comparison. * @param difference * The given reference change. * @return The origin value. */ public static EObject getOriginValue(Comparison comparison, ReferenceChange difference) { final EReference reference = difference.getReference(); if (!reference.isContainment() && !reference.isMany() && difference.getKind().equals(DifferenceKind.CHANGE)) { EObject originContainer = getOriginContainer(comparison, difference); if (originContainer != null) { Object originValue = ReferenceUtil.safeEGet(originContainer, reference); if (originValue instanceof EObject) { return (EObject)originValue; } } } return null; } /** * Get the business model object containing the given <code>difference</code> in the origin side. * * @param comparison * The comparison. * @param difference * The difference. * @return The object. */ public static EObject getOriginContainer(Comparison comparison, Diff difference) { final EObject diffContainer; if (comparison.isThreeWay()) { diffContainer = difference.getMatch().getOrigin(); } else { if (getContainer(comparison, difference) == difference.getMatch().getLeft()) { diffContainer = difference.getMatch().getRight(); } else { diffContainer = difference.getMatch().getLeft(); } } return diffContainer; } /** * Get the business model object containing the given <code>difference</code>. * * @param comparison * The comparison. * @param difference * The difference. * @return The object. */ public static EObject getContainer(Comparison comparison, Diff difference) { EObject result = null; Match match = difference.getMatch(); final DifferenceSource source = difference.getSource(); final DifferenceKind kind = difference.getKind(); switch (kind) { case DELETE: if (comparison.isThreeWay()) { result = match.getOrigin(); } else { result = match.getRight(); } break; case ADD: // fall through case MOVE: if (source == DifferenceSource.LEFT) { result = match.getLeft(); } else { result = match.getRight(); } break; case CHANGE: final Object value = getValue(difference); final EStructuralFeature feature = getStructuralFeature(difference); if (value == null || feature == null) { // TODO ? throw new IllegalArgumentException(); } if (source == DifferenceSource.LEFT) { if (featureContains(match.getLeft(), feature, value)) { result = match.getLeft(); } else if (comparison.isThreeWay()) { result = match.getOrigin(); } else { result = match.getRight(); } } else { if (featureContains(match.getRight(), feature, value)) { result = match.getRight(); } else if (comparison.isThreeWay()) { result = match.getOrigin(); } else { // Cannot happen ... for now result = match.getLeft(); } } break; default: // no other case for now. } return result; } /** * Determines whether the given feature of the given {@link EObject} contains the provided value, while * correctly handling proxies (in other words, in case of proxies, the proxy URI is compared instead of * the objects, which would otherwise lead to false negatives). * * @param eObject * The object of which a feature is to be checked * @param feature * The feature of which containment is to be checked * @param value * The value which is to be verified in the feature * @return <code>true</code> if the feature contains the given value */ // public for testing public static boolean featureContains(EObject eObject, EStructuralFeature feature, Object value) { boolean contains = false; // only compute the value's URI once, and only if needed URI valueURI = null; for (Object element : getAsList(eObject, feature)) { if (element == value) { contains = true; break; } if (element != null && element.equals(value)) { contains = true; break; } if (element instanceof EObject && ((EObject)element).eIsProxy()) { if (valueURI == null && value instanceof EObject) { valueURI = EcoreUtil.getURI((EObject)value); } if (EcoreUtil.getURI((EObject)element).equals(valueURI)) { contains = true; break; } } } return contains; } /** * Get the value of any difference. * * @param input * The difference. * @return the value of the difference. */ public static Object getValue(Diff input) { final CompareSwitch<Object> customSwitch = new CompareSwitch<Object>() { @Override public Object caseAttributeChange(AttributeChange object) { return object.getValue(); } @Override public Object caseReferenceChange(ReferenceChange object) { return object.getValue(); } }; return customSwitch.doSwitch(input); } /** * Get the structural feature of any difference. * * @param input * The difference. * @return the structural feature. */ public static EStructuralFeature getStructuralFeature(Diff input) { final CompareSwitch<EStructuralFeature> customSwitch = new CompareSwitch<EStructuralFeature>() { @Override public EStructuralFeature caseAttributeChange(AttributeChange object) { return object.getAttribute(); } @Override public EStructuralFeature caseReferenceChange(ReferenceChange object) { return object.getReference(); } }; return customSwitch.doSwitch(input); } /** * Get the object matched by a Match on a given side. * * @param m * The match, must not be <code>null</code> * @param side * The side for which we want the matched value, use <code>null</code> for ORIGIN. * @return The value matched by this match on the given side. * @since 3.4 */ public static EObject getMatchedObject(Match m, DifferenceSource side) { if (side == null) { return m.getOrigin(); } switch (side) { case LEFT: return m.getLeft(); case RIGHT: return m.getRight(); default: throw new IllegalArgumentException("Value " + side + " is not a valid DifferenceSource."); //$NON-NLS-1$ //$NON-NLS-2$ } } /** * Get the potential ReferenceChanges that represent add/delete containment differences in the parent * Match of the given Match. * * @param match * the given Match. * @return the potential ReferenceChanges that represent add/delete containment differences in the parent * Match of the given Match, <code>null</code> otherwise. */ public static Iterable<Diff> findAddOrDeleteContainmentDiffs(Match match) { final EObject container = match.eContainer(); if (container instanceof Match) { return Iterables.filter(((Match)container).getDifferences(), and(CONTAINMENT_REFERENCE_CHANGE, ofKind(ADD, DELETE))); } return null; } }