1 /*******************************************************************************
2 * Copyright (c) 2014, 2017 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 * Alexandra Buzila - Fixes for bug 446252
11 * Stefan Dirix - Fixes for Bugs 453218 and 453749
12 *******************************************************************************/
13 package org
.eclipse
.emf
.compare
.merge
;
15 import static org
.eclipse
.emf
.compare
.internal
.utils
.ComparisonUtil
.isFeatureMapContainment
;
16 import static org
.eclipse
.emf
.compare
.utils
.ReferenceUtil
.safeEGet
;
17 import static org
.eclipse
.emf
.compare
.utils
.ReferenceUtil
.safeESet
;
19 import com
.google
.common
.collect
.Iterables
;
21 import java
.util
.List
;
23 import org
.eclipse
.emf
.compare
.Comparison
;
24 import org
.eclipse
.emf
.compare
.Diff
;
25 import org
.eclipse
.emf
.compare
.DifferenceSource
;
26 import org
.eclipse
.emf
.compare
.Equivalence
;
27 import org
.eclipse
.emf
.compare
.FeatureMapChange
;
28 import org
.eclipse
.emf
.compare
.Match
;
29 import org
.eclipse
.emf
.compare
.ReferenceChange
;
30 import org
.eclipse
.emf
.compare
.internal
.utils
.ComparisonUtil
;
31 import org
.eclipse
.emf
.compare
.internal
.utils
.DiffUtil
;
32 import org
.eclipse
.emf
.compare
.utils
.IEqualityHelper
;
33 import org
.eclipse
.emf
.ecore
.EObject
;
34 import org
.eclipse
.emf
.ecore
.EReference
;
35 import org
.eclipse
.emf
.ecore
.EStructuralFeature
;
36 import org
.eclipse
.emf
.ecore
.resource
.Resource
;
37 import org
.eclipse
.emf
.ecore
.util
.BasicFeatureMap
;
38 import org
.eclipse
.emf
.ecore
.util
.FeatureMap
;
39 import org
.eclipse
.emf
.ecore
.util
.FeatureMapUtil
;
40 import org
.eclipse
.emf
.ecore
.xmi
.XMIResource
;
43 * This specific implementation of {@link AbstractMerger} will be used to merge attribute changes.
45 * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
48 public class FeatureMapChangeMerger
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 FeatureMapChange
;
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 FeatureMapChange featureMapChange
= (FeatureMapChange
)diff
;
66 switch (diff
.getKind()) {
68 // Create the same element in right
69 addInTarget(featureMapChange
, rightToLeft
);
72 // Delete that same element from right
73 removeFromTarget(featureMapChange
, rightToLeft
);
76 moveElement(featureMapChange
, rightToLeft
);
79 changeValue(featureMapChange
, 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 FeatureMapChange featureMapChange
= (FeatureMapChange
)diff
;
94 switch (diff
.getKind()) {
96 // We have a ADD on right. we need to revert this addition
97 removeFromTarget(featureMapChange
, rightToLeft
);
100 // DELETE in the right. We need to re-create this element
101 addInTarget(featureMapChange
, rightToLeft
);
104 moveElement(featureMapChange
, rightToLeft
);
107 changeValue(featureMapChange
, 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 object 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(FeatureMapChange diff
, boolean rightToLeft
) {
143 final Match match
= diff
.getMatch();
144 final EObject expectedContainer
;
146 expectedContainer
= match
.getLeft();
148 expectedContainer
= match
.getRight();
150 if (expectedContainer
== null) {
151 throw new IllegalStateException(
152 "Couldn't add in target because its parent hasn't been merged yet: " + diff
); //$NON-NLS-1$
154 final Comparison comparison
= match
.getComparison();
155 final EStructuralFeature attribute
= diff
.getAttribute();
156 final FeatureMap
.Entry expectedValue
= (FeatureMap
.Entry
)diff
.getValue();
157 // We have the container, attribute and value. We need to know the insertion index.
158 final int insertionIndex
= findInsertionIndex(comparison
, diff
, rightToLeft
);
160 final List
<Object
> targetList
= (List
<Object
>)safeEGet(expectedContainer
, attribute
);
161 addFeatureMapValueInTarget(comparison
, rightToLeft
, targetList
, insertionIndex
, expectedValue
);
165 * Add the FeatueMapEntry value at the insertionIndex position in the list.
168 * The comparison object.
172 * The list into which {@code value} should be added.
173 * @param insertionIndex
174 * The index at which {@code value} should be inserted into {@code list}. {@code -1} if it
175 * should be appended at the end of the list.
177 * The value we need to add to {@code list}.
179 private void addFeatureMapValueInTarget(final Comparison comparison
, final boolean rightToLeft
,
180 final List
<Object
> list
, final int insertionIndex
, final FeatureMap
.Entry entry
) {
181 final Object value
= entry
.getValue();
182 final EStructuralFeature key
= entry
.getEStructuralFeature();
183 if (value
instanceof EObject
) {
185 // The value has its equivalent on the opposite side, or not
186 final Match match
= comparison
.getMatch((EObject
)value
);
187 final EObject left
= match
.getLeft();
188 final EObject right
= match
.getRight();
189 if (rightToLeft
&& left
!= null) {
191 } else if (!rightToLeft
&& right
!= null) {
194 copy
= createCopy((EObject
)value
);
197 ((BasicFeatureMap
)(Object
)list
).addUnique(insertionIndex
, FeatureMapUtil
.createEntry(key
, copy
));
199 if (DiffUtil
.isContainmentReference(key
)) {
203 match
.setRight(copy
);
205 // Copy XMI ID when applicable.
206 final Resource initialResource
= ((EObject
)value
).eResource();
207 final Resource targetResource
= copy
.eResource();
208 if (initialResource
instanceof XMIResource
&& targetResource
instanceof XMIResource
) {
209 ((XMIResource
)targetResource
).setID(copy
,
210 ((XMIResource
)initialResource
).getID((EObject
)value
));
214 ((BasicFeatureMap
)(Object
)list
).add(insertionIndex
, FeatureMapUtil
.createEntry(key
, value
));
219 * This will be called when we need to remove an element from the target side.
221 * All necessary sanity checks have been made to ensure that the current operation is one that should
222 * delete an object. In other words, we are :
224 * <li>Copying from right to left and either
226 * <li>we are copying a deletion from the right side (we need to remove the same object in the left) or,
228 * <li>we are copying an addition to the left side (we need to revert the addition).</li>
231 * <li>Copying from left to right and either
233 * <li>we are copying an addition to the right side (we need to revert the addition), or.</li>
234 * <li>we are copying a deletion from the left side (we need to remove the same object in the right).</li>
241 * The diff we are currently merging.
243 * Tells us whether we are to add an object on the left or right side.
245 @SuppressWarnings("unchecked")
246 protected void removeFromTarget(FeatureMapChange diff
, boolean rightToLeft
) {
247 final EObject currentContainer
;
249 currentContainer
= diff
.getMatch().getLeft();
251 currentContainer
= diff
.getMatch().getRight();
254 if (currentContainer
!= null) {
255 FeatureMap
.Entry expectedValue
= (FeatureMap
.Entry
)diff
.getValue();
257 if (!isDiffSourceIsMergeTarget(diff
, rightToLeft
)) {
258 final List
<Object
> targetList
= (List
<Object
>)safeEGet(currentContainer
, diff
.getAttribute());
259 for (Object object
: targetList
) {
260 if (diff
.getMatch().getComparison().getEqualityHelper().matchingValues(expectedValue
,
262 expectedValue
= (FeatureMap
.Entry
)object
;
268 final EStructuralFeature attribute
= diff
.getAttribute();
269 // We have the container, attribute and value to remove.
271 * TODO if the same value appears twice, should we try and find the one that has actually been
272 * deleted? Will it happen that often? For now, remove the first occurence we find.
274 final List
<Object
> targetList
= (List
<Object
>)safeEGet(currentContainer
, attribute
);
275 final Comparison comparison
= diff
.getMatch().getComparison();
276 removeFeatureMapValueFromTarget(comparison
, rightToLeft
, targetList
, expectedValue
);
281 * Remove the FeatueMapEntry value from the list.
284 * The comparison object.
288 * The list from which {@code value} should be removed.
290 * The value we need to remove from {@code list}.
292 private void removeFeatureMapValueFromTarget(final Comparison comparison
, final boolean rightToLeft
,
293 final List
<Object
> list
, final FeatureMap
.Entry entry
) {
294 final Object value
= entry
.getValue();
295 final EStructuralFeature key
= entry
.getEStructuralFeature();
297 if (((EReference
)key
).isContainment()) {
298 final Match expectedContainerMatch
= comparison
.getMatch((EObject
)value
);
300 expectedContainerMatch
.setLeft(null);
302 expectedContainerMatch
.setRight(null);
305 ((BasicFeatureMap
)(Object
)list
).remove(key
, value
);
309 * This will be called when trying to copy a "MOVE" diff.
312 * The diff we are currently merging.
314 * Whether we should move the value in the left or right side.
316 protected void moveElement(final FeatureMapChange diff
, final boolean rightToLeft
) {
317 final Match match
= diff
.getMatch();
318 final Comparison comparison
= match
.getComparison();
319 final EObject expectedContainer
= ComparisonUtil
.moveElementGetExpectedContainer(comparison
, diff
,
321 if (expectedContainer
== null) {
322 throw new IllegalStateException(
323 "Couldn't move element because its parent hasn't been merged yet: " + diff
); //$NON-NLS-1$
325 final FeatureMap
.Entry expectedEntry
= moveElementGetExpectedEntry(comparison
, diff
,
326 expectedContainer
, rightToLeft
);
327 // We now know the target container, target attribute and target entry.
328 doMove(diff
, comparison
, expectedContainer
, expectedEntry
, rightToLeft
);
332 * Get the expected FeatureMap.Entry to move.
335 * The comparison object.
337 * The diff we are currently merging.
338 * @param expectedContainer
339 * The expected container that will contain the expected entry to move.
341 * Whether we should move the value in the left or right side.
342 * @return The expected entry if found, <code>null</code> otherwise.
344 private FeatureMap
.Entry
moveElementGetExpectedEntry(final Comparison comparison
,
345 final FeatureMapChange diff
, final EObject expectedContainer
, final boolean rightToLeft
) {
346 final FeatureMap
.Entry expectedEntry
;
347 if (isDiffSourceIsMergeTarget(diff
, rightToLeft
)) {
348 expectedEntry
= getExpectedEntryWhenDiffSourceIsMergeTarget(comparison
, diff
);
351 expectedEntry
= getExpectedEntryWhenDiffSourceIsNotMergeTarget(comparison
, diff
,
352 expectedContainer
, rightToLeft
);
354 return expectedEntry
;
358 * Get the expected FeatureMap.Entry to move.
361 * The comparison object.
363 * The diff we are currently merging.
364 * @param expectedContainer
365 * The expected container that will contain the expected entry to move.
367 * Whether we should move the value in the left or right side.
368 * @return The expected entry if found, <code>null</code> otherwise.
370 @SuppressWarnings("unchecked")
371 private FeatureMap
.Entry
getExpectedEntryWhenDiffSourceIsNotMergeTarget(final Comparison comparison
,
372 final FeatureMapChange diff
, final EObject expectedContainer
, final boolean rightToLeft
) {
373 FeatureMap
.Entry expectedEntry
= null;
374 final IEqualityHelper equalityHelper
= comparison
.getEqualityHelper();
375 final FeatureMap
.Entry diffEntry
= (FeatureMap
.Entry
)diff
.getValue();
376 // It is a Move on containment entry and the diff has an equivalence.
377 Equivalence equ
= diff
.getEquivalence();
378 if (isFeatureMapContainment(diff
) && equ
!= null) {
379 // There is a ReferenceChange associated with the FeatureMapChange. This ReferenceChange
380 // contains the expected value to move.
381 for (ReferenceChange equivalence
: Iterables
.filter(equ
.getDifferences(),
382 ReferenceChange
.class)) {
383 final Match equivalenceMatchValue
= comparison
.getMatch(equivalence
.getValue());
384 final Object expectedEntryValue
;
386 expectedEntryValue
= equivalenceMatchValue
.getLeft();
388 expectedEntryValue
= equivalenceMatchValue
.getRight();
390 expectedEntry
= FeatureMapUtil
.createEntry(diffEntry
.getEStructuralFeature(),
395 final List
<Object
> targetList
= (List
<Object
>)safeEGet(expectedContainer
, diff
.getAttribute());
396 for (Object object
: targetList
) {
397 if (equalityHelper
.matchingValues(diffEntry
, object
)) {
398 expectedEntry
= (FeatureMap
.Entry
)object
;
403 return expectedEntry
;
407 * Get the expected FeatureMap.Entry to move.
410 * The comparison object.
412 * The diff we are currently merging.
413 * @return The expected entry if found, <code>null</code> otherwise.
415 @SuppressWarnings("unchecked")
416 private FeatureMap
.Entry
getExpectedEntryWhenDiffSourceIsMergeTarget(final Comparison comparison
,
417 final FeatureMapChange diff
) {
418 final FeatureMap
.Entry expectedEntry
;
419 final IEqualityHelper equalityHelper
= comparison
.getEqualityHelper();
420 final FeatureMap
.Entry diffEntry
= (FeatureMap
.Entry
)diff
.getValue();
421 final Match matchValue
= comparison
.getMatch((EObject
)diffEntry
.getValue());
423 if (diff
.getSource() == DifferenceSource
.RIGHT
) {
424 value
= matchValue
.getRight();
426 value
= matchValue
.getLeft();
428 if (comparison
.isThreeWay() && isFeatureMapContainment(diff
)) {
429 // search the origin key associated to the value
430 EStructuralFeature originKey
= null;
431 final List
<Object
> originList
= (List
<Object
>)safeEGet(matchValue
.getOrigin().eContainer(),
432 diff
.getAttribute());
433 for (Object object
: originList
) {
434 if (object
instanceof FeatureMap
.Entry
435 && equalityHelper
.matchingValues(value
, ((FeatureMap
.Entry
)object
).getValue())) {
436 // same value, get the key
437 originKey
= ((FeatureMap
.Entry
)object
).getEStructuralFeature();
441 expectedEntry
= FeatureMapUtil
.createEntry(originKey
, value
);
442 } else if (((EReference
)diffEntry
.getEStructuralFeature()).isContainment()) {
443 EStructuralFeature targetReference
= getTargetReference(comparison
, diff
);
444 expectedEntry
= FeatureMapUtil
.createEntry(targetReference
, value
);
446 expectedEntry
= FeatureMapUtil
.createEntry(diffEntry
.getEStructuralFeature(), value
);
448 return expectedEntry
;
452 * Get the target EStructuralFeature when moving a FeatureMap.Entry.
455 * The comparison object.
457 * The diff we are currently merging.
458 * @return The target reference, i.e. the reference into which the value will be moved.
460 private EStructuralFeature
getTargetReference(final Comparison comparison
, final FeatureMapChange diff
) {
461 final FeatureMap
.Entry diffEntry
= (FeatureMap
.Entry
)diff
.getValue();
462 final Match equivalenceMatchValue
= comparison
.getMatch((EObject
)diffEntry
.getValue());
463 final EObject targetValue
;
464 if (diff
.getSource() == DifferenceSource
.LEFT
) {
465 targetValue
= equivalenceMatchValue
.getRight();
467 targetValue
= equivalenceMatchValue
.getLeft();
469 return targetValue
.eContainingFeature();
473 * Checks if the source of the given diff is the same as the target of the merge.
476 * The diff we are currently merging.
478 * Whether we should merge the diff in the left or right side.
479 * @return true if the source of the given diff is the same as the target of the merge, false otherwise.
481 private boolean isDiffSourceIsMergeTarget(final Diff diff
, final boolean rightToLeft
) {
482 DifferenceSource source
= diff
.getSource();
483 return source
== DifferenceSource
.LEFT
&& rightToLeft
484 || source
== DifferenceSource
.RIGHT
&& !rightToLeft
;
488 * This will do the actual work of moving the element into its attribute. All sanity checks were made in
489 * {@link #moveElement(boolean)} and no more verification will be made here.
492 * The diff we are currently merging.
494 * Comparison holding this Diff.
495 * @param expectedContainer
496 * The container in which we are reorganizing an attribute.
497 * @param expectedValue
498 * The value that is to be moved within its attribute.
500 * Whether we should move the value in the left or right side.
502 @SuppressWarnings("unchecked")
503 protected void doMove(FeatureMapChange diff
, Comparison comparison
, EObject expectedContainer
,
504 FeatureMap
.Entry expectedValue
, boolean rightToLeft
) {
505 final EStructuralFeature attribute
= diff
.getAttribute();
506 if (attribute
.isMany()) {
507 // Element to move cannot be part of the LCS... or there would not be a MOVE diff
508 int insertionIndex
= findInsertionIndex(comparison
, diff
, rightToLeft
);
510 * However, it could still have been located "before" its new index, in which case we need to take
513 final List
<Object
> targetList
= (List
<Object
>)safeEGet(expectedContainer
, attribute
);
514 final int currentIndex
= targetList
.indexOf(expectedValue
);
515 if (insertionIndex
> currentIndex
&& currentIndex
>= 0) {
519 if (currentIndex
== -1) {
520 if (insertionIndex
< 0 || insertionIndex
>= targetList
.size()) {
521 ((BasicFeatureMap
)(Object
)targetList
).addUnique(expectedValue
);
523 ((BasicFeatureMap
)(Object
)targetList
).addUnique(insertionIndex
, expectedValue
);
526 if (insertionIndex
< 0 || insertionIndex
>= targetList
.size()) {
527 ((BasicFeatureMap
)(Object
)targetList
).move(targetList
.size() - 1, expectedValue
);
529 ((BasicFeatureMap
)(Object
)targetList
).move(insertionIndex
, expectedValue
);
533 // This will never happen with the default diff engine, but may still be done from extenders
534 safeESet(expectedContainer
, attribute
, expectedValue
);
539 * This will be called by the merge operations in order to change a key.
542 * The diff we are currently merging.
544 * Direction of the merge.
546 @SuppressWarnings("unchecked")
547 protected void changeValue(FeatureMapChange diff
, boolean rightToLeft
) {
548 final Match match
= diff
.getMatch();
549 final IEqualityHelper equalityHelper
= match
.getComparison().getEqualityHelper();
550 final EStructuralFeature attribute
= diff
.getAttribute();
551 final FeatureMap
.Entry entry
= (FeatureMap
.Entry
)diff
.getValue();
552 // the value we're looking for in expected and origin container.
553 final Object entryValue
= entry
.getValue();
556 final String originValueId
;
557 if (entryValue
instanceof EObject
) {
558 final Resource initialResource
= ((EObject
)entryValue
).eResource();
559 if (initialResource
instanceof XMIResource
) {
560 originValueId
= ((XMIResource
)initialResource
).getID((EObject
)entryValue
);
562 originValueId
= null;
565 originValueId
= null;
568 final EObject expectedContainer
;
570 expectedContainer
= match
.getLeft();
572 expectedContainer
= match
.getRight();
575 final EObject originContainer
;
576 final boolean resetToOrigin
= diff
.getSource() == DifferenceSource
.LEFT
&& rightToLeft
577 || diff
.getSource() == DifferenceSource
.RIGHT
&& !rightToLeft
;
578 if (resetToOrigin
&& match
.getComparison().isThreeWay()) {
579 originContainer
= match
.getOrigin();
580 } else if (rightToLeft
) {
581 originContainer
= match
.getRight();
583 originContainer
= match
.getLeft();
586 // search the origin key associated to the value
587 EStructuralFeature originKey
= null;
588 final List
<Object
> originList
= (List
<Object
>)safeEGet(originContainer
, attribute
);
589 for (Object object
: originList
) {
590 if (object
instanceof FeatureMap
.Entry
) {
591 // same value, get the key
592 if (equalityHelper
.matchingValues(entryValue
, ((FeatureMap
.Entry
)object
).getValue())) {
593 originKey
= ((FeatureMap
.Entry
)object
).getEStructuralFeature();
599 if (originKey
== null) {
600 throw new RuntimeException("FeatureMapChangeMerger: Cannot find the key to change."); //$NON-NLS-1$
603 // search the value in expected container to change his key.
604 final List
<Object
> targetList
= (List
<Object
>)safeEGet(expectedContainer
, attribute
);
606 for (Object object
: targetList
) {
607 if (object
instanceof FeatureMap
.Entry
) {
608 // same value, now change the key
609 Object targetValue
= ((FeatureMap
.Entry
)object
).getValue();
610 if (equalityHelper
.matchingValues(entryValue
, targetValue
)) {
611 // forced to use setUnique(int, Entry) because if the originKey is not present in the
612 // target map, the setUnique(EStructuralFeature, int, Object) will not validate the key
613 ((BasicFeatureMap
)(Object
)targetList
).setUnique(index
,
614 FeatureMapUtil
.createEntry(originKey
, targetValue
));
616 // setUnique(int, Entry) doesn't keep ID, so copy XMI ID when applicable.
617 final Resource targetResource
;
618 if (DiffUtil
.isContainmentReference(originKey
) && targetValue
instanceof EObject
619 && originValueId
!= null) {
620 targetResource
= ((EObject
)targetValue
).eResource();
622 targetResource
= null;
624 if (targetResource
instanceof XMIResource
) {
625 ((XMIResource
)targetResource
).setID((EObject
)targetValue
, originValueId
);
635 * This will be used by the distinct merge actions in order to find the index at which a value should be
636 * inserted in its target list. See {@link DiffUtil#findInsertionIndex(Comparison, Diff, boolean)} for
639 * Sub-classes can override this if the insertion order is irrelevant. A return value of {@code -1} will
640 * be considered as "no index" and the value will be inserted at the end of its target list.
644 * This will be used in order to retrieve the Match for EObjects when comparing them.
646 * The diff which merging will trigger the need for an insertion index in its target list.
648 * {@code true} if the merging will be done into the left list, so that we should consider the
649 * right model as the source and the left as the target.
650 * @return The index at which this {@code diff}'s value should be inserted into the 'target' list, as
651 * inferred from {@code rightToLeft}. {@code -1} if the value should be inserted at the end of its
653 * @see DiffUtil#findInsertionIndex(Comparison, Diff, boolean)
655 protected int findInsertionIndex(Comparison comparison
, Diff diff
, boolean rightToLeft
) {
656 return DiffUtil
.findInsertionIndex(comparison
, diff
, rightToLeft
);