1 /*******************************************************************************
2 * Copyright (c) 2014 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</li>
123 * <li>we are copying a deletion from the left side (we need to revert the deletion).</li>
126 * <li>We are copying from left to right and
128 * <li>we are copying a deletion from the right side (we need to revert the deletion), or</li>
129 * <li>we are copying an addition to the left side (we need to create the same object in the right).</li>
136 * The diff we are currently merging.
138 * Tells us whether we are to add an object on the left or right side.
140 @SuppressWarnings("unchecked")
141 protected void addInTarget(FeatureMapChange diff
, boolean rightToLeft
) {
142 final Match match
= diff
.getMatch();
143 final EObject expectedContainer
;
145 expectedContainer
= match
.getLeft();
147 expectedContainer
= match
.getRight();
150 if (expectedContainer
== null) {
151 // FIXME throw exception? log? re-try to merge our requirements?
152 // one of the "required" diffs should have created our container.
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
);
166 * Add the FeatueMapEntry value at the insertionIndex position in the list.
169 * The comparison object.
173 * The list into which {@code value} should be added.
174 * @param insertionIndex
175 * The index at which {@code value} should be inserted into {@code list}. {@code -1} if it
176 * should be appended at the end of the list.
178 * The value we need to add to {@code list}.
180 private void addFeatureMapValueInTarget(final Comparison comparison
, final boolean rightToLeft
,
181 final List
<Object
> list
, final int insertionIndex
, final FeatureMap
.Entry entry
) {
182 final Object value
= entry
.getValue();
183 final EStructuralFeature key
= entry
.getEStructuralFeature();
184 if (value
instanceof EObject
) {
186 // The value has its equivalent on the opposite side, or not
187 final Match match
= comparison
.getMatch((EObject
)value
);
188 final EObject left
= match
.getLeft();
189 final EObject right
= match
.getRight();
190 if (rightToLeft
&& left
!= null) {
192 } else if (!rightToLeft
&& right
!= null) {
195 copy
= createCopy((EObject
)value
);
198 ((BasicFeatureMap
)(Object
)list
).addUnique(insertionIndex
, FeatureMapUtil
.createEntry(key
, copy
));
200 if (key
instanceof EReference
&& ((EReference
)key
).isContainment()) {
204 match
.setRight(copy
);
206 // Copy XMI ID when applicable.
207 final Resource initialResource
= ((EObject
)value
).eResource();
208 final Resource targetResource
= copy
.eResource();
209 if (initialResource
instanceof XMIResource
&& targetResource
instanceof XMIResource
) {
210 ((XMIResource
)targetResource
).setID(copy
, ((XMIResource
)initialResource
)
211 .getID((EObject
)value
));
215 ((BasicFeatureMap
)(Object
)list
).add(insertionIndex
, FeatureMapUtil
.createEntry(key
, value
));
220 * This will be called when we need to remove an element from the target side.
222 * All necessary sanity checks have been made to ensure that the current operation is one that should
223 * delete an object. In other words, we are :
225 * <li>Copying from right to left and either
227 * <li>we are copying a deletion from the right side (we need to remove the same object in the left) or,</li>
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 // FIXME throw exception? log? re-try to merge our requirements?
257 FeatureMap
.Entry expectedValue
= (FeatureMap
.Entry
)diff
.getValue();
259 if (!isDiffSourceIsMergeTarget(diff
, rightToLeft
)) {
260 final List
<Object
> targetList
= (List
<Object
>)safeEGet(currentContainer
, diff
.getAttribute());
261 for (Object object
: targetList
) {
262 if (diff
.getMatch().getComparison().getEqualityHelper().matchingValues(expectedValue
,
264 expectedValue
= (FeatureMap
.Entry
)object
;
270 final EStructuralFeature attribute
= diff
.getAttribute();
271 // We have the container, attribute and value to remove.
273 * TODO if the same value appears twice, should we try and find the one that has actually been
274 * deleted? Will it happen that often? For now, remove the first occurence we find.
276 final List
<Object
> targetList
= (List
<Object
>)safeEGet(currentContainer
, attribute
);
277 final Comparison comparison
= diff
.getMatch().getComparison();
278 removeFeatureMapValueFromTarget(comparison
, rightToLeft
, targetList
, expectedValue
);
283 * Remove the FeatueMapEntry value from the list.
286 * The comparison object.
290 * The list from which {@code value} should be removed.
292 * The value we need to remove from {@code list}.
294 private void removeFeatureMapValueFromTarget(final Comparison comparison
, final boolean rightToLeft
,
295 final List
<Object
> list
, final FeatureMap
.Entry entry
) {
296 final Object value
= entry
.getValue();
297 final EStructuralFeature key
= entry
.getEStructuralFeature();
298 final Match expectedContainerMatch
= comparison
.getMatch((EObject
)value
);
299 ((BasicFeatureMap
)(Object
)list
).remove(key
, value
);
301 if (((EReference
)key
).isContainment()) {
303 expectedContainerMatch
.setLeft(null);
305 expectedContainerMatch
.setRight(null);
311 * This will be called when trying to copy a "MOVE" diff.
314 * The diff we are currently merging.
316 * Whether we should move the value in the left or right side.
318 protected void moveElement(final FeatureMapChange diff
, final boolean rightToLeft
) {
319 final Match match
= diff
.getMatch();
320 final Comparison comparison
= match
.getComparison();
321 final EObject expectedContainer
= ComparisonUtil
.moveElementGetExpectedContainer(comparison
, diff
,
324 if (expectedContainer
== null) {
325 // TODO throws exception?
327 final FeatureMap
.Entry expectedEntry
= moveElementGetExpectedEntry(comparison
, diff
,
328 expectedContainer
, rightToLeft
);
329 // We now know the target container, target attribute and target entry.
330 doMove(diff
, comparison
, expectedContainer
, expectedEntry
, rightToLeft
);
335 * Get the expected FeatureMap.Entry to move.
338 * The comparison object.
340 * The diff we are currently merging.
341 * @param expectedContainer
342 * The expected container that will contain the expected entry to move.
344 * Whether we should move the value in the left or right side.
345 * @return The expected entry if found, <code>null</code> otherwise.
347 private FeatureMap
.Entry
moveElementGetExpectedEntry(final Comparison comparison
,
348 final FeatureMapChange diff
, final EObject expectedContainer
, final boolean rightToLeft
) {
349 final FeatureMap
.Entry expectedEntry
;
350 if (isDiffSourceIsMergeTarget(diff
, rightToLeft
)) {
351 expectedEntry
= getExpectedEntryWhenDiffSourceIsMergeTarget(comparison
, diff
);
354 expectedEntry
= getExpectedEntryWhenDiffSourceIsNotMergeTarget(comparison
, diff
,
355 expectedContainer
, rightToLeft
);
357 return expectedEntry
;
361 * Get the expected FeatureMap.Entry to move.
364 * The comparison object.
366 * The diff we are currently merging.
367 * @param expectedContainer
368 * The expected container that will contain the expected entry to move.
370 * Whether we should move the value in the left or right side.
371 * @return The expected entry if found, <code>null</code> otherwise.
373 @SuppressWarnings("unchecked")
374 private FeatureMap
.Entry
getExpectedEntryWhenDiffSourceIsNotMergeTarget(final Comparison comparison
,
375 final FeatureMapChange diff
, final EObject expectedContainer
, final boolean rightToLeft
) {
376 FeatureMap
.Entry expectedEntry
= null;
377 final IEqualityHelper equalityHelper
= comparison
.getEqualityHelper();
378 final FeatureMap
.Entry diffEntry
= (FeatureMap
.Entry
)diff
.getValue();
379 // It is a Move on containment entry and the diff has an equivalence.
380 Equivalence equ
= diff
.getEquivalence();
381 if (isFeatureMapContainment(diff
) && equ
!= null) {
382 // There is a ReferenceChange associated with the FeatureMapChange. This ReferenceChange
383 // contains the expected value to move.
384 for (ReferenceChange equivalence
: Iterables
.filter(equ
.getDifferences(), ReferenceChange
.class)) {
385 final Match equivalenceMatchValue
= comparison
.getMatch(equivalence
.getValue());
386 final Object expectedEntryValue
;
388 expectedEntryValue
= equivalenceMatchValue
.getLeft();
390 expectedEntryValue
= equivalenceMatchValue
.getRight();
392 expectedEntry
= FeatureMapUtil
.createEntry(diffEntry
.getEStructuralFeature(),
397 final List
<Object
> targetList
= (List
<Object
>)safeEGet(expectedContainer
, diff
.getAttribute());
398 for (Object object
: targetList
) {
399 if (equalityHelper
.matchingValues(diffEntry
, object
)) {
400 expectedEntry
= (FeatureMap
.Entry
)object
;
405 return expectedEntry
;
409 * Get the expected FeatureMap.Entry to move.
412 * The comparison object.
414 * The diff we are currently merging.
415 * @return The expected entry if found, <code>null</code> otherwise.
417 @SuppressWarnings("unchecked")
418 private FeatureMap
.Entry
getExpectedEntryWhenDiffSourceIsMergeTarget(final Comparison comparison
,
419 final FeatureMapChange diff
) {
420 final FeatureMap
.Entry expectedEntry
;
421 final IEqualityHelper equalityHelper
= comparison
.getEqualityHelper();
422 final FeatureMap
.Entry diffEntry
= (FeatureMap
.Entry
)diff
.getValue();
423 final Match matchValue
= comparison
.getMatch((EObject
)diffEntry
.getValue());
425 if (diff
.getSource() == DifferenceSource
.RIGHT
) {
426 value
= matchValue
.getRight();
428 value
= matchValue
.getLeft();
430 if (comparison
.isThreeWay() && isFeatureMapContainment(diff
)) {
431 // search the origin key associated to the value
432 EStructuralFeature originKey
= null;
433 final List
<Object
> originList
= (List
<Object
>)safeEGet(matchValue
.getOrigin().eContainer(), diff
435 for (Object object
: originList
) {
436 if (object
instanceof FeatureMap
.Entry
437 && equalityHelper
.matchingValues(value
, ((FeatureMap
.Entry
)object
).getValue())) {
438 // same value, get the key
439 originKey
= ((FeatureMap
.Entry
)object
).getEStructuralFeature();
443 expectedEntry
= FeatureMapUtil
.createEntry(originKey
, value
);
444 } else if (((EReference
)diffEntry
.getEStructuralFeature()).isContainment()) {
445 EStructuralFeature targetReference
= getTargetReference(comparison
, diff
);
446 expectedEntry
= FeatureMapUtil
.createEntry(targetReference
, value
);
448 expectedEntry
= FeatureMapUtil
.createEntry(diffEntry
.getEStructuralFeature(), value
);
450 return expectedEntry
;
454 * Get the target EStructuralFeature when moving a FeatureMap.Entry.
457 * The comparison object.
459 * The diff we are currently merging.
460 * @return The target reference, i.e. the reference into which the value will be moved.
462 private EStructuralFeature
getTargetReference(final Comparison comparison
, final FeatureMapChange diff
) {
463 final FeatureMap
.Entry diffEntry
= (FeatureMap
.Entry
)diff
.getValue();
464 final Match equivalenceMatchValue
= comparison
.getMatch((EObject
)diffEntry
.getValue());
465 final EObject targetValue
;
466 if (diff
.getSource() == DifferenceSource
.LEFT
) {
467 targetValue
= equivalenceMatchValue
.getRight();
469 targetValue
= equivalenceMatchValue
.getLeft();
471 return targetValue
.eContainingFeature();
475 * Checks if the source of the given diff is the same as the target of the merge.
478 * The diff we are currently merging.
480 * Whether we should merge the diff in the left or right side.
481 * @return true if the source of the given diff is the same as the target of the merge, false otherwise.
483 private boolean isDiffSourceIsMergeTarget(final Diff diff
, final boolean rightToLeft
) {
484 DifferenceSource source
= diff
.getSource();
485 return source
== DifferenceSource
.LEFT
&& rightToLeft
|| source
== DifferenceSource
.RIGHT
490 * This will do the actual work of moving the element into its attribute. All sanity checks were made in
491 * {@link #moveElement(boolean)} and no more verification will be made here.
494 * The diff we are currently merging.
496 * Comparison holding this Diff.
497 * @param expectedContainer
498 * The container in which we are reorganizing an attribute.
499 * @param expectedValue
500 * The value that is to be moved within its attribute.
502 * Whether we should move the value in the left or right side.
504 @SuppressWarnings("unchecked")
505 protected void doMove(FeatureMapChange diff
, Comparison comparison
, EObject expectedContainer
,
506 FeatureMap
.Entry expectedValue
, boolean rightToLeft
) {
507 final EStructuralFeature attribute
= diff
.getAttribute();
508 if (attribute
.isMany()) {
509 // Element to move cannot be part of the LCS... or there would not be a MOVE diff
510 int insertionIndex
= findInsertionIndex(comparison
, diff
, rightToLeft
);
512 * However, it could still have been located "before" its new index, in which case we need to take
515 final List
<Object
> targetList
= (List
<Object
>)safeEGet(expectedContainer
, attribute
);
516 final int currentIndex
= targetList
.indexOf(expectedValue
);
517 if (insertionIndex
> currentIndex
&& currentIndex
>= 0) {
521 if (currentIndex
== -1) {
522 if (insertionIndex
< 0 || insertionIndex
> targetList
.size()) {
523 ((BasicFeatureMap
)(Object
)targetList
).addUnique(expectedValue
);
525 ((BasicFeatureMap
)(Object
)targetList
).addUnique(insertionIndex
, expectedValue
);
528 if (insertionIndex
< 0 || insertionIndex
> targetList
.size()) {
529 ((BasicFeatureMap
)(Object
)targetList
).move(targetList
.size() - 1, expectedValue
);
531 ((BasicFeatureMap
)(Object
)targetList
).move(insertionIndex
, expectedValue
);
535 // This will never happen with the default diff engine, but may still be done from extenders
536 safeESet(expectedContainer
, attribute
, expectedValue
);
541 * This will be called by the merge operations in order to change a key.
544 * The diff we are currently merging.
546 * Direction of the merge.
548 @SuppressWarnings("unchecked")
549 protected void changeValue(FeatureMapChange diff
, boolean rightToLeft
) {
550 final Match match
= diff
.getMatch();
551 final IEqualityHelper equalityHelper
= match
.getComparison().getEqualityHelper();
552 final EStructuralFeature attribute
= diff
.getAttribute();
553 final FeatureMap
.Entry entry
= (FeatureMap
.Entry
)diff
.getValue();
554 // the value we're looking for in expected and origin container.
555 final Object entryValue
= entry
.getValue();
558 final String originValueId
;
559 if (entryValue
instanceof EObject
) {
560 final Resource initialResource
= ((EObject
)entryValue
).eResource();
561 if (initialResource
instanceof XMIResource
) {
562 originValueId
= ((XMIResource
)initialResource
).getID((EObject
)entryValue
);
564 originValueId
= null;
567 originValueId
= null;
570 final EObject expectedContainer
;
572 expectedContainer
= match
.getLeft();
574 expectedContainer
= match
.getRight();
577 final EObject originContainer
;
578 final boolean resetToOrigin
= diff
.getSource() == DifferenceSource
.LEFT
&& rightToLeft
579 || diff
.getSource() == DifferenceSource
.RIGHT
&& !rightToLeft
;
580 if (resetToOrigin
&& match
.getComparison().isThreeWay()) {
581 originContainer
= match
.getOrigin();
582 } else if (rightToLeft
) {
583 originContainer
= match
.getRight();
585 originContainer
= match
.getLeft();
588 // search the origin key associated to the value
589 EStructuralFeature originKey
= null;
590 final List
<Object
> originList
= (List
<Object
>)safeEGet(originContainer
, attribute
);
591 for (Object object
: originList
) {
592 if (object
instanceof FeatureMap
.Entry
) {
593 // same value, get the key
594 if (equalityHelper
.matchingValues(entryValue
, ((FeatureMap
.Entry
)object
).getValue())) {
595 originKey
= ((FeatureMap
.Entry
)object
).getEStructuralFeature();
601 if (originKey
== null) {
602 throw new RuntimeException("FeatureMapChangeMerger: Cannot find the key to change."); //$NON-NLS-1$
605 // search the value in expected container to change his key.
606 final List
<Object
> targetList
= (List
<Object
>)safeEGet(expectedContainer
, attribute
);
608 for (Object object
: targetList
) {
609 if (object
instanceof FeatureMap
.Entry
) {
610 // same value, now change the key
611 Object targetValue
= ((FeatureMap
.Entry
)object
).getValue();
612 if (equalityHelper
.matchingValues(entryValue
, targetValue
)) {
613 // forced to use setUnique(int, Entry) because if the originKey is not present in the
614 // target map, the setUnique(EStructuralFeature, int, Object) will not validate the key
615 ((BasicFeatureMap
)(Object
)targetList
).setUnique(index
, FeatureMapUtil
.createEntry(
616 originKey
, targetValue
));
618 // setUnique(int, Entry) doesn't keep ID, so copy XMI ID when applicable.
619 final Resource targetResource
;
620 if (originKey
instanceof EReference
&& ((EReference
)originKey
).isContainment()
621 && targetValue
instanceof EObject
&& originValueId
!= null) {
622 targetResource
= ((EObject
)targetValue
).eResource();
624 targetResource
= null;
626 if (targetResource
instanceof XMIResource
) {
627 ((XMIResource
)targetResource
).setID((EObject
)targetValue
, originValueId
);
637 * This will be used by the distinct merge actions in order to find the index at which a value should be
638 * inserted in its target list. See {@link DiffUtil#findInsertionIndex(Comparison, Diff, boolean)} for
641 * Sub-classes can override this if the insertion order is irrelevant. A return value of {@code -1} will
642 * be considered as "no index" and the value will be inserted at the end of its target list.
646 * This will be used in order to retrieve the Match for EObjects when comparing them.
648 * The diff which merging will trigger the need for an insertion index in its target list.
650 * {@code true} if the merging will be done into the left list, so that we should consider the
651 * right model as the source and the left as the target.
652 * @return The index at which this {@code diff}'s value should be inserted into the 'target' list, as
653 * inferred from {@code rightToLeft}. {@code -1} if the value should be inserted at the end of its
655 * @see DiffUtil#findInsertionIndex(Comparison, Diff, boolean)
657 protected int findInsertionIndex(Comparison comparison
, Diff diff
, boolean rightToLeft
) {
658 return DiffUtil
.findInsertionIndex(comparison
, diff
, rightToLeft
);