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
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
;
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
;
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
{
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
;
61 * @see org.eclipse.emf.compare.merge.AbstractMerger#accept(org.eclipse.emf.compare.Diff, boolean)
64 protected void accept(final Diff diff
, boolean rightToLeft
) {
65 AttributeChange attributeChange
= (AttributeChange
)diff
;
66 switch (diff
.getKind()) {
68 // Create the same element in right
69 addInTarget(attributeChange
, rightToLeft
);
72 // Delete that same element from right
73 removeFromTarget(attributeChange
, rightToLeft
);
76 moveElement(attributeChange
, rightToLeft
);
79 changeValue(attributeChange
, rightToLeft
);
89 * @see org.eclipse.emf.compare.merge.AbstractMerger#reject(org.eclipse.emf.compare.Diff, boolean)
92 protected void reject(Diff diff
, boolean rightToLeft
) {
93 AttributeChange attributeChange
= (AttributeChange
)diff
;
94 switch (diff
.getKind()) {
96 // We have a ADD on right. we need to revert this addition
97 removeFromTarget(attributeChange
, rightToLeft
);
100 // DELETE in the right. We need to re-create this element
101 addInTarget(attributeChange
, rightToLeft
);
104 moveElement(attributeChange
, rightToLeft
);
107 changeValue(attributeChange
, rightToLeft
);
115 * This will be called when we need to create an element in the target side.
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 :
120 * <li>We are copying from right to left and
122 * <li>we are copying an addition to the right side (we need to create the same object in the left), or
124 * <li>we are copying a deletion from the left side (we need to revert the deletion).</li>
127 * <li>We are copying from left to right and
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>
137 * The diff we are currently merging.
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
);
160 safeESet(targetContainer
, attribute
, expectedValue
);
165 * This will be called when we need to remove an element from the target side.
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 :
170 * <li>Copying from right to left and either
172 * <li>we are copying a deletion from the right side (we need to remove the same object in the left) or,
174 * <li>we are copying an addition to the left side (we need to revert the addition).</li>
177 * <li>Copying from left to right and either
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>
187 * The diff we are currently merging.
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
);
204 removeNonUniqueFromTarget(diff
, rightToLeft
);
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
;
221 sourceContainer
= diff
.getMatch().getRight();
222 targetContainer
= diff
.getMatch().getLeft();
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();
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
264 if (currentIndexInTarget
>= 0) {
265 targetList
.remove(currentIndexInTarget
);
270 * This will be called when trying to copy a "MOVE" diff.
273 * The diff we are currently merging.
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.
296 * The diff we are currently merging.
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.
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
);
313 doMoveUniqueAttribute(diff
, comparison
, expectedContainer
, expectedValue
, rightToLeft
);
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
332 final List
<Object
> targetList
= (List
<Object
>)safeEGet(expectedContainer
, attribute
);
333 final int currentIndex
= targetList
.indexOf(expectedValue
);
334 if (insertionIndex
> currentIndex
) {
338 if (targetList
instanceof EList
<?
>) {
339 if (insertionIndex
< 0 || insertionIndex
>= targetList
.size()) {
340 ((EList
<Object
>)targetList
).move(targetList
.size() - 1, expectedValue
);
342 ((EList
<Object
>)targetList
).move(insertionIndex
, expectedValue
);
345 targetList
.remove(expectedValue
);
346 if (insertionIndex
< 0 || insertionIndex
> targetList
.size()) {
347 targetList
.add(expectedValue
);
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
;
362 sourceContainer
= diff
.getMatch().getRight();
363 targetContainer
= diff
.getMatch().getLeft();
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
,
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()))
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
) {
392 } else if (sibling
.getSource() == DifferenceSource
.RIGHT
&& !rightToLeft
) {
395 } else if (sibling
.getKind() == DifferenceKind
.DELETE
) {
397 if (sibling
.getSource() == DifferenceSource
.LEFT
&& !rightToLeft
) {
399 } else if (sibling
.getSource() == DifferenceSource
.RIGHT
&& rightToLeft
) {
405 if (ignoredElements
.isEmpty()) {
406 ignoredElements
= Collections
.singleton(valueToMove
);
408 ignoredElements
.add(valueToMove
);
411 List
<Object
> lcs
= DiffUtil
.longestCommonSubsequence(comparison
, ignoredElements
, sourceList
,
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();
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();
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();
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
478 if (insertionIndex
> currentIndexInTarget
) {
482 if (targetList
instanceof EList
<?
>) {
483 if (insertionIndex
< 0 || insertionIndex
>= targetList
.size()) {
484 ((EList
<Object
>)targetList
).move(targetList
.size() - 1, currentIndexInTarget
);
486 ((EList
<Object
>)targetList
).move(insertionIndex
, currentIndexInTarget
);
489 targetList
.remove(currentIndexInTarget
);
490 if (insertionIndex
< 0 || insertionIndex
> targetList
.size()) {
491 targetList
.add(valueToMove
);
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.
502 * Should never be called on multi-valued attributes.
506 * The diff we are currently merging.
508 * Tells us the direction of this merge operation.
509 * @deprecated this has been refactored into {@link #changeValue(AttributeChange, boolean)}.
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
);
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.
530 * The diff we are currently merging.
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.
542 final EStructuralFeature attribute
= diff
.getAttribute();
543 final Object targetValue
= getTargetValue(diff
, rightToLeft
);
545 if (isUnset(diff
, targetValue
)) {
546 targetContainer
.eUnset(attribute
);
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}.
557 * The diff we are currently merging.
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
);
575 targetValue
= sourceValue
;
582 * Specifies whether the given {@code diff} concerns a change of a dynamic EObject at an attribute with an
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.
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}.
617 * The diff we are currently merging.
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();
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}.
640 * The diff we are currently merging.
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();
648 return match
.getLeft();
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}.
659 * The difference to check.
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}.
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.
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.
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.
702 * The diff to be checked.
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.
717 * The diff to be checked.
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.
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.
734 * The diff for which a three-way text diff is to be performed.
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
);
743 return performRejectingThreeWayTextMerge(diff
, rightToLeft
);
748 * Performs a three-way text merge accepting the given {@code diff} and returns the merged text.
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.
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.
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.
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
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.
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.
798 * The diff for which a three-way text diff is to be performed.
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
)) {
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}.
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()) {
830 changedContainer
= diff
.getMatch().getLeft();
833 changedContainer
= diff
.getMatch().getRight();
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
846 * The left version of the String.
848 * The right version of the String.
850 * The original version of the String.
851 * @return The merged version.
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
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.
869 * This will be used in order to retrieve the Match for EObjects when comparing them.
871 * The diff which merging will trigger the need for an insertion index in its target list.
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
878 * @see DiffUtil#findInsertionIndex(Comparison, Diff, boolean)
880 protected int findInsertionIndex(Comparison comparison
, Diff diff
, boolean rightToLeft
) {
881 return DiffUtil
.findInsertionIndex(comparison
, diff
, rightToLeft
);