Java tutorial
/******************************************************************************* * 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 - bugs 441172, 452147 and 454579 * Alexandra Buzila - Fixes for Bug 446252 *******************************************************************************/ package org.eclipse.emf.compare.merge; import static com.google.common.base.Predicates.in; import static com.google.common.base.Predicates.or; import static com.google.common.collect.Iterables.addAll; import static com.google.common.collect.Iterables.any; import static com.google.common.collect.Iterables.concat; import static com.google.common.collect.Iterables.transform; import static org.eclipse.emf.compare.utils.EMFComparePredicates.fromSide; import static org.eclipse.emf.compare.utils.EMFComparePredicates.hasConflict; import static org.eclipse.emf.compare.utils.EMFComparePredicates.hasSameReferenceAs; import static org.eclipse.emf.compare.utils.EMFComparePredicates.isDiffOnEOppositeOf; import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.Collections2; import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.log4j.Logger; import org.eclipse.emf.common.util.Monitor; import org.eclipse.emf.compare.Comparison; import org.eclipse.emf.compare.Conflict; import org.eclipse.emf.compare.ConflictKind; import org.eclipse.emf.compare.Diff; import org.eclipse.emf.compare.DifferenceKind; import org.eclipse.emf.compare.DifferenceSource; import org.eclipse.emf.compare.DifferenceState; 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.EMFCompareCopier; import org.eclipse.emf.compare.utils.EMFComparePredicates; import org.eclipse.emf.compare.utils.ReferenceUtil; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.emf.ecore.util.FeatureMap; import org.eclipse.emf.ecore.util.InternalEList; /** * Abstract implementation of an {@link IMerger}. This can be used as a base implementation to avoid * re-implementing the whole contract. * * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a> * @since 3.0 */ public abstract class AbstractMerger implements IMerger2, IMergeOptionAware { /** The key of the merge option that allows to the mergers to consider sub-diffs of a diff as a whole. */ public static final String SUB_DIFF_AWARE_OPTION = "subDiffAwareOption"; //$NON-NLS-1$ /** The logger. */ private static final Logger LOGGER = Logger.getLogger(AbstractMerger.class); /** Ranking of this merger. */ private int ranking; /** Registry from which this merger has been created. */ private Registry registry; /** The map of all merge options that this merger should be aware of. */ private Map<Object, Object> mergeOptions; /** * Default constructor. */ public AbstractMerger() { this.mergeOptions = Maps.newHashMap(); } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.merge.IMerger#getRanking() */ public int getRanking() { return ranking; } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.merge.IMerger#setRanking(int) */ public void setRanking(int r) { ranking = r; } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.merge.IMerger#getRegistry() */ public Registry getRegistry() { return registry; } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.merge.IMerger#setRegistry(org.eclipse.emf.compare.merge.IMerger.Registry) */ public void setRegistry(Registry registry) { if (this.registry != null && registry != null) { throw new IllegalStateException("The registry has to be set only once."); //$NON-NLS-1$ } this.registry = registry; } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.merge.IMergeOptionAware#getMergeOptions() */ public Map<Object, Object> getMergeOptions() { return this.mergeOptions; } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.merge.IMergeOptionAware#setMergeOptions(java.util.Map) * @since 3.3 */ public void setMergeOptions(Map<Object, Object> options) { this.mergeOptions = options; } /** * Check the SUB_DIFF_AWARE_OPTION state. * * @return true if the SUB_DIFF_AWARE_OPTION of the merge options is set to true, false otherwise. */ private boolean isHandleSubDiffs() { if (this.mergeOptions != null) { Object subDiffs = this.mergeOptions.get(SUB_DIFF_AWARE_OPTION); return subDiffs == Boolean.TRUE; } return false; } /** * {@inheritDoc} * * @since 3.2 */ public Set<Diff> getDirectMergeDependencies(Diff diff, boolean mergeRightToLeft) { long start = System.currentTimeMillis(); final Set<Diff> dependencies = new LinkedHashSet<Diff>(); if (mergeRightToLeft) { if (DifferenceSource.LEFT == diff.getSource()) { dependencies.addAll(diff.getImplies()); dependencies.addAll(diff.getRequiredBy()); } else { dependencies.addAll(diff.getImpliedBy()); dependencies.addAll(diff.getRequires()); } } else { if (DifferenceSource.LEFT == diff.getSource()) { dependencies.addAll(diff.getImpliedBy()); dependencies.addAll(diff.getRequires()); } else { dependencies.addAll(diff.getImplies()); dependencies.addAll(diff.getRequiredBy()); } } dependencies.addAll(diff.getRefinedBy()); if (diff.getEquivalence() != null) { final Diff masterEquivalence = findMasterEquivalence(diff, mergeRightToLeft); if (masterEquivalence != null && masterEquivalence != diff) { dependencies.add(masterEquivalence); } } if (LOGGER.isDebugEnabled()) { Long duration = new Long(System.currentTimeMillis() - start); String log = String.format( "getDirectMergeDependencies(Diff, boolean) - %d dependencies found in %d ms for diff %d", //$NON-NLS-1$ new Integer(dependencies.size()), duration, new Integer(diff.hashCode())); LOGGER.debug(log); } return dependencies; } /** * {@inheritDoc} * * @since 3.2 */ public Set<Diff> getDirectResultingMerges(Diff target, boolean mergeRightToLeft) { long start = System.currentTimeMillis(); final Set<Diff> resulting = new LinkedHashSet<Diff>(); if (mergeRightToLeft) { if (DifferenceSource.LEFT == target.getSource()) { resulting.addAll(target.getImpliedBy()); } else { resulting.addAll(target.getImplies()); } } else { if (DifferenceSource.LEFT == target.getSource()) { resulting.addAll(target.getImplies()); } else { resulting.addAll(target.getImpliedBy()); } } if (target.getEquivalence() != null) { resulting.addAll(target.getEquivalence().getDifferences()); resulting.remove(target); } if (target.getConflict() != null && target.getConflict().getKind() == ConflictKind.PSEUDO) { resulting.addAll(target.getConflict().getDifferences()); resulting.remove(target); } // If a diff refines another, we have to check if the "macro" diff has to be merged with it. It is the // case when the unresolved diffs that refine the "macro" diff are all contained by the set // (target + resulting) (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=458961) for (Diff refine : target.getRefines()) { Set<Diff> tmp = Sets.newHashSet(resulting); tmp.add(target); Collection<Diff> unresolvedRefinedDiffs = Collections2.filter(refine.getRefinedBy(), EMFComparePredicates.hasState(DifferenceState.UNRESOLVED)); if (tmp.containsAll(unresolvedRefinedDiffs)) { resulting.add(refine); } } // Bug 452147: // Add interlocked differences to the resulting merges to avoid merging redundant differences with // undefined consequences. if (target instanceof ReferenceChange) { final ReferenceChange refTarget = (ReferenceChange) target; if (isOneToOneAndChange(refTarget)) { resulting.addAll(findInterlockedOneToOneDiffs(refTarget, mergeRightToLeft)); } } if (isHandleSubDiffs()) { final Set<Diff> resultingToMerge = Sets.newLinkedHashSet(resulting); Iterable<Diff> subDiffs = concat( transform(resultingToMerge, ComparisonUtil.getSubDiffs(!mergeRightToLeft))); addAll(resulting, subDiffs); } if (LOGGER.isDebugEnabled()) { Long duration = new Long(System.currentTimeMillis() - start); String log = String.format( "getDirectResultingMerges(Diff, boolean) - %d resulting merges found in %d ms for diff %d", //$NON-NLS-1$ new Integer(resulting.size()), duration, new Integer(target.hashCode())); LOGGER.debug(log); } return resulting; } /** * Interlocked differences only occur in special cases: When both ends of a one-to-one feature have the * same type and are actually set to the container object in an instance model. * <p> * For each end of the feature usually two differences are determined: Setting the feature in object A and * in object B. Each pair of differences is equivalent. But when the value of the feature is set to its * containing object, those differences may ALL act as equivalent depending on the merge direction. * <p> * These interlocked differences are therefore indirectly equivalent and need special treatment to avoid * merging the same effects twice. These differences are determined by this method. * * @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=452147">Bugzilla #452147</a> for more * information. * @param referenceChange * The diff for which interlocked differences are determined. * @param mergeRightToLeft * The direction in which we're considering a merge. * @return All interlocked differences in regards to the given {@code referenceChange} and * {@code mergeDirection}. */ private Collection<? extends Diff> findInterlockedOneToOneDiffs(ReferenceChange referenceChange, boolean mergeRightToLeft) { final boolean sanityChecks = referenceChange.getKind() != DifferenceKind.CHANGE || referenceChange.getReference().isMany() || referenceChange.getReference().getEOpposite().isMany(); // check if value to be set is the container itself final EObject sourceContainer = ComparisonUtil.getExpectedSide(referenceChange.getMatch(), referenceChange.getSource(), mergeRightToLeft); if (!sanityChecks && sourceContainer != null) { final Object sourceValue = ReferenceUtil.safeEGet(sourceContainer, referenceChange.getReference()); if (sourceValue == sourceContainer) { // collect all diffs which might be "equal" final Match match = referenceChange.getMatch(); final Set<Diff> candidates = new LinkedHashSet<Diff>(); for (Diff diff : match.getDifferences()) { candidates.add(diff); if (diff.getEquivalence() != null) { candidates.addAll(diff.getEquivalence().getDifferences()); } } // special case - check for interlocked diffs and return them as result return filterInterlockedOneToOneDiffs(candidates, referenceChange, mergeRightToLeft); } } return Collections.emptyList(); } /** * Checks for interlocked differences from a list of candidates. See * {@link #findInterlockedOneToOneDiffs(ReferenceChange, boolean)} for more information. * * @param diffsToCheck * The differences to be checked for indirect equivalence. * @param referenceChange * The diff to which the determined differences are indirectly equivalent. * @param mergeRightToLeft * The direction in which we're considering a merge. * @return All differences (and their equivalents) from {@code diffsToCheck} which are indirectly * equivalent to {@code referenceChange}. Does not modify the given collection. */ private Collection<? extends Diff> filterInterlockedOneToOneDiffs(Collection<? extends Diff> diffsToCheck, ReferenceChange referenceChange, boolean mergeRightToLeft) { final Object sourceContainer = ComparisonUtil.getExpectedSide(referenceChange.getMatch(), referenceChange.getSource(), mergeRightToLeft); final EReference sourceReference = referenceChange.getReference(); final Set<Diff> result = new LinkedHashSet<Diff>(); for (Diff candidate : diffsToCheck) { if (candidate instanceof ReferenceChange) { // check if container & reference(-opposite) are the same as from the given referenceChange final Object candidateContainer = ComparisonUtil.getExpectedSide(candidate.getMatch(), candidate.getSource(), mergeRightToLeft); final EReference candidateReference = ((ReferenceChange) candidate).getReference(); if (sourceContainer == candidateContainer && sourceReference.getEOpposite() == candidateReference) { result.add(candidate); if (candidate.getEquivalence() != null) { result.addAll(candidate.getEquivalence().getDifferences()); } } } } return result; } /** * {@inheritDoc} * * @since 3.2 */ public Set<Diff> getDirectResultingRejections(Diff target, boolean mergeRightToLeft) { long start = System.currentTimeMillis(); final Set<Diff> directlyImpliedRejections = new LinkedHashSet<Diff>(); final Conflict conflict = target.getConflict(); if (conflict != null && conflict.getKind() == ConflictKind.REAL) { if (mergeRightToLeft && target.getSource() == DifferenceSource.RIGHT) { Iterables.addAll(directlyImpliedRejections, Iterables.filter(conflict.getDifferences(), fromSide(DifferenceSource.LEFT))); } else if (!mergeRightToLeft && target.getSource() == DifferenceSource.LEFT) { Iterables.addAll(directlyImpliedRejections, Iterables.filter(conflict.getDifferences(), fromSide(DifferenceSource.RIGHT))); } } if (LOGGER.isDebugEnabled()) { Long duration = new Long(System.currentTimeMillis() - start); String log = String.format( "getDirectResultingMerges(Diff, boolean) - %d implied rejections found in %d ms for diff %d", //$NON-NLS-1$ new Integer(directlyImpliedRejections.size()), duration, new Integer(target.hashCode())); LOGGER.debug(log); } return directlyImpliedRejections; } /** * Even within 'equivalent' differences, there might be one that we need to consider as the "master", one * part of the equivalence that should take precedence over the others when merging. * <p> * There are four main cases in which this happens : * <ol> * <li>Equivalent differences regarding two "eOpposite" sides, with one side being a single-valued * reference while the other side is a multi-valued reference (one-to-many). In such a case, we need the * 'many' side of that equivalence to be merged over the 'single' side, so as to avoid potential ordering * issues. Additionally, to avoid losing information, equivalent differences with * {@link DifferenceKind.ADD} instead of {@link DifferenceKind.REMOVE} must be merged first.</li> * <li>Equivalent differences regarding two "eOpposite" sides, with both sides being a single-valued * reference (one-to-one). In such a case, we need to merge the difference that results in setting a * feature value over the difference unsetting a feature. This is needed to prevent information loss.</li> * <li>Equivalent differences with conflicts: basically, if one of the diffs of an equivalence relation is * in conflict while the others are not, then none of the equivalent differences can be automatically * merged. We need to consider the conflict to be taking precedence over the others to make sure that the * conflict is resolved before even trying to merge anything.</li> * <li>Equivalent {@link ReferenceChange} and {@link FeatureMapChange} differences: in this case the * {@link FeatureMapChange} difference will take precedence over the {@link ReferenceChange} when the the * resulting operation actively modifies a FeatureMap. The {@link ReferenceChange} will take precedence * when a FeatureMap is only modified implicitly. This happens in order to prevent special cases in which * the {@link ReferenceChangeMerger} cannot ensure the correct order of the feature map attribute.</li> * </ol> * </p> * * @param diff * The diff we need to check the equivalence for a 'master' difference. * @param mergeRightToLeft * Direction of the merge operation. * @return The master difference of this equivalence relation. May be <code>null</code> if there are none. */ private Diff findMasterEquivalence(Diff diff, boolean mergeRightToLeft) { final List<Diff> equivalentDiffs = diff.getEquivalence().getDifferences(); final Optional<Diff> firstConflicting = Iterables.tryFind(equivalentDiffs, hasConflict(ConflictKind.REAL)); final Diff idealMasterDiff; if (diff instanceof ReferenceChange) { final ReferenceChange referenceChange = (ReferenceChange) diff; idealMasterDiff = getMasterEquivalenceForReferenceChange(referenceChange, mergeRightToLeft); } else if (diff instanceof FeatureMapChange) { final FeatureMapChange featureMapChange = (FeatureMapChange) diff; idealMasterDiff = getMasterEquivalenceForFeatureMapChange(featureMapChange, mergeRightToLeft); } else { idealMasterDiff = null; } final Diff masterDiff; // conflicting equivalents take precedence over the ideal master equivalence if (firstConflicting.isPresent() && !hasRealConflict(idealMasterDiff)) { if (hasRealConflict(diff)) { masterDiff = null; } else { masterDiff = firstConflicting.get(); } } else { masterDiff = idealMasterDiff; } return masterDiff; } /** * Determines if the given {@link Diff} has a conflict of kind {@link ConflictKind#REAL}. * * @param diff * The {@link Diff} to check. * @return {@code true} if the diff exists and has a conflict of kind {@link ConflictKind#REAL}, * {@code false} otherwise. */ private boolean hasRealConflict(Diff diff) { return diff != null && diff.getConflict() != null && diff.getConflict().getKind() == ConflictKind.REAL; } /** * Returns the master equivalence for a {@link FeatureMapChange}. * * @see AbstractMerger#findMasterEquivalence(Diff, boolean) * @param diff * The {@link Diff} we need to check the equivalence for a 'master' difference. * @param mergeRightToLeft * Direction of the current merging. * @return The master difference of {@code diff} and its equivalent diffs. This method may return * <code>null</code> if there is no master diff. */ private Diff getMasterEquivalenceForFeatureMapChange(FeatureMapChange diff, boolean mergeRightToLeft) { if (diff.getKind() == DifferenceKind.MOVE) { final Comparison comparison = diff.getMatch().getComparison(); final FeatureMap.Entry entry = (FeatureMap.Entry) diff.getValue(); if (entry.getValue() instanceof EObject) { final Match valueMatch = comparison.getMatch((EObject) entry.getValue()); final EObject expectedValue = ComparisonUtil.getExpectedSide(valueMatch, diff.getSource(), mergeRightToLeft); // Try to find the ReferenceChange-MasterEquivalence when the expected value will not be // contained in a FeatureMap if (!ComparisonUtil.isContainedInFeatureMap(expectedValue)) { return Iterators.tryFind(diff.getEquivalence().getDifferences().iterator(), Predicates.instanceOf(ReferenceChange.class)).orNull(); } } } return null; } /** * Returns the master equivalence for a {@link ReferenceChange}. * * @see AbstractMerger#findMasterEquivalence(Diff, boolean) * @param diff * The {@link Diff} we need to check the equivalence for a 'master' difference. * @param mergeRightToLeft * Direction of the current merging. * @return The master difference of {@code diff} and its equivalent diffs. This method may return * <code>null</code> if there is no master diff. */ private Diff getMasterEquivalenceForReferenceChange(ReferenceChange diff, boolean mergeRightToLeft) { Diff masterDiff = getMasterEquivalenceOnReference(diff, mergeRightToLeft); if (masterDiff == null) { masterDiff = getMasterEquivalenceOnFeatureMap(diff, mergeRightToLeft); } return masterDiff; } /** * Returns the master equivalence for a {@link ReferenceChange} from among its equivalents with the same * or {@code eOpposite} reference. * * @see AbstractMerger#findMasterEquivalence(Diff, boolean) * @param diff * The {@link Diff} we need to check the equivalence for a 'master' difference. * @param mergeRightToLeft * Direction of the current merging. * @return The master difference of {@code diff} and its equivalent diffs. This method may return * <code>null</code> if there is no master diff. */ private Diff getMasterEquivalenceOnReference(ReferenceChange diff, final boolean mergeRightToLeft) { Diff masterDiff = null; /* * For the following, we'll only consider diffs that are either on the same reference as "diff", or on * its eopposite. */ final Predicate<Diff> candidateFilter = or(isDiffOnEOppositeOf(diff), hasSameReferenceAs(diff)); final List<Diff> equivalentDiffs = diff.getEquivalence().getDifferences(); // We need to lookup the first multi-valued addition final Optional<Diff> multiValuedAddition = Iterators .tryFind(Iterators.filter(equivalentDiffs.iterator(), candidateFilter), new Predicate<Diff>() { public boolean apply(Diff input) { return input instanceof ReferenceChange && ((ReferenceChange) input).getReference().isMany() && isAdd((ReferenceChange) input, mergeRightToLeft); } }); final Iterator<Diff> candidateDiffs = Iterators.filter(equivalentDiffs.iterator(), candidateFilter); if (multiValuedAddition.isPresent()) { // We have at least one multi-valued addition. It will take precedence if there is any // single-valued reference change or multi-valued deletion while (masterDiff == null && candidateDiffs.hasNext()) { final ReferenceChange next = (ReferenceChange) candidateDiffs.next(); if (!next.getReference().isMany() || !isAdd(next, mergeRightToLeft)) { masterDiff = multiValuedAddition.get(); } } } else { // The only diff that could take precedence is a single-valued set, _if_ there is any multi-valued // deletion or single-valued unset in the list. ReferenceChange candidate = null; if (candidateDiffs.hasNext()) { candidate = (ReferenceChange) candidateDiffs.next(); } while (masterDiff == null && candidateDiffs.hasNext()) { assert candidate != null; final ReferenceChange next = (ReferenceChange) candidateDiffs.next(); if (candidate.getReference().isMany() || isUnset(candidate, mergeRightToLeft)) { // candidate is a multi-valued deletion or an unset. Do we have a single-valued set in the // list? if (!next.getReference().isMany() && isSet(next, mergeRightToLeft)) { masterDiff = next; } } else if (isSet(candidate, mergeRightToLeft)) { // candidate is a set. Is it our master diff? if (next.getReference().isMany() || isUnset(next, mergeRightToLeft)) { masterDiff = candidate; } } else { // candidate is a change on a single-valued reference. This has no influence over the // 'master' lookup. Go on to the next. candidate = next; } } } return masterDiff; } /** * Returns the master equivalence of type {@link FeatureMapChange}, for a {@link ReferenceChange}. * * @see AbstractMerger#findMasterEquivalence(Diff, boolean) * @param diff * The {@link Diff} we need to check the equivalence for a 'master' difference. * @param mergeRightToLeft * Direction of the current merging. * @return The master difference of {@code diff} and its equivalent diffs. This method may return * <code>null</code> if there is no master diff. */ private Diff getMasterEquivalenceOnFeatureMap(ReferenceChange diff, boolean mergeRightToLeft) { if (diff.getKind() == DifferenceKind.MOVE) { Comparison comparison = diff.getMatch().getComparison(); Match valueMatch = comparison.getMatch(diff.getValue()); EObject sourceValue = ComparisonUtil.getExpectedSide(valueMatch, diff.getSource(), mergeRightToLeft); // No FeatureMap-MasterEquivalence when the resulting destination is not a FeatureMap if (!ComparisonUtil.isContainedInFeatureMap(sourceValue)) { return null; } } return Iterators.tryFind(diff.getEquivalence().getDifferences().iterator(), Predicates.instanceOf(FeatureMapChange.class)).orNull(); } /** * Specifies whether the given reference changes, {@code diff} and {@code equivalent}, affect references * constituting an one-to-many relationship and whether {@code equivalent} is an addition in the current * merging. * * @param diff * The difference to check. One-side of the relation. * @param equivalent * The equivalent to the {@code diff}. Many-side of the relation. * @param mergeRightToLeft * Direction of the merge. * @return <code>true</code> if {@code diff} and {@code equivalent} are one-to-many eOpposites with * {@code equivalent} resulting in an Add-operation, <code>false</code> otherwise. * @deprecated */ @Deprecated private boolean isOneToManyAndAdd(ReferenceChange diff, ReferenceChange equivalent, boolean mergeRightToLeft) { return !diff.getReference().isMany() && equivalent.getReference().isMany() && isAdd(equivalent, mergeRightToLeft); } /** * Specifies whether the given reference changes, {@code diff} and {@code equivalent}, affect references * constituting a one-to-one relationship and whether {@code equivalent} is a set in the current merging. * * @param diff * The difference to check. * @param equivalent * The equivalent to the {@code diff}. * @param mergeRightToLeft * Direction of the merge. * @return <code>true</code> if {@code diff} and {@code equivalent} are one-to-many eOpposites with * {@code equivalent} resulting in an Add-operation, <code>false</code> otherwise. * @deprecated */ @Deprecated private boolean isOneToOneAndSet(ReferenceChange diff, ReferenceChange equivalent, boolean mergeRightToLeft) { return !diff.getReference().isMany() && !equivalent.getReference().isMany() && isSet(equivalent, mergeRightToLeft); } /** * Checks whether the given {@code diff} is of kind {@link DifferenceKind#CHANGE} and its reference is * one-to-one. * * @param diff * The ReferenceChange to check. * @return {@code true} if the given {@code diff} is of kind {@link DifferenceKind#CHANGE} and describes a * one-to-one reference, {@code false} otherwise. */ private boolean isOneToOneAndChange(ReferenceChange diff) { final boolean oppositeReferenceExists = diff.getReference() != null && diff.getReference().getEOpposite() != null; return diff.getKind() == DifferenceKind.CHANGE && oppositeReferenceExists && !diff.getReference().isMany() && !diff.getReference().getEOpposite().isMany(); } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.merge.IMerger#copyLeftToRight(org.eclipse.emf.compare.Diff, * org.eclipse.emf.common.util.Monitor) * @since 3.1 */ public void copyLeftToRight(Diff target, Monitor monitor) { // Don't merge an already merged (or discarded) diff if (target.getState() != DifferenceState.UNRESOLVED) { return; } long start = System.currentTimeMillis(); // Change the diff's state before we actually merge it : this allows us to avoid requirement cycles. target.setState(DifferenceState.MERGED); final Set<Diff> dependencies = getDirectMergeDependencies(target, false); // We'll redo some of the work from getDirectMergeDependencies here in order to ensure we haven't been // merged by another diff (equivalence or implication). // requiresMerging must be executed before actually merging the dependencies because // findMasterEquivalence may return a different result after merging. boolean requiresMerging = requiresMerging(target, false); for (Diff mergeMe : dependencies) { mergeDiff(mergeMe, false, monitor); } for (Diff transitiveMerge : getDirectResultingMerges(target, false)) { transitiveMerge.setState(DifferenceState.MERGED); } if (requiresMerging) { if (target.getSource() == DifferenceSource.LEFT) { accept(target, false); } else { reject(target, false); } } if (isHandleSubDiffs()) { Set<Diff> subDiffs = Sets.newLinkedHashSet(ComparisonUtil.getDirectSubDiffs(true).apply(target)); for (Diff subDiff : subDiffs) { mergeDiff(subDiff, false, monitor); } } if (LOGGER.isDebugEnabled()) { long duration = System.currentTimeMillis() - start; LOGGER.debug("copyLeftToRight(Diff, Monitor) - diff " + target.hashCode() + " merged in " //$NON-NLS-1$ //$NON-NLS-2$ + duration + "ms"); //$NON-NLS-1$ } } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.merge.IMerger#copyRightToLeft(org.eclipse.emf.compare.Diff, * org.eclipse.emf.common.util.Monitor) * @since 3.1 */ public void copyRightToLeft(Diff target, Monitor monitor) { // Don't merge an already merged (or discarded) diff if (target.getState() != DifferenceState.UNRESOLVED) { return; } long start = System.currentTimeMillis(); // Change the diff's state before we actually merge it : this allows us to avoid requirement cycles. target.setState(DifferenceState.MERGED); final Set<Diff> dependencies = getDirectMergeDependencies(target, true); // We'll redo some of the work from getDirectMergeDependencies here in order to ensure we haven't been // merged by another diff (equivalence or implication). // requiresMerging must be executed before actually merging the dependencies because // findMasterEquivalence may return a different result after merging. boolean requiresMerging = requiresMerging(target, true); for (Diff mergeMe : dependencies) { mergeDiff(mergeMe, true, monitor); } for (Diff transitiveMerge : getDirectResultingMerges(target, true)) { transitiveMerge.setState(DifferenceState.MERGED); } if (requiresMerging) { if (target.getSource() == DifferenceSource.LEFT) { reject(target, true); } else { accept(target, true); } } if (isHandleSubDiffs()) { Set<Diff> subDiffs = Sets.newLinkedHashSet(ComparisonUtil.getDirectSubDiffs(false).apply(target)); for (Diff subDiff : subDiffs) { mergeDiff(subDiff, true, monitor); } } if (LOGGER.isDebugEnabled()) { long duration = System.currentTimeMillis() - start; LOGGER.debug("copyLeftToRight(Diff, Monitor) - diff " + target.hashCode() + " merged in " //$NON-NLS-1$ //$NON-NLS-2$ + duration + "ms"); //$NON-NLS-1$ } } /** * Checks whether the given diff still needs to be merged or if it has been merged because of an * implication or 'master' equivalence. * * @param target * The difference we are considering merging. * @param mergeRightToLeft * The direction in which we're considering a merge. * @return <code>true</code> if the <code>target</code> diff hasn't been merged yet and requires handling * of its own. */ private boolean requiresMerging(Diff target, boolean mergeRightToLeft) { boolean requiresMerging = true; if (isImpliedMerge(target, mergeRightToLeft)) { // first, if we are implied by something, then we're already merged requiresMerging = false; } else if (target.getEquivalence() != null) { final Diff masterEquivalence = findMasterEquivalence(target, mergeRightToLeft); if (masterEquivalence != null && masterEquivalence != target) { // If we have a "master" equivalence (see doc on findMasterEquivalence) then we've been merged // along with it requiresMerging = false; } else { // We also need to check for implications on our equivalence (dependency loops) requiresMerging = !hasTransitiveImplicationBeenMerged(target, mergeRightToLeft); } } return requiresMerging; } /** * Checks if the given diff is implied by another in the given merge direction, which means that it * doesn't need to be merged individually. * * @param target * The diff we're considering merging. * @param mergeRightToLeft * The direction in which we're currently merging. * @return <code>true</code> if the given diff will be implicitely merged by another in that direction. */ private boolean isImpliedMerge(Diff target, boolean mergeRightToLeft) { final boolean isImpliedForDirection; if (mergeRightToLeft) { if (DifferenceSource.LEFT == target.getSource()) { isImpliedForDirection = !target.getImplies().isEmpty(); } else { isImpliedForDirection = !target.getImpliedBy().isEmpty(); } } else { if (DifferenceSource.LEFT == target.getSource()) { isImpliedForDirection = !target.getImpliedBy().isEmpty(); } else { isImpliedForDirection = !target.getImplies().isEmpty(); } } return isImpliedForDirection; } // FIXME find a use case and check whether this is still required. /** * Checks whether the given diff has been merged through a dependency cycle on its equivalence relations * (this diff requires the merging of a diff that implies one of its equivalences). * <p> * This should only be called on differences that have equivalences. * </p> * * @param target * The difference we are considering merging. * @param mergeRightToLeft * The direction in which we're considering a merge. * @return <code>true</code> if the <code>target</code> diff has already been merged. */ private boolean hasTransitiveImplicationBeenMerged(Diff target, boolean mergeRightToLeft) { boolean mergedThroughEquivalentImplication = false; final Iterator<Diff> equivalenceIterator = target.getEquivalence().getDifferences().iterator(); while (!mergedThroughEquivalentImplication && equivalenceIterator.hasNext()) { final Diff equivalent = equivalenceIterator.next(); if (equivalent != target && mergeRightToLeft) { if (target.getSource() == DifferenceSource.LEFT) { mergedThroughEquivalentImplication = any(equivalent.getImplies(), in(target.getRequiredBy())); } else { mergedThroughEquivalentImplication = any(equivalent.getImpliedBy(), in(target.getRequires())); } } else if (equivalent != target) { if (target.getSource() == DifferenceSource.LEFT) { mergedThroughEquivalentImplication = any(equivalent.getImpliedBy(), in(target.getRequires())); } else { mergedThroughEquivalentImplication = any(equivalent.getImplies(), in(target.getRequiredBy())); } } } return mergedThroughEquivalentImplication; } /** * Accept the given difference. This may be overridden by clients. * * @param diff * the difference to merge * @param rightToLeft * the direction of the merge * @since 3.1 */ protected void accept(final Diff diff, boolean rightToLeft) { // Empty default implementation } /** * Reject the given difference. This may be overridden by clients. * * @param diff * the difference to merge * @param rightToLeft * the direction of the merge * @since 3.1 */ protected void reject(final Diff diff, boolean rightToLeft) { // Empty default implementation } /** * This will merge all {@link Diff#getRequiredBy() differences that require} {@code diff} in the given * direction. * * @param diff * We need to merge all differences that require this one (see {@link Diff#getRequiredBy()}. * @param rightToLeft * If {@code true}, {@link #copyRightToLeft(Diff, Monitor) apply} all differences that require * {@code diff}. Otherwise, {@link #copyLeftToRight(Diff, Monitor) revert} them. * @param monitor * The monitor we should use to report progress. * @Deprecated */ @Deprecated protected void mergeRequiredBy(Diff diff, boolean rightToLeft, Monitor monitor) { // TODO log back to the user what we will merge along? for (Diff dependency : diff.getRequiredBy()) { // TODO: what to do when state = Discarded but is required? mergeDiff(dependency, rightToLeft, monitor); } } /** * Mark as {@link DifferenceState#MERGED merged} all the implied differences recursively from the given * one. * * @param diff * The difference from which the implications have to be marked. * @param rightToLeft * The direction of the merge. * @param monitor * Monitor. * @since 3.1 * @deprecated */ @Deprecated protected void handleImplies(Diff diff, boolean rightToLeft, Monitor monitor) { for (Diff implied : diff.getImplies()) { implied.setState(DifferenceState.MERGED); handleImplies(implied, rightToLeft, monitor); } } /** * Mark as {@link DifferenceState#MERGED merged} all the implying differences recursively from the given * one. * * @param diff * The difference from which the implications have to be marked. * @param rightToLeft * The direction of the merge. * @param monitor * Monitor. * @since 3.1 * @deprecated */ @Deprecated protected void handleImpliedBy(Diff diff, boolean rightToLeft, Monitor monitor) { // Do nothing, this was an implementation error } /** * This will merge all {@link Diff#getRequires() differences required by} {@code diff} in the given * direction. * * @param diff * The difference which requirements we need to merge. * @param rightToLeft * If {@code true}, {@link #copyRightToLeft(Diff, Monitor) apply} all required differences. * Otherwise, {@link #copyLeftToRight(Diff, Monitor) revert} them. * @param monitor * The monitor we should use to report progress. * @deprecated */ @Deprecated protected void mergeRequires(Diff diff, boolean rightToLeft, Monitor monitor) { // TODO log back to the user what we will merge along? for (Diff dependency : diff.getRequires()) { // TODO: what to do when state = Discarded but is required? mergeDiff(dependency, rightToLeft, monitor); } } /** * This can be used by mergers to merge another (required, equivalent...) difference using the right * merger for that diff. * * @param diff * The diff we need to merge. * @param rightToLeft * Direction of that merge. * @param monitor * The monitor we should use to report progress. */ protected void mergeDiff(Diff diff, boolean rightToLeft, Monitor monitor) { final IMerger delegate = getRegistry().getHighestRankingMerger(diff); if (rightToLeft) { delegate.copyRightToLeft(diff, monitor); } else { delegate.copyLeftToRight(diff, monitor); } } /** * Handles the equivalences of this difference. * <p> * Note that in certain cases, we'll merge our opposite instead of merging this diff. This is done to * avoid merge orders where merging differences of kind {@link DifferenceKind.REMOVE} or * {@link DifferenceKind.CHANGE} (resulting in an unset) first can lead to information loss. Additionally * in case of one-to-many eOpposites this allows us not to worry about the order of the references on the * 'many' side. * </p> * <p> * This is called before the merge of <code>this</code>. In short, if this returns <code>false</code>, we * won't carry on merging <code>this</code> after returning. * </p> * * @param diff * The diff we are currently merging. * @param rightToLeft * Direction of the merge. * @param monitor * The monitor to use in order to report progress information. * @return <code>true</code> if the current difference should still be merged after handling its * equivalences, <code>false</code> if it should be considered "already merged". * @since 3.1 * @Deprecated */ @Deprecated protected boolean handleEquivalences(Diff diff, boolean rightToLeft, Monitor monitor) { boolean continueMerge = true; for (Diff equivalent : diff.getEquivalence().getDifferences()) { if (diff instanceof ReferenceChange && equivalent instanceof ReferenceChange) { final ReferenceChange diffRC = (ReferenceChange) diff; final ReferenceChange equivalentRC = (ReferenceChange) equivalent; if (diffRC.getReference().getEOpposite() == equivalentRC.getReference() && equivalent.getState() == DifferenceState.UNRESOLVED) { // This equivalence is on our eOpposite. Should we merge it instead of 'this'? final boolean mergeEquivalence = isOneToManyAndAdd(diffRC, equivalentRC, rightToLeft) || isOneToOneAndSet(diffRC, equivalentRC, rightToLeft); if (mergeEquivalence) { mergeDiff(equivalent, rightToLeft, monitor); continueMerge = false; } } } // in the case of a ReferenceChange-FeatureMapChange equivalence, the FeatureMapChange is // preferred to the ReferenceChange - cf. Bug 446252 if (diff instanceof ReferenceChange && equivalent instanceof FeatureMapChange) { mergeDiff(equivalent, rightToLeft, monitor); continueMerge = false; } /* * If one of the equivalent differences is implied or implying (depending on the merge direction) * a merged diff, then we have a dependency loop : the "current" difference has already been * merged because of this implication. This will allow us to break out of that loop. */ if (rightToLeft) { if (diff.getSource() == DifferenceSource.LEFT) { continueMerge = continueMerge && !any(equivalent.getImplies(), in(diff.getRequiredBy())); } else { continueMerge = continueMerge && !any(equivalent.getImpliedBy(), in(diff.getRequires())); } } else { if (diff.getSource() == DifferenceSource.LEFT) { continueMerge = continueMerge && !any(equivalent.getImpliedBy(), in(diff.getRequires())); } else { continueMerge = continueMerge && !any(equivalent.getImplies(), in(diff.getRequiredBy())); } } equivalent.setState(DifferenceState.MERGED); } return continueMerge; } /** * Specifies whether the given {@code diff} will add a value in the target model for the current merging. * <p> * To check whether the {@code diff} is an addition, we have to check the direction of the merge, * specified in {@code rightToLeft} and the {@link Diff#getSource() source of the diff}. Therefore, this * method delegates to {@link #isLeftAddOrRightDelete(ReferenceChange)} and * {@link #isLeftDeleteOrRightAdd(ReferenceChange)}. * </p> * * @param diff * The difference to check. * @param rightToLeft * Direction of the merge. * @return <code>true</code> if {@code diff} will add a value with this merge, <code>false</code> * otherwise. * @since 3.2 */ protected boolean isAdd(ReferenceChange diff, boolean rightToLeft) { if (rightToLeft) { return isLeftDeleteOrRightAdd(diff); } else { return isLeftAddOrRightDelete(diff); } } /** * Specifies whether the given {@code diff} is either an addition on the left-hand side or a deletion on * the right-hand side. * * @param diff * The difference to check. * @return <code>true</code> if it is a left addition or a right deletion. */ private boolean isLeftAddOrRightDelete(ReferenceChange diff) { if (diff.getSource() == DifferenceSource.LEFT) { return diff.getKind() == DifferenceKind.ADD; } else { return diff.getKind() == DifferenceKind.DELETE; } } /** * Specifies whether the given {@code diff} is either a deletion on the left-hand side or an addition on * the right-hand side. * * @param diff * The difference to check. * @return <code>true</code> if it is a left deletion or a right addition. */ private boolean isLeftDeleteOrRightAdd(ReferenceChange diff) { if (diff.getSource() == DifferenceSource.LEFT) { return diff.getKind() == DifferenceKind.DELETE; } else { return diff.getKind() == DifferenceKind.ADD; } } /** * Checks whether the given diff will result in the unsetting of a reference in the given merge direction. * * @param diff * The difference to check. * @param mergeRightToLeft * Direction of the merge. * @return <code>true</code> if {@code diff} will unset a value with this merge, <code>false</code> if * this will either "set" or "change" values... or if the given diff is affecting a multi-valued * reference. */ private boolean isUnset(ReferenceChange diff, boolean mergeRightToLeft) { if (diff.getKind() != DifferenceKind.CHANGE) { return false; } boolean isUnset = false; final Match match = diff.getMatch(); final EObject container; if (diff.getSource() == DifferenceSource.LEFT) { container = match.getLeft(); } else { container = match.getRight(); } if (container == null) { // This is an unset diff. However, if we're merging towards the source, we're actually "rejecting" // the unset, and the merge operation will be a "set" isUnset = !isRejecting(diff, mergeRightToLeft); } else { if (!ReferenceUtil.safeEIsSet(container, diff.getReference())) { // No value on the source side, this is an unset // Same case as above, if we are rejecting the diff, it is a "set" operation isUnset = !isRejecting(diff, mergeRightToLeft); } else { // The feature is set on the source side. If we're merging towards the other side, this cannot // be an unset. // Otherwise we're going to reset this reference to its previous value. That will end as an // "unset" if the "previous value" is unset itself. if (isRejecting(diff, mergeRightToLeft)) { final EObject originContainer; if (match.getComparison().isThreeWay()) { originContainer = match.getOrigin(); } else if (mergeRightToLeft) { originContainer = match.getRight(); } else { originContainer = match.getLeft(); } isUnset = originContainer == null || !ReferenceUtil.safeEIsSet(originContainer, diff.getReference()); } } } return isUnset; } /** * Checks whether the given diff will result in the setting of a reference in the given merge direction. * * @param diff * The difference to check. * @param mergeRightToLeft * Direction of the merge. * @return <code>true</code> if {@code diff} will set a value with this merge, <code>false</code> if this * will either "unset" or "change" values... or if the given diff is affecting a multi-valued * reference. */ private boolean isSet(ReferenceChange diff, boolean mergeRightToLeft) { if (diff.getKind() != DifferenceKind.CHANGE) { return false; } boolean isSet = false; final Match match = diff.getMatch(); final EObject container; if (diff.getSource() == DifferenceSource.LEFT) { container = match.getLeft(); } else { container = match.getRight(); } if (container == null) { // This is an unset diff. However, if we're merging towards the source, we're actually "rejecting" // the unset, and the merge operation will be a "set" isSet = isRejecting(diff, mergeRightToLeft); } else { if (!ReferenceUtil.safeEIsSet(container, diff.getReference())) { // No value on the source side, this is an unset // Same case as above, if we are rejecting the diff, it is a "set" operation isSet = isRejecting(diff, mergeRightToLeft); } else { // The feature is set on the source side. If we're merging towards the other side, this is a // "set" operation if the feature is not set on the target side. // Otherwise we're going to reset this reference to its previous value. That will end as an // "unset" if the "previous value" is unset itself. if (isRejecting(diff, mergeRightToLeft)) { final EObject originContainer; if (match.getComparison().isThreeWay()) { originContainer = match.getOrigin(); } else if (mergeRightToLeft) { originContainer = match.getRight(); } else { originContainer = match.getLeft(); } isSet = originContainer != null && ReferenceUtil.safeEIsSet(originContainer, diff.getReference()); } else { final EObject targetContainer; if (mergeRightToLeft) { targetContainer = match.getLeft(); } else { targetContainer = match.getRight(); } isSet = targetContainer == null || !ReferenceUtil.safeEIsSet(targetContainer, diff.getReference()); } } } return isSet; } /** * Checks whether the given merge direction will result in rejecting this difference. * * @param diff * The difference we're merging. * @param mergeRightToLeft * Direction of the merge operation. * @return <code>true</code> if we're rejecting this diff. */ private boolean isRejecting(Diff diff, boolean mergeRightToLeft) { if (diff.getSource() == DifferenceSource.LEFT) { return mergeRightToLeft; } else { return !mergeRightToLeft; } } /** * This will create a copy of the given EObject that can be used as the target of an addition (or the * reverting of a deletion). * <p> * The target will be self-contained and will have no reference towards any other EObject set (neither * containment nor "classic" references). All of its attributes' values will match the given * {@code referenceObject}'s. * </p> * * @param referenceObject * The EObject for which we'll create a copy. * @return A self-contained copy of {@code referenceObject}. * @see EMFCompareCopier#copy(EObject) */ protected EObject createCopy(EObject referenceObject) { /* * We can't simply use EcoreUtil.copy. References will have their own diffs and will thus be merged * later on. */ final EcoreUtil.Copier copier = new EMFCompareCopier(); return copier.copy(referenceObject); } /** * Adds the given {@code value} into the given {@code list} at the given {@code index}. An {@code index} * under than zero or above the list's size will mean that the value should be appended at the end of the * list. * * @param list * The list into which {@code value} should be added. * @param value * The value we need to add to {@code list}. * @param <E> * Type of objects contained in the list. * @param insertionIndex * The index at which {@code value} should be inserted into {@code list}. {@code -1} if it * should be appended at the end of the list. */ @SuppressWarnings("unchecked") protected <E> void addAt(List<E> list, E value, int insertionIndex) { if (list instanceof InternalEList<?>) { if (insertionIndex < 0 || insertionIndex > list.size()) { ((InternalEList<Object>) list).addUnique(value); } else { ((InternalEList<Object>) list).addUnique(insertionIndex, value); } } else { if (insertionIndex < 0 || insertionIndex > list.size()) { list.add(value); } else { list.add(insertionIndex, value); } } } }