| /******************************************************************************* |
| * Copyright (c) 2012, 2017 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 |
| * Philip Langer - fixes for bug 413520 |
| *******************************************************************************/ |
| package org.eclipse.emf.compare.merge; |
| |
| import static com.google.common.collect.Iterators.filter; |
| import static org.eclipse.emf.compare.merge.IMergeCriterion.NONE; |
| import static org.eclipse.emf.compare.utils.ReferenceUtil.safeEGet; |
| import static org.eclipse.emf.compare.utils.ReferenceUtil.safeEIsSet; |
| import static org.eclipse.emf.compare.utils.ReferenceUtil.safeESet; |
| |
| import com.google.common.collect.Iterators; |
| |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.eclipse.emf.common.util.EList; |
| import org.eclipse.emf.compare.Comparison; |
| import org.eclipse.emf.compare.Diff; |
| import org.eclipse.emf.compare.DifferenceSource; |
| import org.eclipse.emf.compare.Match; |
| import org.eclipse.emf.compare.ReferenceChange; |
| import org.eclipse.emf.compare.internal.utils.DiffUtil; |
| import org.eclipse.emf.compare.utils.IEqualityHelper; |
| import org.eclipse.emf.compare.utils.ReferenceUtil; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.EReference; |
| import org.eclipse.emf.ecore.EStructuralFeature; |
| import org.eclipse.emf.ecore.resource.Resource; |
| import org.eclipse.emf.ecore.util.EcoreUtil; |
| import org.eclipse.emf.ecore.xmi.XMIResource; |
| |
| /** |
| * This specific implementation of {@link AbstractMerger} will be used to merge reference changes. |
| * |
| * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a> |
| */ |
| public class ReferenceChangeMerger extends AbstractMerger { |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.compare.merge.IMerger#isMergerFor(org.eclipse.emf.compare.Diff) |
| */ |
| public boolean isMergerFor(Diff target) { |
| return target instanceof ReferenceChange; |
| } |
| |
| @Override |
| public boolean apply(IMergeCriterion criterion) { |
| return criterion == null || criterion == NONE; |
| } |
| |
| /** |
| * Merge the given difference rejecting it. |
| * |
| * @param diff |
| * The difference to merge. |
| * @param rightToLeft |
| * The direction of the merge. |
| */ |
| @Override |
| protected void reject(final Diff diff, boolean rightToLeft) { |
| ReferenceChange referenceChange = (ReferenceChange)diff; |
| DifferenceSource source = referenceChange.getSource(); |
| switch (referenceChange.getKind()) { |
| case ADD: |
| // We have a ADD on left, thus nothing in right. We need to revert the addition |
| removeFromTarget(referenceChange, rightToLeft); |
| break; |
| case DELETE: |
| // DELETE in the left, thus an element in right. We need to re-create that element |
| addInTarget(referenceChange, rightToLeft); |
| break; |
| case MOVE: |
| moveElement(referenceChange, rightToLeft); |
| break; |
| case CHANGE: |
| EObject container = null; |
| if (source == DifferenceSource.LEFT) { |
| container = referenceChange.getMatch().getLeft(); |
| |
| } else { |
| container = referenceChange.getMatch().getRight(); |
| } |
| // Is it an unset? |
| if (container != null) { |
| final EObject leftValue = (EObject)safeEGet(container, referenceChange.getReference()); |
| if (leftValue == null) { |
| // Value has been unset in the right, and we are merging towards right. |
| // We need to re-add this element |
| addInTarget(referenceChange, rightToLeft); |
| } else { |
| // We'll actually need to "reset" this reference to its original value |
| resetInTarget(referenceChange, rightToLeft); |
| } |
| } else { |
| // we have no left, and the source is on the left. Can only be an unset |
| addInTarget(referenceChange, rightToLeft); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| /** |
| * Merge the given difference accepting it. |
| * |
| * @param diff |
| * The difference to merge. |
| * @param rightToLeft |
| * The direction of the merge. |
| */ |
| @Override |
| protected void accept(final Diff diff, boolean rightToLeft) { |
| ReferenceChange referenceChange = (ReferenceChange)diff; |
| DifferenceSource source = diff.getSource(); |
| switch (diff.getKind()) { |
| case ADD: |
| // Create the same element in right |
| addInTarget(referenceChange, rightToLeft); |
| break; |
| case DELETE: |
| // Delete that same element from right |
| removeFromTarget(referenceChange, rightToLeft); |
| break; |
| case MOVE: |
| moveElement(referenceChange, rightToLeft); |
| break; |
| case CHANGE: |
| EObject container = null; |
| if (source == DifferenceSource.LEFT) { |
| container = referenceChange.getMatch().getLeft(); |
| } else { |
| container = referenceChange.getMatch().getRight(); |
| } |
| // Is it an unset? |
| if (container != null) { |
| final EObject leftValue = (EObject)safeEGet(container, referenceChange.getReference()); |
| if (leftValue == null) { |
| removeFromTarget(referenceChange, rightToLeft); |
| } else { |
| addInTarget(referenceChange, rightToLeft); |
| } |
| } else { |
| // we have no left, and the source is on the left. Can only be an unset |
| removeFromTarget(referenceChange, rightToLeft); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| /** |
| * This will be called when trying to copy a "MOVE" diff. |
| * |
| * @param diff |
| * The diff we are currently merging. |
| * @param rightToLeft |
| * Whether we should move the value in the left or right side. |
| */ |
| protected void moveElement(ReferenceChange diff, boolean rightToLeft) { |
| final Comparison comparison = diff.getMatch().getComparison(); |
| final Match valueMatch = comparison.getMatch(diff.getValue()); |
| final EReference reference = diff.getReference(); |
| |
| final EObject expectedContainer; |
| if (reference.isContainment()) { |
| /* |
| * We cannot "trust" the holding match (getMatch) in this case. However, "valueMatch" cannot be |
| * null : we cannot have detected a move if the moved element is not matched on both sides. Use |
| * that information to retrieve the proper "target" container. |
| */ |
| final Match targetContainerMatch; |
| // If it exists, use the source side's container as reference |
| if (rightToLeft && valueMatch.getRight() != null) { |
| targetContainerMatch = comparison.getMatch(valueMatch.getRight().eContainer()); |
| } else if (!rightToLeft && valueMatch.getLeft() != null) { |
| targetContainerMatch = comparison.getMatch(valueMatch.getLeft().eContainer()); |
| } else { |
| // Otherwise, the value we're moving on one side has been removed from its source side. |
| targetContainerMatch = comparison.getMatch(valueMatch.getOrigin().eContainer()); |
| } |
| if (rightToLeft) { |
| expectedContainer = targetContainerMatch.getLeft(); |
| } else { |
| expectedContainer = targetContainerMatch.getRight(); |
| } |
| } else if (rightToLeft) { |
| expectedContainer = diff.getMatch().getLeft(); |
| } else { |
| expectedContainer = diff.getMatch().getRight(); |
| } |
| if (expectedContainer == null) { |
| throw new IllegalStateException( |
| "Couldn't move element because its parent hasn't been merged yet: " + diff); //$NON-NLS-1$ |
| } |
| |
| final EObject expectedValue; |
| if (valueMatch == null) { |
| // The value being moved is out of the scope |
| /* |
| * Note : there should not be a way to end up with a "move" for an out of scope value : a move can |
| * only be detected if the object is matched on both sides, otherwise all we can see is "add" and |
| * "delete"... Is this "fallback" code even reachable? If so, how? |
| */ |
| // We need to look it up |
| if (reference.isMany()) { |
| @SuppressWarnings("unchecked") |
| final List<EObject> targetList = (List<EObject>)safeEGet(expectedContainer, reference); |
| expectedValue = findMatchIn(comparison, targetList, diff.getValue()); |
| } else { |
| expectedValue = (EObject)safeEGet(expectedContainer, reference); |
| } |
| } else { |
| if (rightToLeft) { |
| expectedValue = valueMatch.getLeft(); |
| } else { |
| expectedValue = valueMatch.getRight(); |
| } |
| } |
| // If expectedValue is null at this point, we have to copy the value from the other side. |
| // It can happens with a move between the ancestor and one side, while the other side doesn't has the |
| // value. |
| if (expectedValue == null) { |
| addInTarget(diff, rightToLeft); |
| } else { |
| // We now know the target container, target reference and target value. |
| doMove(diff, comparison, expectedContainer, expectedValue, rightToLeft); |
| } |
| } |
| |
| /** |
| * This will do the actual work of moving the element into its reference. All sanity checks were made in |
| * {@link #moveElement(boolean)} and no more verification will be made here. |
| * |
| * @param diff |
| * The diff we are currently merging. |
| * @param comparison |
| * Comparison holding this Diff. |
| * @param expectedContainer |
| * The container in which we are reorganizing a reference. |
| * @param expectedValue |
| * The value that is to be moved within its reference. |
| * @param rightToLeft |
| * Whether we should move the value in the left or right side. |
| */ |
| @SuppressWarnings("unchecked") |
| protected void doMove(ReferenceChange diff, Comparison comparison, EObject expectedContainer, |
| EObject expectedValue, boolean rightToLeft) { |
| final EReference reference = getMoveTargetReference(comparison, diff, rightToLeft); |
| |
| if (reference.isMany()) { |
| // Element to move cannot be part of the LCS... or there would not be a MOVE diff |
| int insertionIndex = findInsertionIndex(comparison, diff, rightToLeft); |
| |
| /* |
| * However, it could still have been located "before" its new index, in which case we need to take |
| * it into account. |
| */ |
| final List<EObject> targetList = (List<EObject>)safeEGet(expectedContainer, reference); |
| final int currentIndex = targetList.indexOf(expectedValue); |
| if (insertionIndex > currentIndex && currentIndex >= 0) { |
| insertionIndex--; |
| } |
| |
| if (currentIndex == -1) { |
| // happens for container changes for example. |
| if (!reference.isContainment()) { |
| targetList.remove(expectedValue); |
| } |
| if (insertionIndex < 0 || insertionIndex > targetList.size()) { |
| targetList.add(expectedValue); |
| } else { |
| targetList.add(insertionIndex, expectedValue); |
| } |
| } else if (targetList instanceof EList<?>) { |
| if (insertionIndex < 0 || insertionIndex >= targetList.size()) { |
| ((EList<EObject>)targetList).move(targetList.size() - 1, expectedValue); |
| } else { |
| ((EList<EObject>)targetList).move(insertionIndex, expectedValue); |
| } |
| } else { |
| targetList.remove(expectedValue); |
| if (insertionIndex < 0 || insertionIndex >= targetList.size()) { |
| targetList.add(expectedValue); |
| } else { |
| targetList.add(insertionIndex, expectedValue); |
| } |
| } |
| } else { |
| safeESet(expectedContainer, reference, expectedValue); |
| } |
| } |
| |
| /** |
| * Returns the reference of the target container in case of a MOVE Diff. |
| * |
| * @param comparison |
| * the comparison object holding the given Diff. |
| * @param diff |
| * the given Diff. |
| * @param rightToLeft |
| * whether we should move the value in the left or right side. |
| * @return the reference of the target container in case of a MOVE Diff. |
| */ |
| private EReference getMoveTargetReference(Comparison comparison, ReferenceChange diff, |
| boolean rightToLeft) { |
| final EReference reference; |
| final DifferenceSource source = diff.getSource(); |
| final Match valueMatch = comparison.getMatch(diff.getValue()); |
| if (!diff.getReference().isContainment() || valueMatch == null) { |
| reference = diff.getReference(); |
| } else if (rightToLeft && source == DifferenceSource.LEFT) { |
| EObject sourceValue = valueMatch.getRight(); |
| if (sourceValue == null) { |
| sourceValue = valueMatch.getOrigin(); |
| } |
| EStructuralFeature feature = sourceValue.eContainingFeature(); |
| if (feature instanceof EReference) { |
| reference = (EReference)feature; |
| } else { |
| // FIXME Manage this case. See javadoc of eContainingFeature. This is possible and will happen |
| // with feature maps. http: |
| // //download.eclipse.org/modeling/emf/emf/javadoc/2.8.0/org/eclipse/emf/ecore/EObject.html#eContainingFeature%28%29 |
| reference = diff.getReference(); |
| } |
| } else if (!rightToLeft && source == DifferenceSource.RIGHT) { |
| EObject sourceValue = valueMatch.getLeft(); |
| if (sourceValue == null) { |
| sourceValue = valueMatch.getOrigin(); |
| } |
| EStructuralFeature feature = sourceValue.eContainingFeature(); |
| if (feature instanceof EReference) { |
| reference = (EReference)feature; |
| } else { |
| // FIXME Manage this case. See javadoc of eContainingFeature. This is possible and will happen |
| // with feature maps. http: |
| // //download.eclipse.org/modeling/emf/emf/javadoc/2.8.0/org/eclipse/emf/ecore/EObject.html#eContainingFeature%28%29 |
| reference = diff.getReference(); |
| } |
| } else { |
| reference = diff.getReference(); |
| } |
| return reference; |
| } |
| |
| /** |
| * This will be called when we need to create an element in the target side. |
| * <p> |
| * All necessary sanity checks have been made to ensure that the current operation is one that should |
| * create an object in its side or add an objet to a reference. In other words, either : |
| * <ul> |
| * <li>We are copying from right to left and |
| * <ul> |
| * <li>we are copying an addition to the right side (we need to create the same object in the left), or |
| * </li> |
| * <li>we are copying a deletion from the left side (we need to revert the deletion).</li> |
| * </ul> |
| * </li> |
| * <li>We are copying from left to right and |
| * <ul> |
| * <li>we are copying a deletion from the right side (we need to revert the deletion), or</li> |
| * <li>we are copying an addition to the left side (we need to create the same object in the right).</li> |
| * </ul> |
| * </li> |
| * </ul> |
| * </p> |
| * |
| * @param diff |
| * The diff we are currently merging. |
| * @param rightToLeft |
| * Tells us whether we are to add an object on the left or right side. |
| */ |
| @SuppressWarnings("unchecked") |
| protected void addInTarget(ReferenceChange diff, boolean rightToLeft) { |
| final Match match = diff.getMatch(); |
| final EObject expectedContainer; |
| if (rightToLeft) { |
| expectedContainer = match.getLeft(); |
| } else { |
| expectedContainer = match.getRight(); |
| } |
| |
| if (expectedContainer == null) { |
| throw new IllegalStateException( |
| "Couldn't add in target because its parent hasn't been merged yet: " + diff); //$NON-NLS-1$ |
| } |
| |
| final Comparison comparison = match.getComparison(); |
| final EReference reference = diff.getReference(); |
| final EObject expectedValue; |
| final Match valueMatch = comparison.getMatch(diff.getValue()); |
| boolean needXmiId = false; |
| if (valueMatch == null) { |
| // This is an out of scope value. |
| if (diff.getValue().eIsProxy()) { |
| // Copy the proxy |
| expectedValue = EcoreUtil.copy(diff.getValue()); |
| } else { |
| // Use the same value. |
| expectedValue = diff.getValue(); |
| } |
| } else if (rightToLeft) { |
| if (valueMatch.getLeft() == null) { |
| expectedValue = createCopy(diff.getValue()); |
| valueMatch.setLeft(expectedValue); |
| needXmiId = true; |
| } else { |
| expectedValue = valueMatch.getLeft(); |
| } |
| } else { |
| if (valueMatch.getRight() == null) { |
| expectedValue = createCopy(diff.getValue()); |
| valueMatch.setRight(expectedValue); |
| needXmiId = true; |
| } else { |
| expectedValue = valueMatch.getRight(); |
| } |
| } |
| |
| // We have the container, reference and value. We need to know the insertion index. |
| if (reference.isMany()) { |
| final int insertionIndex = findInsertionIndex(comparison, diff, rightToLeft); |
| |
| final List<EObject> targetList = (List<EObject>)safeEGet(expectedContainer, reference); |
| addAt(targetList, expectedValue, insertionIndex); |
| } else { |
| safeESet(expectedContainer, reference, expectedValue); |
| } |
| |
| if (needXmiId) { |
| // Copy XMI ID when applicable. |
| final Resource initialResource = diff.getValue().eResource(); |
| final Resource targetResource = expectedContainer.eResource(); |
| if (initialResource instanceof XMIResource && targetResource instanceof XMIResource) { |
| ((XMIResource)targetResource).setID(expectedValue, |
| ((XMIResource)initialResource).getID(diff.getValue())); |
| } |
| } |
| |
| checkImpliedDiffsOrdering(diff, rightToLeft); |
| } |
| |
| /** |
| * This will be called when we need to remove an element from the target side. |
| * <p> |
| * All necessary sanity checks have been made to ensure that the current operation is one that should |
| * delete an object. In other words, we are : |
| * <ul> |
| * <li>Copying from right to left and either |
| * <ul> |
| * <li>we are copying a deletion from the right side (we need to remove the same object in the left) or, |
| * </li> |
| * <li>we are copying an addition to the left side (we need to revert the addition).</li> |
| * </ul> |
| * </li> |
| * <li>Copying from left to right and either |
| * <ul> |
| * <li>we are copying an addition to the right side (we need to revert the addition), or.</li> |
| * <li>we are copying a deletion from the left side (we need to remove the same object in the right).</li> |
| * </ul> |
| * </li> |
| * </ul> |
| * </p> |
| * |
| * @param diff |
| * The diff we are currently merging. |
| * @param rightToLeft |
| * Tells us whether we are to add an object on the left or right side. |
| */ |
| @SuppressWarnings("unchecked") |
| protected void removeFromTarget(ReferenceChange diff, boolean rightToLeft) { |
| final Match match = diff.getMatch(); |
| final EReference reference = diff.getReference(); |
| final EObject currentContainer; |
| if (rightToLeft) { |
| currentContainer = match.getLeft(); |
| } else { |
| currentContainer = match.getRight(); |
| } |
| final Comparison comparison = match.getComparison(); |
| final Match valueMatch = comparison.getMatch(diff.getValue()); |
| |
| if (currentContainer == null) { |
| // Nothing to do, parent already removed |
| return; |
| } |
| |
| final EObject expectedValue; |
| if (valueMatch == null) { |
| // value is out of the scope... we need to look it up |
| if (reference.isMany()) { |
| final List<EObject> targetList = (List<EObject>)safeEGet(currentContainer, reference); |
| expectedValue = findMatchIn(comparison, targetList, diff.getValue()); |
| } else { |
| // the value will not be needed anyway |
| expectedValue = null; |
| } |
| } else if (rightToLeft) { |
| expectedValue = valueMatch.getLeft(); |
| } else { |
| expectedValue = valueMatch.getRight(); |
| } |
| |
| // We have the container, reference and value to remove. Expected value can be null when the |
| // deletion was made on both side (i.e. a pseudo delete) |
| if (reference.isContainment() && expectedValue != null) { |
| EcoreUtil.remove(expectedValue); |
| if (rightToLeft && valueMatch != null) { |
| valueMatch.setLeft(null); |
| } else if (valueMatch != null) { |
| valueMatch.setRight(null); |
| } |
| // TODO remove dangling? remove empty Match? |
| } else if (reference.isMany()) { |
| /* |
| * TODO if the same value appears twice, should we try and find the one that has actually been |
| * deleted? Can it happen? For now, remove the first occurence we find. |
| */ |
| final List<EObject> targetList = (List<EObject>)safeEGet(currentContainer, reference); |
| targetList.remove(expectedValue); |
| } else { |
| currentContainer.eUnset(reference); |
| } |
| } |
| |
| /** |
| * This will be called by the merge operations in order to reset a reference to its original value, be |
| * that the left or right side. |
| * <p> |
| * Should never be called on multi-valued references. |
| * </p> |
| * |
| * @param diff |
| * The diff we are currently merging. |
| * @param rightToLeft |
| * Tells us the direction of this merge operation. |
| */ |
| protected void resetInTarget(ReferenceChange diff, boolean rightToLeft) { |
| final Match match = diff.getMatch(); |
| final EReference reference = diff.getReference(); |
| final EObject targetContainer; |
| if (rightToLeft) { |
| targetContainer = match.getLeft(); |
| } else { |
| targetContainer = match.getRight(); |
| } |
| |
| final EObject originContainer; |
| if (match.getComparison().isThreeWay()) { |
| originContainer = match.getOrigin(); |
| } else if (rightToLeft) { |
| originContainer = match.getRight(); |
| } else { |
| originContainer = match.getLeft(); |
| } |
| |
| if (originContainer == null || !safeEIsSet(originContainer, reference)) { |
| targetContainer.eUnset(reference); |
| } else { |
| final EObject originalValue = (EObject)safeEGet(originContainer, reference); |
| final Match valueMatch = match.getComparison().getMatch(originalValue); |
| final EObject expectedValue; |
| if (valueMatch == null) { |
| // Value is out of the scope, use it as-is |
| expectedValue = originalValue; |
| } else if (rightToLeft) { |
| expectedValue = valueMatch.getLeft(); |
| } else { |
| expectedValue = valueMatch.getRight(); |
| } |
| safeESet(targetContainer, reference, expectedValue); |
| } |
| } |
| |
| /** |
| * In the case of many-to-many eOpposite references, EMF will simply report the difference made on one |
| * side of the equivalence to the other, without considering ordering in any way. In such cases, we'll |
| * iterate over our equivalences after the merge, and double-check the ordering ourselves, fixing it as |
| * needed. |
| * <p> |
| * Note that both implied and equivalent diffs will be double-checked from here. |
| * </p> |
| * |
| * @param diff |
| * The diff we are currently merging. |
| * @param rightToLeft |
| * Direction of the merge. |
| * @since 3.1 |
| */ |
| protected void checkImpliedDiffsOrdering(ReferenceChange diff, boolean rightToLeft) { |
| final EReference reference = diff.getReference(); |
| final List<Diff> mergedImplications; |
| if (isAccepting(diff, rightToLeft)) { |
| mergedImplications = diff.getImplies(); |
| } else { |
| mergedImplications = diff.getImpliedBy(); |
| } |
| |
| Iterator<Diff> impliedDiffs = mergedImplications.iterator(); |
| if (reference.isMany() && diff.getEquivalence() != null) { |
| impliedDiffs = Iterators.concat(impliedDiffs, diff.getEquivalence().getDifferences().iterator()); |
| } |
| final Iterator<ReferenceChange> impliedReferenceChanges = filter(impliedDiffs, ReferenceChange.class); |
| |
| while (impliedReferenceChanges.hasNext()) { |
| final ReferenceChange implied = impliedReferenceChanges.next(); |
| if (implied != diff && isInTerminalState(implied)) { |
| if (implied.getReference().isMany() && isAdd(implied, rightToLeft)) { |
| internalCheckOrdering(implied, rightToLeft); |
| checkImpliedDiffsOrdering(implied, rightToLeft); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Checks a particular difference for the ordering of its target values. This will be used to double-check |
| * that equivalent differences haven't been "broken" by EMF by not preserving their value order. |
| * <p> |
| * Should only be used on <u>merged</u> differences which target <u>many-valued</u> references. |
| * </p> |
| * |
| * @param diff |
| * The diff that is to be checked. |
| * @param rightToLeft |
| * Direction of the merge that took place. |
| */ |
| private void internalCheckOrdering(ReferenceChange diff, boolean rightToLeft) { |
| final EStructuralFeature feature = diff.getReference(); |
| final EObject value = diff.getValue(); |
| final Match match = diff.getMatch(); |
| final Comparison comparison = match.getComparison(); |
| final Match valueMatch = comparison.getMatch(value); |
| |
| final EObject sourceContainer; |
| final EObject targetContainer; |
| final EObject newValue; |
| if (rightToLeft) { |
| sourceContainer = match.getRight(); |
| targetContainer = match.getLeft(); |
| newValue = valueMatch.getLeft(); |
| } else { |
| sourceContainer = match.getLeft(); |
| targetContainer = match.getRight(); |
| newValue = valueMatch.getRight(); |
| } |
| |
| final List<Object> sourceList = ReferenceUtil.getAsList(sourceContainer, feature); |
| final List<Object> targetList = ReferenceUtil.getAsList(targetContainer, feature); |
| |
| final List<Object> lcs = DiffUtil.longestCommonSubsequence(comparison, sourceList, targetList); |
| if (lcs.contains(valueMatch.getLeft()) || lcs.contains(valueMatch.getRight())) { |
| // Ordering is correct on this one |
| return; |
| } |
| |
| int insertionIndex = DiffUtil.findInsertionIndex(comparison, sourceList, targetList, value); |
| if (insertionIndex >= 0) { |
| /* |
| * We've used unresolving views of the eobject lists since we didn't know whether there was |
| * actually any work to do. Use the real list now. |
| */ |
| @SuppressWarnings("unchecked") |
| final List<EObject> changedList = (List<EObject>)safeEGet(targetContainer, feature); |
| if (changedList.size() > 1) { |
| if (changedList instanceof EList<?>) { |
| if (insertionIndex > changedList.size()) { |
| ((EList<EObject>)changedList).move(changedList.size() - 1, newValue); |
| } else { |
| ((EList<EObject>)changedList).move(insertionIndex, newValue); |
| } |
| } else { |
| changedList.remove(newValue); |
| if (insertionIndex > changedList.size()) { |
| changedList.add(newValue); |
| } else { |
| changedList.add(insertionIndex, newValue); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Seeks a match of the given {@code element} in the given list, using the equality helper to find it. |
| * This is only used when moving or deleting proxies for now. |
| * |
| * @param comparison |
| * The comparison which Diff we are currently merging. |
| * @param list |
| * The list from which we seek a value. |
| * @param element |
| * The value for which we need a match in {@code list}. |
| * @return The match of {@code element} in {@code list}, {@code null} if none. |
| */ |
| protected EObject findMatchIn(Comparison comparison, List<EObject> list, EObject element) { |
| final IEqualityHelper helper = comparison.getEqualityHelper(); |
| final Iterator<EObject> it = list.iterator(); |
| while (it.hasNext()) { |
| final EObject next = it.next(); |
| if (helper.matchingValues(next, element)) { |
| return next; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * This will be used by the distinct merge actions in order to find the index at which a value should be |
| * inserted in its target list. See {@link DiffUtil#findInsertionIndex(Comparison, Diff, boolean)} for |
| * more on this. |
| * <p> |
| * Sub-classes can override this if the insertion order is irrelevant. A return value of {@code -1} will |
| * be considered as "no index" and the value will be inserted at the end of its target list. |
| * </p> |
| * |
| * @param comparison |
| * This will be used in order to retrieve the Match for EObjects when comparing them. |
| * @param diff |
| * The diff which merging will trigger the need for an insertion index in its target list. |
| * @param rightToLeft |
| * {@code true} if the merging will be done into the left list, so that we should consider the |
| * right model as the source and the left as the target. |
| * @return The index at which this {@code diff}'s value should be inserted into the 'target' list, as |
| * inferred from {@code rightToLeft}. {@code -1} if the value should be inserted at the end of its |
| * target list. |
| * @see DiffUtil#findInsertionIndex(Comparison, Diff, boolean) |
| */ |
| protected int findInsertionIndex(Comparison comparison, Diff diff, boolean rightToLeft) { |
| return DiffUtil.findInsertionIndex(comparison, diff, rightToLeft); |
| } |
| } |