Index issue when merging non-unique attributes' elements
[EMFCompare2.git] / plugins / org.eclipse.emf.compare / src / org / eclipse / emf / compare / merge / AttributeChangeMerger.java
blob11c53cc4b8c8aa6c392787c0e3bf57adb2bc8599
1 /*******************************************************************************
2 * Copyright (c) 2012, 2015 Obeo and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 * Obeo - initial API and implementation
10 * Philip Langer - [446947, 458147] Support for three-way text merging
11 *******************************************************************************/
12 package org.eclipse.emf.compare.merge;
14 import static org.eclipse.emf.compare.utils.ReferenceUtil.safeEGet;
15 import static org.eclipse.emf.compare.utils.ReferenceUtil.safeEIsSet;
16 import static org.eclipse.emf.compare.utils.ReferenceUtil.safeESet;
18 import java.util.ArrayList;
19 import java.util.Collections;
20 import java.util.Iterator;
21 import java.util.List;
22 import java.util.Set;
24 import org.eclipse.emf.common.util.EList;
25 import org.eclipse.emf.compare.AttributeChange;
26 import org.eclipse.emf.compare.Comparison;
27 import org.eclipse.emf.compare.Diff;
28 import org.eclipse.emf.compare.DifferenceKind;
29 import org.eclipse.emf.compare.DifferenceSource;
30 import org.eclipse.emf.compare.DifferenceState;
31 import org.eclipse.emf.compare.Match;
32 import org.eclipse.emf.compare.internal.ThreeWayTextDiff;
33 import org.eclipse.emf.compare.internal.utils.DiffUtil;
34 import org.eclipse.emf.compare.utils.IEqualityHelper;
35 import org.eclipse.emf.ecore.EAttribute;
36 import org.eclipse.emf.ecore.EEnum;
37 import org.eclipse.emf.ecore.EEnumLiteral;
38 import org.eclipse.emf.ecore.ENamedElement;
39 import org.eclipse.emf.ecore.EObject;
40 import org.eclipse.emf.ecore.EStructuralFeature;
41 import org.eclipse.emf.ecore.impl.DynamicEObjectImpl;
43 /**
44 * This specific implementation of {@link AbstractMerger} will be used to merge attribute changes.
46 * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
48 public class AttributeChangeMerger extends AbstractMerger {
49 /**
50 * {@inheritDoc}
52 * @see org.eclipse.emf.compare.merge.IMerger#isMergerFor(org.eclipse.emf.compare.Diff)
54 public boolean isMergerFor(Diff target) {
55 return target instanceof AttributeChange;
58 /**
59 * {@inheritDoc}
61 * @see org.eclipse.emf.compare.merge.AbstractMerger#accept(org.eclipse.emf.compare.Diff, boolean)
63 @Override
64 protected void accept(final Diff diff, boolean rightToLeft) {
65 AttributeChange attributeChange = (AttributeChange)diff;
66 switch (diff.getKind()) {
67 case ADD:
68 // Create the same element in right
69 addInTarget(attributeChange, rightToLeft);
70 break;
71 case DELETE:
72 // Delete that same element from right
73 removeFromTarget(attributeChange, rightToLeft);
74 break;
75 case MOVE:
76 moveElement(attributeChange, rightToLeft);
77 break;
78 case CHANGE:
79 changeValue(attributeChange, rightToLeft);
80 break;
81 default:
82 break;
86 /**
87 * {@inheritDoc}
89 * @see org.eclipse.emf.compare.merge.AbstractMerger#reject(org.eclipse.emf.compare.Diff, boolean)
91 @Override
92 protected void reject(Diff diff, boolean rightToLeft) {
93 AttributeChange attributeChange = (AttributeChange)diff;
94 switch (diff.getKind()) {
95 case ADD:
96 // We have a ADD on right. we need to revert this addition
97 removeFromTarget(attributeChange, rightToLeft);
98 break;
99 case DELETE:
100 // DELETE in the right. We need to re-create this element
101 addInTarget(attributeChange, rightToLeft);
102 break;
103 case MOVE:
104 moveElement(attributeChange, rightToLeft);
105 break;
106 case CHANGE:
107 changeValue(attributeChange, rightToLeft);
108 break;
109 default:
110 break;
115 * This will be called when we need to create an element in the target side.
116 * <p>
117 * All necessary sanity checks have been made to ensure that the current operation is one that should
118 * create an object in its side or add an objet to an attribute. In other words, either :
119 * <ul>
120 * <li>We are copying from right to left and
121 * <ul>
122 * <li>we are copying an addition to the right side (we need to create the same object in the left), or
123 * </li>
124 * <li>we are copying a deletion from the left side (we need to revert the deletion).</li>
125 * </ul>
126 * </li>
127 * <li>We are copying from left to right and
128 * <ul>
129 * <li>we are copying a deletion from the right side (we need to revert the deletion), or</li>
130 * <li>we are copying an addition to the left side (we need to create the same object in the right).</li>
131 * </ul>
132 * </li>
133 * </ul>
134 * </p>
136 * @param diff
137 * The diff we are currently merging.
138 * @param rightToLeft
139 * Tells us whether we are to add an object on the left or right side.
141 @SuppressWarnings("unchecked")
142 protected void addInTarget(AttributeChange diff, boolean rightToLeft) {
143 final Match match = diff.getMatch();
144 final EObject targetContainer = getTargetContainer(diff, rightToLeft);
146 if (targetContainer == null) {
147 throw new IllegalStateException(
148 "Couldn't add in target because its parent hasn't been merged yet: " + diff); //$NON-NLS-1$
150 final Comparison comparison = match.getComparison();
151 final EStructuralFeature attribute = diff.getAttribute();
152 final Object expectedValue = diff.getValue();
153 // We have the container, attribute and value. We need to know the insertion index.
154 if (attribute.isMany()) {
155 final int insertionIndex = findInsertionIndex(comparison, diff, rightToLeft);
157 final List<Object> targetList = (List<Object>)safeEGet(targetContainer, attribute);
158 addAt(targetList, expectedValue, insertionIndex);
159 } else {
160 safeESet(targetContainer, attribute, expectedValue);
165 * This will be called when we need to remove an element from the target side.
166 * <p>
167 * All necessary sanity checks have been made to ensure that the current operation is one that should
168 * delete an object. In other words, we are :
169 * <ul>
170 * <li>Copying from right to left and either
171 * <ul>
172 * <li>we are copying a deletion from the right side (we need to remove the same object in the left) or,
173 * </li>
174 * <li>we are copying an addition to the left side (we need to revert the addition).</li>
175 * </ul>
176 * </li>
177 * <li>Copying from left to right and either
178 * <ul>
179 * <li>we are copying an addition to the right side (we need to revert the addition), or.</li>
180 * <li>we are copying a deletion from the left side (we need to remove the same object in the right).</li>
181 * </ul>
182 * </li>
183 * </ul>
184 * </p>
186 * @param diff
187 * The diff we are currently merging.
188 * @param rightToLeft
189 * Tells us whether we are to add an object on the left or right side.
191 @SuppressWarnings("unchecked")
192 protected void removeFromTarget(AttributeChange diff, boolean rightToLeft) {
193 final EObject currentContainer = getTargetContainer(diff, rightToLeft);
195 if (currentContainer != null) {
196 final Object expectedValue = diff.getValue();
197 final EStructuralFeature attribute = diff.getAttribute();
198 // We have the container, attribute and value to remove.
199 if (attribute.isMany()) {
200 if (attribute.isUnique()) {
201 final List<Object> targetList = (List<Object>)safeEGet(currentContainer, attribute);
202 targetList.remove(expectedValue);
203 } else {
204 removeNonUniqueFromTarget(diff, rightToLeft);
206 } else {
207 currentContainer.eUnset(attribute);
212 @SuppressWarnings("unchecked")
213 private void removeNonUniqueFromTarget(AttributeChange diff, boolean rightToLeft) {
214 Comparison comparison = diff.getMatch().getComparison();
215 IEqualityHelper equalityHelper = comparison.getEqualityHelper();
216 EAttribute attribute = diff.getAttribute();
218 EObject sourceContainer;
219 EObject targetContainer;
220 if (rightToLeft) {
221 sourceContainer = diff.getMatch().getRight();
222 targetContainer = diff.getMatch().getLeft();
223 } else {
224 sourceContainer = diff.getMatch().getLeft();
225 targetContainer = diff.getMatch().getRight();
228 List<Object> sourceList = (List<Object>)safeEGet(sourceContainer, attribute);
229 List<Object> targetList = (List<Object>)safeEGet(targetContainer, attribute);
230 Object valueToRemove = diff.getValue();
232 List<Object> lcs = DiffUtil.longestCommonSubsequence(comparison, sourceList, targetList);
234 // The current index, in the target list, of that value
235 int currentIndexInTarget = -1;
236 Iterator<Object> lcsIteratorForTargetLookup = lcs.iterator();
237 Object currentLCS = null;
238 if (lcsIteratorForTargetLookup.hasNext()) {
239 currentLCS = lcsIteratorForTargetLookup.next();
241 for (int j = 0; j < targetList.size() && currentIndexInTarget == -1; j++) {
242 if (currentLCS == null) {
243 // we've iterated on our whole LCS.
244 // first instance of our target is the one to move.
245 if (equalityHelper.matchingAttributeValues(targetList.get(j), valueToRemove)) {
246 currentIndexInTarget = j;
248 } else if (equalityHelper.matchingAttributeValues(targetList.get(j), currentLCS)) {
249 // this matches our current lcs item. continue to next one if any
250 if (lcsIteratorForTargetLookup.hasNext()) {
251 currentLCS = lcsIteratorForTargetLookup.next();
252 } else {
253 currentLCS = null;
255 } else {
256 // This item is not in our LCS. Is it the one we're looking for?
257 if (equalityHelper.matchingAttributeValues(targetList.get(j), valueToRemove)) {
258 currentIndexInTarget = j;
262 // value might not exist in target list if it has already been removed or if this was a pseudo
263 // conflict deletion
264 if (currentIndexInTarget >= 0) {
265 targetList.remove(currentIndexInTarget);
270 * This will be called when trying to copy a "MOVE" diff.
272 * @param diff
273 * The diff we are currently merging.
274 * @param rightToLeft
275 * Whether we should move the value in the left or right side.
277 protected void moveElement(AttributeChange diff, boolean rightToLeft) {
278 final EObject expectedContainer = getTargetContainer(diff, rightToLeft);
280 if (expectedContainer == null) {
281 throw new IllegalStateException(
282 "Couldn't move element because its target parent hasn't been merged yet: " + diff); //$NON-NLS-1$
284 final Comparison comparison = diff.getMatch().getComparison();
285 final Object expectedValue = diff.getValue();
287 // We now know the target container, target attribute and target value.
288 doMove(diff, comparison, expectedContainer, expectedValue, rightToLeft);
292 * This will do the actual work of moving the element into its attribute. All sanity checks were made in
293 * {@link #moveElement(boolean)} and no more verification will be made here.
295 * @param diff
296 * The diff we are currently merging.
297 * @param comparison
298 * Comparison holding this Diff.
299 * @param expectedContainer
300 * The container in which we are reorganizing an attribute.
301 * @param expectedValue
302 * The value that is to be moved within its attribute.
303 * @param rightToLeft
304 * Whether we should move the value in the left or right side.
306 protected void doMove(AttributeChange diff, Comparison comparison, EObject expectedContainer,
307 Object expectedValue, boolean rightToLeft) {
308 final EStructuralFeature attribute = diff.getAttribute();
309 if (attribute.isMany()) {
310 if (!attribute.isUnique()) {
311 doMoveNonUniqueAttribute(diff, comparison, rightToLeft);
312 } else {
313 doMoveUniqueAttribute(diff, comparison, expectedContainer, expectedValue, rightToLeft);
315 } else {
316 // This will never happen with the default diff engine, but may still be done from extenders
317 safeESet(expectedContainer, attribute, expectedValue);
321 @SuppressWarnings("unchecked")
322 private void doMoveUniqueAttribute(AttributeChange diff, Comparison comparison, EObject expectedContainer,
323 Object expectedValue, boolean rightToLeft) {
324 final EStructuralFeature attribute = diff.getAttribute();
326 // Element to move cannot be part of the LCS... or there would not be a MOVE diff
327 int insertionIndex = findInsertionIndex(comparison, diff, rightToLeft);
329 * However, it could still have been located "before" its new index, in which case we need to take it
330 * into account.
332 final List<Object> targetList = (List<Object>)safeEGet(expectedContainer, attribute);
333 final int currentIndex = targetList.indexOf(expectedValue);
334 if (insertionIndex > currentIndex) {
335 insertionIndex--;
338 if (targetList instanceof EList<?>) {
339 if (insertionIndex < 0 || insertionIndex >= targetList.size()) {
340 ((EList<Object>)targetList).move(targetList.size() - 1, expectedValue);
341 } else {
342 ((EList<Object>)targetList).move(insertionIndex, expectedValue);
344 } else {
345 targetList.remove(expectedValue);
346 if (insertionIndex < 0 || insertionIndex > targetList.size()) {
347 targetList.add(expectedValue);
348 } else {
349 targetList.add(insertionIndex, expectedValue);
354 @SuppressWarnings("unchecked")
355 private void doMoveNonUniqueAttribute(AttributeChange diff, Comparison comparison, boolean rightToLeft) {
356 IEqualityHelper equalityHelper = comparison.getEqualityHelper();
357 EAttribute attribute = diff.getAttribute();
359 EObject sourceContainer;
360 EObject targetContainer;
361 if (rightToLeft) {
362 sourceContainer = diff.getMatch().getRight();
363 targetContainer = diff.getMatch().getLeft();
364 } else {
365 sourceContainer = diff.getMatch().getLeft();
366 targetContainer = diff.getMatch().getRight();
369 List<Object> sourceList = (List<Object>)safeEGet(sourceContainer, attribute);
370 List<Object> targetList = (List<Object>)safeEGet(targetContainer, attribute);
371 // copy this one : we'll have to modify target while iterating over this view
372 List<Object> copyTarget = new ArrayList<>(targetList);
373 Object valueToMove = diff.getValue();
375 Set<Object> ignoredElements = DiffUtil.computeIgnoredElements(comparison, equalityHelper, targetList,
376 diff, rightToLeft);
377 // We're "moving" an element, so it is present on both sides and should not be part of the LCS
378 // computation since it is our move target. However, if we also have a diff on that same value, we do
379 // not have to ignore it.
380 Iterator<Diff> siblingDiffs = diff.getMatch().getDifferences().stream()
381 .filter(AttributeChange.class::isInstance)
382 .filter(d -> d.getState() == DifferenceState.UNRESOLVED && equalityHelper
383 .matchingAttributeValues(valueToMove, ((AttributeChange)d).getValue()))
384 .iterator();
385 boolean ignoreValue = true;
386 while (siblingDiffs.hasNext()) {
387 Diff sibling = siblingDiffs.next();
388 if (sibling.getKind() == DifferenceKind.ADD) {
389 // We have another duplicate of that value on this side that corresponds to none on the other
390 if (sibling.getSource() == DifferenceSource.LEFT && rightToLeft) {
391 ignoreValue = false;
392 } else if (sibling.getSource() == DifferenceSource.RIGHT && !rightToLeft) {
393 ignoreValue = false;
395 } else if (sibling.getKind() == DifferenceKind.DELETE) {
396 // reverse the above
397 if (sibling.getSource() == DifferenceSource.LEFT && !rightToLeft) {
398 ignoreValue = false;
399 } else if (sibling.getSource() == DifferenceSource.RIGHT && rightToLeft) {
400 ignoreValue = false;
404 if (ignoreValue) {
405 if (ignoredElements.isEmpty()) {
406 ignoredElements = Collections.singleton(valueToMove);
407 } else {
408 ignoredElements.add(valueToMove);
411 List<Object> lcs = DiffUtil.longestCommonSubsequence(comparison, ignoredElements, sourceList,
412 copyTarget);
414 // We need to find the current index, in the source list, of the value to move
415 int currentIndexInSource = -1;
416 Iterator<Object> lcsIteratorForSourceLookup = lcs.iterator();
417 Object currentLCS = null;
418 if (lcsIteratorForSourceLookup.hasNext()) {
419 currentLCS = lcsIteratorForSourceLookup.next();
421 for (int j = 0; j < sourceList.size() && currentIndexInSource == -1; j++) {
422 if (currentLCS == null) {
423 // we've iterated on our whole LCS.
424 // first instance of our target is the one to move.
425 if (equalityHelper.matchingAttributeValues(sourceList.get(j), valueToMove)) {
426 currentIndexInSource = j;
428 } else if (equalityHelper.matchingAttributeValues(sourceList.get(j), currentLCS)) {
429 // this matches our current lcs item. continue to next one if any
430 if (lcsIteratorForSourceLookup.hasNext()) {
431 currentLCS = lcsIteratorForSourceLookup.next();
432 } else {
433 currentLCS = null;
435 } else {
436 // This item is not in our LCS. Is it the one we're looking for?
437 if (equalityHelper.matchingAttributeValues(sourceList.get(j), valueToMove)) {
438 currentIndexInSource = j;
442 // The current index, in the target list, of that value
443 int currentIndexInTarget = -1;
444 Iterator<Object> lcsIteratorForTargetLookup = lcs.iterator();
445 currentLCS = null;
446 if (lcsIteratorForTargetLookup.hasNext()) {
447 currentLCS = lcsIteratorForTargetLookup.next();
449 for (int j = 0; j < targetList.size() && currentIndexInTarget == -1; j++) {
450 if (currentLCS == null) {
451 // we've iterated on our whole LCS.
452 // first instance of our target is the one to move.
453 if (equalityHelper.matchingAttributeValues(targetList.get(j), valueToMove)) {
454 currentIndexInTarget = j;
456 } else if (equalityHelper.matchingAttributeValues(targetList.get(j), currentLCS)) {
457 // this matches our current lcs item. continue to next one if any
458 if (lcsIteratorForTargetLookup.hasNext()) {
459 currentLCS = lcsIteratorForTargetLookup.next();
460 } else {
461 currentLCS = null;
463 } else {
464 // This item is not in our LCS. Is it the one we're looking for?
465 if (equalityHelper.matchingAttributeValues(targetList.get(j), valueToMove)) {
466 currentIndexInTarget = j;
471 // Then the index, in the target, at which this value needs to be moved
472 int insertionIndex = DiffUtil.findInsertionIndexForElementAt(comparison, sourceList, copyTarget, lcs,
473 currentIndexInSource);
475 * However, it could still have been located "before" its new index, in which case we need to take it
476 * into account.
478 if (insertionIndex > currentIndexInTarget) {
479 insertionIndex--;
482 if (targetList instanceof EList<?>) {
483 if (insertionIndex < 0 || insertionIndex >= targetList.size()) {
484 ((EList<Object>)targetList).move(targetList.size() - 1, currentIndexInTarget);
485 } else {
486 ((EList<Object>)targetList).move(insertionIndex, currentIndexInTarget);
488 } else {
489 targetList.remove(currentIndexInTarget);
490 if (insertionIndex < 0 || insertionIndex > targetList.size()) {
491 targetList.add(valueToMove);
492 } else {
493 targetList.add(insertionIndex, valueToMove);
499 * This will be called by the merge operations in order to reset an attribute to its original value, be
500 * that the left or right side.
501 * <p>
502 * Should never be called on multi-valued attributes.
503 * </p>
505 * @param diff
506 * The diff we are currently merging.
507 * @param rightToLeft
508 * Tells us the direction of this merge operation.
509 * @deprecated this has been refactored into {@link #changeValue(AttributeChange, boolean)}.
511 @Deprecated
512 protected void resetInTarget(AttributeChange diff, boolean rightToLeft) {
513 final EStructuralFeature attribute = diff.getAttribute();
514 final EObject targetContainer = getTargetContainer(diff, rightToLeft);
515 final EObject sourceContainer = getSourceContainer(diff, rightToLeft);
517 if (sourceContainer == null || !safeEIsSet(targetContainer, attribute)
518 || !safeEIsSet(sourceContainer, attribute)) {
519 targetContainer.eUnset(attribute);
520 } else {
521 final Object expectedValue = safeEGet(sourceContainer, attribute);
522 safeESet(targetContainer, attribute, expectedValue);
527 * This will be called by the merge operations in order to change single-valued attributes.
529 * @param diff
530 * The diff we are currently merging.
531 * @param rightToLeft
532 * Direction of the merge.
534 protected void changeValue(AttributeChange diff, boolean rightToLeft) {
535 final EObject targetContainer = getTargetContainer(diff, rightToLeft);
536 if (targetContainer == null) {
537 // We're merging the unset of an attribute value under a containment deletion.
538 // There's actually no action to take in such a case.
539 return;
542 final EStructuralFeature attribute = diff.getAttribute();
543 final Object targetValue = getTargetValue(diff, rightToLeft);
545 if (isUnset(diff, targetValue)) {
546 targetContainer.eUnset(attribute);
547 } else {
548 safeESet(targetContainer, attribute, targetValue);
553 * Returns the target value, that is, the value to be set when merging the given {@code diff} in the
554 * direction indicated by {@code rightToLeft}.
556 * @param diff
557 * The diff we are currently merging.
558 * @param rightToLeft
559 * Direction of the merge.
560 * @return The target value to be set when merging.
562 private Object getTargetValue(AttributeChange diff, boolean rightToLeft) {
563 final EStructuralFeature attribute = diff.getAttribute();
564 final EObject targetContainer = getTargetContainer(diff, rightToLeft);
565 final EObject sourceContainer = getSourceContainer(diff, rightToLeft);
566 final Object sourceValue = safeEGet(sourceContainer, attribute);
568 final Object targetValue;
569 if (isEnumChangeOfDynamicEObject(diff, targetContainer)) {
570 final EEnum eEnum = getAttributeTypeEnumFromDynamicObject(targetContainer, attribute);
571 targetValue = eEnum.getEEnumLiteral(((ENamedElement)sourceValue).getName());
572 } else if (requireThreeWayTextMerge(diff)) {
573 targetValue = performThreeWayTextMerge(diff, rightToLeft);
574 } else {
575 targetValue = sourceValue;
578 return targetValue;
582 * Specifies whether the given {@code diff} concerns a change of a dynamic EObject at an attribute with an
583 * EEnum type.
585 * @param diff
586 * The diff to check.
587 * @param targetContainer
588 * The target container.
589 * @return <code>true</code> if is a change of a dynamic EObject with an EEnum-typed attribute,
590 * <code>false</code> otherwise.
592 private boolean isEnumChangeOfDynamicEObject(AttributeChange diff, EObject targetContainer) {
593 return diff.getAttribute().getEType() instanceof EEnum
594 && targetContainer instanceof DynamicEObjectImpl;
598 * Returns the EEnum that is the attribute type of the given {@code attribute} used in the given container
599 * object {@code dynamicObject}.
601 * @param dynamicObject
602 * The container object.
603 * @param attribute
604 * The attribute.
605 * @return The EEnum acting as type of {@code attribute}.
607 private EEnum getAttributeTypeEnumFromDynamicObject(EObject dynamicObject, EStructuralFeature attribute) {
608 return (EEnum)((EEnumLiteral)safeEGet(dynamicObject, attribute)).eContainer();
612 * Returns the source container, that is the original container holding the original value before the
613 * given {@code diff} is applied. This is different depending on direction indicated by
614 * {@code rightToLeft}.
616 * @param diff
617 * The diff we are currently merging.
618 * @param rightToLeft
619 * Direction of the merge.
620 * @return The source container.
622 private EObject getSourceContainer(AttributeChange diff, boolean rightToLeft) {
623 final EObject sourceContainer;
624 final Match match = diff.getMatch();
625 if (match.getComparison().isThreeWay() && isRejectingChange(diff, rightToLeft)) {
626 sourceContainer = match.getOrigin();
627 } else if (rightToLeft) {
628 sourceContainer = match.getRight();
629 } else {
630 sourceContainer = match.getLeft();
632 return sourceContainer;
636 * Returns the target container, that is the container holding the value to be updated when merging the
637 * given {@code diff} in the direction indicated by {@code rightToLeft}.
639 * @param diff
640 * The diff we are currently merging.
641 * @param rightToLeft
642 * Direction of the merge.
643 * @return The target container to be updated when merging.
645 private EObject getTargetContainer(AttributeChange diff, boolean rightToLeft) {
646 final Match match = diff.getMatch();
647 if (rightToLeft) {
648 return match.getLeft();
649 } else {
650 return match.getRight();
655 * Specifies whether the given {@code diff} unsets the attribute value when updating the attribute with
656 * the given {@code targetValue}.
658 * @param diff
659 * The difference to check.
660 * @param targetValue
661 * The value to be set.
662 * @return <code>true</code> if setting {@code targetValue} is an unset, <code>false</code> otherwise.
664 private boolean isUnset(AttributeChange diff, Object targetValue) {
665 final Object defaultValue = diff.getAttribute().getDefaultValue();
666 return targetValue == null || targetValue.equals(defaultValue)
667 || (defaultValue == null && "".equals(targetValue)); //$NON-NLS-1$
671 * Specifies whether a three-way text merge is required for applying the given {@code diff} in the
672 * direction indicated in {@code rightToLeft}.
673 * <p>
674 * Three-way text merging is required when accepting or rejecting the changes of a
675 * {@link #isStringAttribute(EAttribute) String attributes} in a three-way merge scenario.
676 * </p>
678 * @param diff
679 * The diff to be applied.
680 * @return <code>true</code> if three-way text merging is required, <code>false</code> otherwise.
682 private boolean requireThreeWayTextMerge(AttributeChange diff) {
683 return diff.getMatch().getComparison().isThreeWay() && isStringAttribute(diff.getAttribute());
687 * Specifies whether the given {@code attribute} is an attribute of type String.
689 * @param attribute
690 * The attribute to be checked.
691 * @return <code>true</code> if it is a String attribute, <code>false</code> otherwise.
693 private boolean isStringAttribute(EAttribute attribute) {
694 return attribute.getEAttributeType().getInstanceClass() == String.class;
698 * Specifies whether applying the given {@code diff} in the direction indicated in {@code rightToLeft}
699 * means accepting the change as opposed to rejecting the change.
701 * @param diff
702 * The diff to be checked.
703 * @param rightToLeft
704 * The direction of applying {@code diff}.
705 * @return <code>true</code> if it means accepting the change, <code>false</code> otherwise.
707 private boolean isAcceptingChange(AttributeChange diff, boolean rightToLeft) {
708 return (DifferenceSource.LEFT.equals(diff.getSource()) && !rightToLeft)
709 || (DifferenceSource.RIGHT.equals(diff.getSource()) && rightToLeft);
713 * Specifies whether applying the given {@code diff} in the direction indicated in {@code rightToLeft}
714 * means rejecting the change as opposed to accepting the change.
716 * @param diff
717 * The diff to be checked.
718 * @param rightToLeft
719 * The direction of applying {@code diff}.
720 * @return <code>true</code> if it means rejecting the change, <code>false</code> otherwise.
722 private boolean isRejectingChange(AttributeChange diff, boolean rightToLeft) {
723 return !isAcceptingChange(diff, rightToLeft);
727 * Performs a three-way text merge for the given {@code diff} and returns the merged text.
728 * <p>
729 * Depending on whether the given {@code diff} is an accept or reject in the context of the merge
730 * direction indicated by {@code rightToLeft}, this method will perform different strategies of merging.
731 * </p>
733 * @param diff
734 * The diff for which a three-way text diff is to be performed.
735 * @param rightToLeft
736 * The direction of applying the {@code diff}.
737 * @return The merged text.
739 private String performThreeWayTextMerge(AttributeChange diff, boolean rightToLeft) {
740 if (isAcceptingChange(diff, rightToLeft)) {
741 return performAcceptingThreeWayTextMerge(diff);
742 } else {
743 return performRejectingThreeWayTextMerge(diff, rightToLeft);
748 * Performs a three-way text merge accepting the given {@code diff} and returns the merged text.
749 * <p>
750 * This method must only be called for {@link #isStringAttribute(EAttribute) String attributes}. As the
751 * three-way text merging is symmetric, the result is equal irrespectively of the direction of merging as
752 * long as applying the change {@link #isAcceptingChange(AttributeChange, boolean) means accepting it} as
753 * opposed to rejecting it.
754 * </p>
756 * @param diff
757 * The diff for which a three-way text diff is to be performed.
758 * @return The merged text.
760 private String performAcceptingThreeWayTextMerge(AttributeChange diff) {
761 final Match match = diff.getMatch();
762 final EAttribute attribute = diff.getAttribute();
763 final String originValue = (String)safeEGet(match.getOrigin(), attribute);
764 final String leftValue = (String)safeEGet(match.getLeft(), attribute);
765 final String rightValue = (String)safeEGet(match.getRight(), attribute);
767 return performThreeWayTextMerge(leftValue, rightValue, originValue);
771 * Performs a three-way text merge rejecting the given {@code diff} and returns the merged text.
772 * <p>
773 * When rejecting an attribute change, we need to undo its effects on the attribute value, which is in
774 * most of the cases done by setting the value to the original value. However, if there is a concurrent
775 * attribute change of the same attribute value at the opposite side, it might have been merged to the
776 * current side already. Therefore, we need to undo only those differences in the attribute value that
777 * come from the current to-be-rejected diff.
778 * </p>
779 * <p>
780 * This is done by applying a normal three-way merge, but instead of the origin value, left value, and
781 * right value, we compute the three-way merge as follows: as origin value, we use the value of the
782 * {@code diff}, which is the value as it was set on the respective side. As a left value, the value of
783 * the current side as it is currently stored in the model; thus, a potential merging of opposite diffs
784 * may have changed this value already. And as a right value, we use the actual origin value from the
785 * origin model.
786 * </p>
787 * <p>
788 * Since we consider the current value as it is in the model right now (including potential previous
789 * merges) and the origin value as the two changed sides, a three-way merge will apply the changes we did
790 * through merging and reset all other to the origin value.
791 * </p>
792 * <p>
793 * Note that, if {@code diff} is an unset (that is, the current value is empty or null or default), we
794 * just use the original value.
795 * </p>
797 * @param diff
798 * The diff for which a three-way text diff is to be performed.
799 * @param rightToLeft
800 * The direction of applying the {@code diff}.
801 * @return The merged text.
803 private String performRejectingThreeWayTextMerge(AttributeChange diff, boolean rightToLeft) {
804 final EAttribute attribute = diff.getAttribute();
805 final EObject originContainer = diff.getMatch().getOrigin();
806 final String originValue = (String)safeEGet(originContainer, attribute);
807 final String changedValueFromModel = getChangedValueFromModel(diff);
808 final String changedValue = (String)diff.getValue();
810 if (isUnset(diff, changedValueFromModel)) {
811 return originValue;
812 } else {
813 return performThreeWayTextMerge(changedValueFromModel, originValue, changedValue);
818 * Returns the changed value, as it is right now stored in the model, of the attribute that is affected by
819 * the given {@code diff}.
821 * @param diff
822 * The diff to get the changed value for.
823 * @return The changed value.
825 private String getChangedValueFromModel(AttributeChange diff) {
826 final EAttribute attribute = diff.getAttribute();
827 final EObject changedContainer;
828 switch (diff.getSource()) {
829 case LEFT:
830 changedContainer = diff.getMatch().getLeft();
831 break;
832 case RIGHT:
833 changedContainer = diff.getMatch().getRight();
834 break;
835 default:
836 throw new IllegalArgumentException();
838 return (String)safeEGet(changedContainer, attribute);
842 * Performs a three-way text merge for the given {@code origin}, {@code left}, and {@code right} text
843 * versions.
845 * @param left
846 * The left version of the String.
847 * @param right
848 * The right version of the String.
849 * @param origin
850 * The original version of the String.
851 * @return The merged version.
852 * @since 3.2
854 protected String performThreeWayTextMerge(final String left, final String right, final String origin) {
855 ThreeWayTextDiff textDiff = new ThreeWayTextDiff(origin, left, right);
856 return textDiff.getMerged();
860 * This will be used by the distinct merge actions in order to find the index at which a value should be
861 * inserted in its target list. See {@link DiffUtil#findInsertionIndex(Comparison, Diff, boolean)} for
862 * more on this.
863 * <p>
864 * Sub-classes can override this if the insertion order is irrelevant. A return value of {@code -1} will
865 * be considered as "no index" and the value will be inserted at the end of its target list.
866 * </p>
868 * @param comparison
869 * This will be used in order to retrieve the Match for EObjects when comparing them.
870 * @param diff
871 * The diff which merging will trigger the need for an insertion index in its target list.
872 * @param rightToLeft
873 * {@code true} if the merging will be done into the left list, so that we should consider the
874 * right model as the source and the left as the target.
875 * @return The index at which this {@code diff}'s value should be inserted into the 'target' list, as
876 * inferred from {@code rightToLeft}. {@code -1} if the value should be inserted at the end of its
877 * target list.
878 * @see DiffUtil#findInsertionIndex(Comparison, Diff, boolean)
880 protected int findInsertionIndex(Comparison comparison, Diff diff, boolean rightToLeft) {
881 return DiffUtil.findInsertionIndex(comparison, diff, rightToLeft);