1 /*******************************************************************************
2 * Copyright (c) 2012, 2014 Obeo.
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 *******************************************************************************/
11 package org
.eclipse
.emf
.compare
.internal
.utils
;
13 import static com
.google
.common
.base
.Predicates
.and
;
14 import static com
.google
.common
.base
.Predicates
.not
;
15 import static com
.google
.common
.base
.Predicates
.or
;
16 import static com
.google
.common
.collect
.Iterables
.addAll
;
17 import static org
.eclipse
.emf
.compare
.utils
.EMFComparePredicates
.fromSide
;
18 import static org
.eclipse
.emf
.compare
.utils
.EMFComparePredicates
.ofKind
;
20 import com
.google
.common
.base
.Predicate
;
21 import com
.google
.common
.collect
.HashMultiset
;
22 import com
.google
.common
.collect
.Iterables
;
23 import com
.google
.common
.collect
.Lists
;
24 import com
.google
.common
.collect
.Multiset
;
25 import com
.google
.common
.collect
.Sets
;
27 import java
.util
.ArrayList
;
28 import java
.util
.Arrays
;
29 import java
.util
.Collections
;
30 import java
.util
.Iterator
;
31 import java
.util
.LinkedHashSet
;
32 import java
.util
.List
;
33 import java
.util
.ListIterator
;
36 import org
.eclipse
.emf
.compare
.AttributeChange
;
37 import org
.eclipse
.emf
.compare
.Comparison
;
38 import org
.eclipse
.emf
.compare
.Conflict
;
39 import org
.eclipse
.emf
.compare
.ConflictKind
;
40 import org
.eclipse
.emf
.compare
.Diff
;
41 import org
.eclipse
.emf
.compare
.DifferenceKind
;
42 import org
.eclipse
.emf
.compare
.DifferenceSource
;
43 import org
.eclipse
.emf
.compare
.DifferenceState
;
44 import org
.eclipse
.emf
.compare
.EMFCompareMessages
;
45 import org
.eclipse
.emf
.compare
.Equivalence
;
46 import org
.eclipse
.emf
.compare
.Match
;
47 import org
.eclipse
.emf
.compare
.ReferenceChange
;
48 import org
.eclipse
.emf
.compare
.utils
.IEqualityHelper
;
49 import org
.eclipse
.emf
.compare
.utils
.ReferenceUtil
;
50 import org
.eclipse
.emf
.ecore
.EObject
;
51 import org
.eclipse
.emf
.ecore
.EReference
;
52 import org
.eclipse
.emf
.ecore
.EStructuralFeature
;
55 * This utility class will be used to provide similarity implementations.
57 * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
59 public final class DiffUtil
{
60 /** This utility class does not need to be instantiated. */
62 // Hides default constructor
66 * Computes the dice coefficient between the two given String's bigrams.
68 * This implementation is case sensitive.
72 * First of the two Strings to compare.
74 * Second of the two Strings to compare.
75 * @return The dice coefficient of the two given String's bigrams, ranging from 0 to 1.
77 public static double diceCoefficient(String first
, String second
) {
78 final char[] str1
= first
.toCharArray();
79 final char[] str2
= second
.toCharArray();
81 final double coefficient
;
83 if (Arrays
.equals(str1
, str2
)) {
85 } else if (str1
.length
<= 2 || str2
.length
<= 2) {
88 for (int i
= 0; i
< Math
.min(str1
.length
, str2
.length
); i
++) {
89 if (str1
[i
] == str2
[i
]) {
94 int union
= str1
.length
+ str2
.length
;
95 if (str1
.length
!= str2
.length
) {
96 coefficient
= (double)equalChars
/ union
;
98 coefficient
= ((double)equalChars
* 2) / union
;
101 Set
<String
> s1Bigrams
= Sets
.newHashSet();
102 Set
<String
> s2Bigrams
= Sets
.newHashSet();
104 for (int i
= 0; i
< str1
.length
- 1; i
++) {
105 char[] chars
= new char[] {str1
[i
], str1
[i
+ 1], };
106 s1Bigrams
.add(String
.valueOf(chars
));
108 for (int i
= 0; i
< str2
.length
- 1; i
++) {
109 char[] chars
= new char[] {str2
[i
], str2
[i
+ 1], };
110 s2Bigrams
.add(String
.valueOf(chars
));
113 Set
<String
> intersection
= Sets
.intersection(s1Bigrams
, s2Bigrams
);
114 coefficient
= (2d
* intersection
.size()) / (s1Bigrams
.size() + s2Bigrams
.size());
121 * This will compute the longest common subsequence between the two given Lists, ignoring any object that
122 * is included in {@code ignoredElements}. We will use
123 * {@link IEqualityHelper#matchingValues(Object, Object)} in order to try and match the values from both
124 * lists two-by-two. This can thus be used both for reference values or attribute values. If there are two
125 * subsequences of the same "longest" length, the first (according to the second argument) will be
128 * Take note that this might be slower than {@link #longestCommonSubsequence(Comparison, List, List)} and
129 * should only be used when elements should be removed from the potential LCS. This is mainly aimed at
130 * merge operations during three-way comparisons as some objects might be in conflict and thus shifting
131 * the computed insertion indices.
134 * Please see {@link #longestCommonSubsequence(Comparison, List, List)} for a more complete description.
138 * This will be used in order to retrieve the Match for EObjects when comparing them.
139 * @param ignoredElements
140 * Specifies elements that should be excluded from the subsequences.
142 * First of the two sequences to consider.
144 * Second of the two sequences to consider.
146 * Type of the sequences content.
147 * @return The LCS of the two given sequences. Will never be the same instance as one of the input
149 * @see #longestCommonSubsequence(Comparison, List, List).
151 public static <E
> List
<E
> longestCommonSubsequence(Comparison comparison
, Iterable
<E
> ignoredElements
,
152 List
<E
> sequence1
, List
<E
> sequence2
) {
153 final List
<E
> copy1
= Lists
.newArrayList(sequence1
);
154 final List
<E
> copy2
= Lists
.newArrayList(sequence2
);
157 final List
<E
> prefix
= trimPrefix(comparison
, ignoredElements
, copy1
, copy2
);
158 final List
<E
> suffix
= trimSuffix(comparison
, ignoredElements
, copy1
, copy2
);
160 final List
<E
> subLCS
;
161 // FIXME extract an interface for the LCS and properly separate these two differently typed
163 if (copy1
.size() > Short
.MAX_VALUE
|| copy2
.size() > Short
.MAX_VALUE
) {
164 subLCS
= intLongestCommonSubsequence(comparison
, ignoredElements
, copy1
, copy2
);
166 subLCS
= shortLongestCommonSubsequence(comparison
, ignoredElements
, copy1
, copy2
);
169 final List
<E
> lcs
= new ArrayList
<E
>(prefix
.size() + subLCS
.size() + suffix
.size());
173 return Collections
.unmodifiableList(lcs
);
177 * This will compute the longest common subsequence between the two given Lists. We will use
178 * {@link IEqualityHelper#matchingValues(Object, Object)} in order to try and match the values from both
179 * lists two-by-two. This can thus be used both for reference values or attribute values. If there are two
180 * subsequences of the same "longest" length, the first (according to the second argument) will be
183 * For example, it the two given sequence are, in this order, <code>{"a", "b", "c", "d", "e"}</code> and
184 * <code>{"c", "z", "d", "a", "b"}</code>, there are two "longest" subsequences : <code>{"a", "b"}</code>
185 * and <code>{"c", "d"}</code>. The first of those two subsequences in the second list is
186 * <code>{"c", "d"}</code>. On the other hand, the LCS of <code>{"a", "b", "c", "d", "e"}</code> and
187 * <code>{"y", "c", "d", "e", "b"}</code> is <code>{"c", "d", "e"}</code>.
190 * The following algorithm has been inferred from the wikipedia article on the Longest Common Subsequence,
191 * http://en.wikipedia.org/wiki/Longest_common_subsequence_problem at the time of writing. It is
192 * decomposed in two : we first compute the LCS matrix, then we backtrack through the input to determine
193 * the LCS. Evaluation will be shortcut after the first part if the LCS is one of the two input sequences.
196 * Note : we are not using Iterables as input in order to make use of the random access cost of
197 * ArrayLists. This might also be converted to directly use arrays. This implementation will not play well
198 * with LinkedLists or any List which needs to iterate over the values for each call to
199 * {@link List#get(int)}, i.e any list which is not instanceof RandomAccess or does not satisfy its
204 * This will be used in order to retrieve the Match for EObjects when comparing them.
206 * First of the two sequences to consider.
208 * Second of the two sequences to consider.
210 * Type of the sequences content.
211 * @return The LCS of the two given sequences. Will never be the same instance as one of the input
214 public static <E
> List
<E
> longestCommonSubsequence(Comparison comparison
, List
<E
> sequence1
,
216 return longestCommonSubsequence(comparison
, Collections
.<E
> emptyList(), sequence1
, sequence2
);
220 * Trims and returns the common prefix of the two given sequences. All ignored elements within or after
221 * this common prefix will also be trimmed.
223 * Note that the two given sequences will be modified in-place.
227 * This will be used in order to retrieve the Match for EObjects when comparing them.
228 * @param ignoredElements
229 * Specifies elements that should be excluded from the subsequences.
231 * First of the two sequences to consider.
233 * Second of the two sequences to consider.
235 * Type of the sequences content.
236 * @return The common prefix of the two given sequences, less their ignored elements. As a side note, both
237 * {@code sequence1} and {@code sequence2} will have been trimmed of their prefix when this
240 private static <E
> List
<E
> trimPrefix(Comparison comparison
, Iterable
<E
> ignoredElements
,
241 List
<E
> sequence1
, List
<E
> sequence2
) {
242 final IEqualityHelper equalityHelper
= comparison
.getEqualityHelper();
243 final int size1
= sequence1
.size();
244 final int size2
= sequence2
.size();
246 final List
<E
> prefix
= Lists
.newArrayList();
249 boolean matching
= true;
250 while (start1
<= size1
&& start2
<= size2
&& matching
) {
251 final E first
= sequence1
.get(start1
- 1);
252 final E second
= sequence2
.get(start2
- 1);
253 if (equalityHelper
.matchingValues(first
, second
)) {
258 boolean ignore1
= contains(comparison
, equalityHelper
, ignoredElements
, first
);
259 boolean ignore2
= contains(comparison
, equalityHelper
, ignoredElements
, second
);
266 if (!ignore1
&& !ignore2
) {
271 sequence1
.subList(0, start1
- 1).clear();
272 sequence2
.subList(0, start2
- 1).clear();
278 * Trims and returns the common suffix of the two given sequences. All ignored elements within or before
279 * this common suffix will also be trimmed.
281 * Note that the two given sequences will be modified in-place.
285 * This will be used in order to retrieve the Match for EObjects when comparing them.
286 * @param ignoredElements
287 * Specifies elements that should be excluded from the subsequences.
289 * First of the two sequences to consider.
291 * Second of the two sequences to consider.
293 * Type of the sequences content.
294 * @return The common suffix of the two given sequences, less their ignored elements. As a side note, both
295 * {@code sequence1} and {@code sequence2} will have been trimmed of their suffix when this
298 private static <E
> List
<E
> trimSuffix(Comparison comparison
, Iterable
<E
> ignoredElements
,
299 List
<E
> sequence1
, List
<E
> sequence2
) {
300 final IEqualityHelper equalityHelper
= comparison
.getEqualityHelper();
301 final int size1
= sequence1
.size();
302 final int size2
= sequence2
.size();
304 final List
<E
> suffix
= Lists
.newArrayList();
307 boolean matching
= true;
308 while (end1
> 0 && end2
> 0 && matching
) {
309 final E first
= sequence1
.get(end1
- 1);
310 final E second
= sequence2
.get(end2
- 1);
311 if (equalityHelper
.matchingValues(first
, second
)) {
316 boolean ignore1
= contains(comparison
, equalityHelper
, ignoredElements
, first
);
317 boolean ignore2
= contains(comparison
, equalityHelper
, ignoredElements
, second
);
324 if (!ignore1
&& !ignore2
) {
329 sequence1
.subList(end1
, size1
).clear();
330 sequence2
.subList(end2
, size2
).clear();
332 return Lists
.reverse(suffix
);
336 * Checks whether the given {@code sequence} contains the given {@code element} according to the semantics
337 * of the given {@code equalityHelper}.
340 * This will be used in order to retrieve the Match for EObjects when comparing them.
341 * @param equalityHelper
342 * The {@link IEqualityHelper} gives us the necessary semantics for Object matching.
344 * The sequence which elements we need to compare with {@code element}.
346 * The element we are seeking in {@code sequence}.
348 * Type of the sequence content.
349 * @return {@code true} if the given {@code sequence} contains an element matching {@code element},
350 * {@code false} otherwise.
351 * @see IEqualityHelper#matchingValues(Comparison, Object, Object)
353 private static <E
> boolean contains(Comparison comparison
, IEqualityHelper equalityHelper
,
354 Iterable
<E
> sequence
, E element
) {
355 final Iterator
<E
> iterator
= sequence
.iterator();
356 while (iterator
.hasNext()) {
357 E candidate
= iterator
.next();
358 if (equalityHelper
.matchingValues(candidate
, element
)) {
366 * This is a classic, single-threaded implementation. We use shorts for the score matrix so as to limit
367 * the memory cost (we know the max LCS length is not greater than Short#MAX_VALUE).
370 * This will be used in order to retrieve the Match for EObjects when comparing them.
371 * @param ignoredElements
372 * Specifies elements that should be excluded from the subsequences.
374 * First of the two sequences to consider.
376 * Second of the two sequences to consider.
378 * Type of the sequences content.
379 * @return The LCS of the two given sequences. Will never be the same instance as one of the input
382 private static <E
> List
<E
> shortLongestCommonSubsequence(Comparison comparison
,
383 Iterable
<E
> ignoredElements
, List
<E
> sequence1
, List
<E
> sequence2
) {
384 final IEqualityHelper equalityHelper
= comparison
.getEqualityHelper();
385 final int size1
= sequence1
.size();
386 final int size2
= sequence2
.size();
388 final short[][] matrix
= new short[size1
+ 1][size2
+ 1];
390 // Compute the LCS matrix
391 for (int i
= 1; i
<= size1
; i
++) {
392 final E first
= sequence1
.get(i
- 1);
393 for (int j
= 1; j
<= size2
; j
++) {
394 // assume array dereferencing and arithmetics faster than equals
395 final short current
= matrix
[i
- 1][j
- 1];
396 final short nextIfNoMatch
= (short)Math
.max(matrix
[i
- 1][j
], matrix
[i
][j
- 1]);
398 if (nextIfNoMatch
> current
) {
399 matrix
[i
][j
] = nextIfNoMatch
;
401 final E second
= sequence2
.get(j
- 1);
402 if (equalityHelper
.matchingValues(first
, second
)
403 && !contains(comparison
, equalityHelper
, ignoredElements
, second
)) {
404 matrix
[i
][j
] = (short)(1 + current
);
406 matrix
[i
][j
] = nextIfNoMatch
;
412 // Traceback the matrix to create the final LCS
413 int current1
= size1
;
414 int current2
= size2
;
415 final List
<E
> result
= Lists
.newArrayList();
417 while (current1
> 0 && current2
> 0) {
418 final short currentLength
= matrix
[current1
][current2
];
419 final short nextLeft
= matrix
[current1
][current2
- 1];
420 final short nextUp
= matrix
[current1
- 1][current2
];
421 if (currentLength
> nextLeft
&& currentLength
> nextUp
) {
422 result
.add(sequence1
.get(current1
- 1));
425 } else if (nextLeft
>= nextUp
) {
432 return Lists
.reverse(result
);
436 * This is a classic, single-threaded implementation. We know the max LCS length is greater than
437 * Short#MAX_VALUE... the score matrix will thus be int-typed, resulting in a huge memory cost.
440 * This will be used in order to retrieve the Match for EObjects when comparing them.
441 * @param ignoredElements
442 * Specifies elements that should be excluded from the subsequences.
444 * First of the two sequences to consider.
446 * Second of the two sequences to consider.
448 * Type of the sequences content.
449 * @return The LCS of the two given sequences. Will never be the same instance as one of the input
452 private static <E
> List
<E
> intLongestCommonSubsequence(Comparison comparison
,
453 Iterable
<E
> ignoredElements
, List
<E
> sequence1
, List
<E
> sequence2
) {
454 final IEqualityHelper equalityHelper
= comparison
.getEqualityHelper();
455 final int size1
= sequence1
.size();
456 final int size2
= sequence2
.size();
458 final int[][] matrix
= new int[size1
+ 1][size2
+ 1];
460 // Compute the LCS matrix
461 for (int i
= 1; i
<= size1
; i
++) {
462 final E first
= sequence1
.get(i
- 1);
463 for (int j
= 1; j
<= size2
; j
++) {
464 // assume array dereferencing and arithmetics faster than equals
465 final int current
= matrix
[i
- 1][j
- 1];
466 final int nextIfNoMatch
= Math
.max(matrix
[i
- 1][j
], matrix
[i
][j
- 1]);
468 if (nextIfNoMatch
> current
) {
469 matrix
[i
][j
] = nextIfNoMatch
;
471 final E second
= sequence2
.get(j
- 1);
472 if (equalityHelper
.matchingValues(first
, second
)
473 && !contains(comparison
, equalityHelper
, ignoredElements
, second
)) {
474 matrix
[i
][j
] = 1 + current
;
476 matrix
[i
][j
] = nextIfNoMatch
;
482 // Traceback the matrix to create the final LCS
483 int current1
= size1
;
484 int current2
= size2
;
485 final List
<E
> result
= Lists
.newArrayList();
487 while (current1
> 0 && current2
> 0) {
488 final int currentLength
= matrix
[current1
][current2
];
489 final int nextLeft
= matrix
[current1
][current2
- 1];
490 final int nextUp
= matrix
[current1
- 1][current2
];
491 if (currentLength
> nextLeft
&& currentLength
> nextUp
) {
492 result
.add(sequence1
.get(current1
- 1));
495 } else if (nextLeft
>= nextUp
) {
502 return Lists
.reverse(result
);
506 * TODO perf : all "lookups" in source and target could be rewritten by using the lcs elements' matches.
507 * This may or may not help, should be profiled.
510 * This will try and determine the index at which a given element from the {@code source} list should be
511 * inserted in the {@code target} list. We expect {@code newElement} to be an element from the
512 * {@code source} or to have a Match that allows us to map it to one of the {@code source} list's
515 * The expected insertion index will always be relative to the Longest Common Subsequence (LCS) between
516 * the two given lists, ignoring all elements from that LCS that have changed between the target list and
517 * the common origin of the two. If there are more than one "longest" subsequence between the two lists,
518 * the insertion index will be relative to the first that comes in the {@code target} list.
521 * Note : we are not using Iterables as input in order to make use of the random access cost of
522 * ArrayLists. This might also be converted to directly use arrays. This implementation will not play well
523 * with LinkedLists or any List which needs to iterate over the values for each call to
524 * {@link List#get(int)}, i.e any list which is not instanceof RandomAccess or does not satisfy its
529 * This will be used in order to retrieve the Match for EObjects when comparing them.
530 * @param ignoredElements
531 * If there are elements from {@code target} that should be ignored when searching for an
532 * insertion index, set them here. Can be {@code null} or an empty list.
534 * The List from which one element has to be added to the {@code target} list.
536 * The List into which one element from {@code source} has to be added.
538 * The element from {@code source} that needs to be added into {@code target}.
540 * Type of the sequences content.
541 * @return The index at which {@code newElement} should be inserted in {@code target}.
542 * @see #longestCommonSubsequence(Comparison, List, List)
543 * @noreference This method is not intended to be referenced by clients.
545 public static <E
> int findInsertionIndex(Comparison comparison
, Iterable
<E
> ignoredElements
,
546 List
<E
> source
, List
<E
> target
, E newElement
) {
547 final IEqualityHelper equalityHelper
= comparison
.getEqualityHelper();
550 if (ignoredElements
!= null) {
551 lcs
= longestCommonSubsequence(comparison
, ignoredElements
, source
, target
);
553 lcs
= longestCommonSubsequence(comparison
, source
, target
);
558 if (lcs
.size() > 0) {
559 firstLCS
= lcs
.get(0);
560 lastLCS
= lcs
.listIterator(lcs
.size()).previous();
563 final int noLCS
= -2;
564 int currentIndex
= -1;
565 int firstLCSIndex
= -1;
566 int lastLCSIndex
= -1;
567 if (firstLCS
== null) {
569 firstLCSIndex
= noLCS
;
570 lastLCSIndex
= noLCS
;
573 ListIterator
<E
> sourceIterator
= source
.listIterator();
574 for (int i
= 0; sourceIterator
.hasNext() && (currentIndex
== -1 || firstLCSIndex
== -1); i
++) {
575 final E sourceElement
= sourceIterator
.next();
576 if (currentIndex
== -1 && equalityHelper
.matchingValues(sourceElement
, newElement
)) {
579 if (firstLCSIndex
== -1 && equalityHelper
.matchingValues(sourceElement
, firstLCS
)) {
583 // The list may contain duplicates, use a reverse iteration to find the last from LCS.
584 final int sourceSize
= source
.size();
585 sourceIterator
= source
.listIterator(sourceSize
);
586 for (int i
= sourceSize
- 1; sourceIterator
.hasPrevious() && lastLCSIndex
== -1; i
--) {
587 final E sourceElement
= sourceIterator
.previous();
588 if (lastLCSIndex
== -1 && equalityHelper
.matchingValues(sourceElement
, lastLCS
)) {
593 int insertionIndex
= -1;
594 if (firstLCSIndex
== noLCS
) {
595 // We have no LCS. The two lists have no element in common. Insert at the very end of the target.
596 insertionIndex
= target
.size();
597 } else if (currentIndex
< firstLCSIndex
) {
598 // The object we are to insert is before the LCS in source.
599 insertionIndex
= insertBeforeLCS(target
, equalityHelper
, firstLCS
);
600 } else if (currentIndex
> lastLCSIndex
) {
601 // The object we are to insert is after the LCS in source.
602 insertionIndex
= findInsertionIndexAfterLCS(target
, equalityHelper
, lastLCS
);
604 // Our object is in-between two elements A and B of the LCS in source
605 insertionIndex
= findInsertionIndexWithinLCS(source
, target
, equalityHelper
, lcs
, currentIndex
);
608 // We somehow failed to determine the insertion index. Insert at the very end.
609 if (insertionIndex
== -1) {
610 insertionIndex
= target
.size();
613 return insertionIndex
;
617 * This will be called to try and find the insertion index for an element that is located in-between two
618 * elements of the LCS between {@code source} and {@code target}.
621 * The List from which one element has to be added to the {@code target} list.
623 * The List into which one element from {@code source} has to be added.
624 * @param equalityHelper
625 * The equality helper to use for this computation.
627 * The lcs between {@code source} and {@code target}.
628 * @param currentIndex
629 * Current index (in {@code source} of the element we are to insert into {@code target}.
631 * Type of the sequences content.
632 * @return The index in the target list in which should be inserted that element.
634 private static <E
> int findInsertionIndexWithinLCS(List
<E
> source
, List
<E
> target
,
635 final IEqualityHelper equalityHelper
, final List
<E
> lcs
, int currentIndex
) {
636 int insertionIndex
= -1;
638 * If any element of the subsequence {<index of A>, <index of B>} from source had been in the same
639 * subsequence in target, it would have been part of the LCS. We thus know none is.
641 // The insertion index will be just after A in target
643 // First, find which element of the LCS is "A"
644 int lcsIndexOfSubsequenceStart
= -1;
645 for (int i
= 0; i
< currentIndex
; i
++) {
646 final E sourceElement
= source
.get(i
);
648 boolean isInLCS
= false;
649 for (int j
= lcsIndexOfSubsequenceStart
+ 1; j
< lcs
.size() && !isInLCS
; j
++) {
650 final E lcsElement
= lcs
.get(j
);
652 if (equalityHelper
.matchingValues(sourceElement
, lcsElement
)) {
654 lcsIndexOfSubsequenceStart
++;
659 if (lcsIndexOfSubsequenceStart
> -1) {
660 // Do we have duplicates before A in the lcs?
661 final Multiset
<E
> dupesLCS
= HashMultiset
.create(lcs
.subList(0, lcsIndexOfSubsequenceStart
+ 1));
662 final E subsequenceStart
= lcs
.get(lcsIndexOfSubsequenceStart
);
663 int duplicatesToGo
= dupesLCS
.count(subsequenceStart
) - 1;
665 // Then, find the index of "A" in target
666 for (int i
= 0; i
< target
.size() && insertionIndex
== -1; i
++) {
667 final E targetElement
= target
.get(i
);
669 if (equalityHelper
.matchingValues(targetElement
, subsequenceStart
)) {
670 if (duplicatesToGo
> 0) {
673 insertionIndex
= i
+ 1;
679 return insertionIndex
;
683 * This will be called when we are to insert an element after the LCS in the {@code target} list.
686 * The List into which one element has to be added.
687 * @param equalityHelper
688 * The equality helper to use for this computation.
690 * The last element of the LCS.
692 * Type of the sequences content.
693 * @return The index to use for insertion into {@code target} in order to add an element just after the
696 private static <E
> int findInsertionIndexAfterLCS(List
<E
> target
, IEqualityHelper equalityHelper
,
698 int insertionIndex
= -1;
699 // The insertion index will be inside the subsequence {<LCS end>, <list.size()>} in target.
701 * We'll insert it just after the LCS end : there cannot be any common element between the two lists
702 * "after" the LCS since it would be part of the LCS itself.
704 for (int i
= target
.size() - 1; i
>= 0 && insertionIndex
== -1; i
--) {
705 final E targetElement
= target
.get(i
);
706 if (equalityHelper
.matchingValues(targetElement
, lastLCS
)) {
707 // We've reached the last element of the LCS in target. insert after it.
708 insertionIndex
= i
+ 1;
711 return insertionIndex
;
715 * This will be called when we are to insert an element before the LCS in the {@code target} list.
718 * The List into which one element has to be added.
719 * @param equalityHelper
720 * The equality helper to use for this computation.
722 * The first element of the LCS.
724 * Type of the sequences content.
725 * @return The index to use for insertion into {@code target} in order to add an element just before the
728 private static <E
> int insertBeforeLCS(List
<E
> target
, IEqualityHelper equalityHelper
, E firstLCS
) {
729 int insertionIndex
= -1;
730 // The insertion index will be inside the subsequence {0, <LCS start>} in target
732 * We'll insert it just before the LCS start : there cannot be any common element between the two
733 * lists "before" the LCS since it would be part of the LCS itself.
735 for (int i
= 0; i
< target
.size() && insertionIndex
== -1; i
++) {
736 final E targetElement
= target
.get(i
);
738 if (equalityHelper
.matchingValues(targetElement
, firstLCS
)) {
739 // We've reached the first element from the LCS in target. Insert here
743 return insertionIndex
;
747 * This will try and determine the index at which a given element from the {@code source} list should be
748 * inserted in the {@code target} list. We expect {@code newElement} to be an element from the
749 * {@code source} or to have a Match that allows us to map it to one of the {@code source} list's
752 * The expected insertion index will always be relative to the Longest Common Subsequence (LCS) between
753 * the two given lists. If there are more than one "longest" subsequence between the two lists, the
754 * insertion index will be relative to the first that comes in the {@code target} list.
757 * For example, assume {@code source} is <code>{"1", "2", "4", "6", "8", "3", "0", "7", "5"}</code> and
758 * {@code target} is <code>{"8", "1", "2", "9", "3", "4", "7"}</code>; I try to merge the addition of
759 * {@code "0"} in the right list. The returned "insertion index" will be {@code 5} : just after
760 * {@code "3"}. There are two subsequence of the same "longest" length 4 :
761 * <code>{"1", "2", "3", "7"}</code> and <code>{"1", "2", "4", "7"}</code>. However, the first of those
762 * two in {@code target} is <code>{"1", "2", "3", "7"}</code>. The closest element before {@code "0"} in
763 * this LCS in {@code source} is {@code "3"}.
766 * Note : we are not using Iterables as input in order to make use of the random access cost of
767 * ArrayLists. This might also be converted to directly use arrays. This implementation will not play well
768 * with LinkedLists or any List which needs to iterate over the values for each call to
769 * {@link List#get(int)}, i.e any list which is not instanceof RandomAccess or does not satisfy its
774 * This will be used in order to retrieve the Match for EObjects when comparing them.
776 * The List from which one element has to be added to the {@code target} list.
778 * The List into which one element from {@code source} has to be added.
780 * The element from {@code source} that needs to be added into {@code target}.
782 * Type of the sequences content.
783 * @return The index at which {@code newElement} should be inserted in {@code target}.
784 * @see #longestCommonSubsequence(Comparison, List, List)
786 public static <E
> int findInsertionIndex(Comparison comparison
, List
<E
> source
, List
<E
> target
,
788 return findInsertionIndex(comparison
, null, source
, target
, newElement
);
792 * This is the main entry point for {@link #findInsertionIndex(Comparison, Iterable, List, List, Object)}.
793 * It will use default algorithms to determine the source and target lists as well as the list of elements
794 * that should be ignored when computing the insertion index.
797 * This will be used in order to retrieve the Match for EObjects when comparing them.
799 * The diff which merging will trigger the need for an insertion index in its target list.
801 * {@code true} if the merging will be done into the left list, so that we should consider the
802 * right model as the source and the left as the target.
803 * @return The index at which this {@code diff}'s value should be inserted into the 'target' list, as
804 * inferred from {@code rightToLeft}.
805 * @see #findInsertionIndex(Comparison, Iterable, List, List, Object)
807 public static int findInsertionIndex(Comparison comparison
, Diff diff
, boolean rightToLeft
) {
808 final EStructuralFeature feature
;
810 if (diff
instanceof AttributeChange
) {
811 feature
= ((AttributeChange
)diff
).getAttribute();
812 value
= ((AttributeChange
)diff
).getValue();
813 } else if (diff
instanceof ReferenceChange
) {
814 feature
= ((ReferenceChange
)diff
).getReference();
815 value
= ((ReferenceChange
)diff
).getValue();
817 throw new IllegalArgumentException(EMFCompareMessages
.getString(
818 "DiffUtil.IllegalDiff", diff
.eClass().getName())); //$NON-NLS-1$
820 if (!feature
.isMany()) {
821 throw new IllegalArgumentException(EMFCompareMessages
.getString(
822 "DiffUtil.IllegalFeature", feature
.getName())); //$NON-NLS-1$
824 final Match match
= diff
.getMatch();
826 final EObject expectedContainer
;
827 if (feature
instanceof EReference
&& ((EReference
)feature
).isContainment()
828 && diff
.getKind() == DifferenceKind
.MOVE
) {
829 // The value can only be an EObject, and its match cannot be null.
830 // If any of these two assumptions is wrong, something went horribly awry beforehand.
831 final Match valueMatch
= comparison
.getMatch((EObject
)value
);
833 final Match targetContainerMatch
;
834 // If it exists, use the source side's container as reference
835 if (rightToLeft
&& valueMatch
.getRight() != null) {
836 targetContainerMatch
= comparison
.getMatch(valueMatch
.getRight().eContainer());
837 } else if (!rightToLeft
&& valueMatch
.getLeft() != null) {
838 targetContainerMatch
= comparison
.getMatch(valueMatch
.getLeft().eContainer());
840 // Otherwise, the value we're moving on one side has been removed from its source side.
841 targetContainerMatch
= comparison
.getMatch(valueMatch
.getOrigin().eContainer());
844 expectedContainer
= targetContainerMatch
.getLeft();
846 expectedContainer
= targetContainerMatch
.getRight();
848 } else if (rightToLeft
) {
849 expectedContainer
= match
.getLeft();
851 expectedContainer
= match
.getRight();
854 final List
<Object
> sourceList
= getSourceList(diff
, feature
, rightToLeft
);
855 final List
<Object
> targetList
= ReferenceUtil
.getAsList(expectedContainer
, feature
);
857 Iterable
<Object
> ignoredElements
= Iterables
.concat(computeIgnoredElements(targetList
, diff
),
858 Collections
.singleton(value
));
859 // We know we'll have to iterate quite a number of times on this one.
860 ignoredElements
= Lists
.newArrayList(ignoredElements
);
862 return DiffUtil
.findInsertionIndex(comparison
, ignoredElements
, sourceList
, targetList
, value
);
866 * Get the list of all required differences for merge of the given difference (required, required of
867 * required..., equivalences and pseudo conflicts).
870 * the given difference.
873 * @return the list of all required differences.
876 public static Set
<Diff
> getRequires(Diff diff
, boolean leftToRight
) {
877 return getAllRequires(diff
, diff
, leftToRight
, Sets
.newHashSet());
881 * Get the list of required differences (only the first level) for merge of the given original difference.
884 * the current difference being processed.
885 * @param originalDiff
886 * the original given difference.
889 * @param processedDiffs
890 * the list of already processed diffs.
891 * @return the list of all required differences.
893 private static Set
<Diff
> getFirstLevelRequires(Diff currentDiff
, final Diff originalDiff
,
894 boolean leftToRight
, Set
<Object
> processedDiffs
) {
895 Set
<Diff
> requires
= Sets
.newHashSet();
896 DifferenceSource originalDiffSource
= originalDiff
.getSource();
897 // Add requires or requiredBy according to the way of merge and the source of the diff.
899 if (DifferenceSource
.LEFT
== originalDiffSource
) {
900 addAll(requires
, currentDiff
.getRequires());
901 } else if (DifferenceSource
.RIGHT
== originalDiffSource
) {
902 addAll(requires
, currentDiff
.getRequiredBy());
907 if (DifferenceSource
.RIGHT
== originalDiffSource
) {
908 addAll(requires
, currentDiff
.getRequires());
909 } else if (DifferenceSource
.LEFT
== originalDiffSource
) {
910 addAll(requires
, currentDiff
.getRequiredBy());
916 // Add the refined differences.
917 addAll(requires
, currentDiff
.getRefinedBy());
918 // Add the equivalences.
919 addAll(requires
, getEquivalences(currentDiff
));
920 // Add the pseudo conflicted differences.
921 Conflict conflict
= currentDiff
.getConflict();
922 if (conflict
!= null && conflict
.getKind() == ConflictKind
.PSEUDO
) {
923 addAll(requires
, conflict
.getDifferences());
926 addAll(processedDiffs
, requires
);
927 requires
.remove(currentDiff
);
933 * Get the list of required differences (only the first level) for merge of the given original difference.
936 * the current difference being processed.
937 * @param originalDiff
938 * the original given difference.
941 * @param processedDiffs
942 * the list of already processed diffs.
943 * @return the list of all required differences.
945 private static Set
<Diff
> getAllRequires(Diff currentDiff
, final Diff originalDiff
, boolean leftToRight
,
946 Set
<Object
> processedDiffs
) {
947 Set
<Diff
> requires
= Sets
.newHashSet();
948 Set
<Diff
> firstLevelRequires
= getFirstLevelRequires(currentDiff
, originalDiff
, leftToRight
,
950 addAll(requires
, firstLevelRequires
);
951 for (Diff require
: firstLevelRequires
) {
952 if (!originalDiff
.equals(require
) && !processedDiffs
.contains(require
)) {
953 addAll(requires
, getAllRequires(require
, originalDiff
, leftToRight
, processedDiffs
));
960 * Get the list of all unmergeable differences after the merge of the given difference.
963 * the given difference.
966 * @return the list of all unmergeable differences.
969 public static Set
<Diff
> getUnmergeables(Diff diff
, boolean leftToRight
) {
970 return getAllUnmergeables(diff
, leftToRight
, Sets
.newHashSet());
974 * Get the list of unmergeable differences (only the first level) after the merge of the given difference.
977 * the given difference.
980 * @param processedDiffs
981 * the list of already processed diffs.
982 * @return the list of unmergeable differences.
984 private static Set
<Diff
> getFirstLevelUnmergeables(Diff diff
, boolean leftToRight
,
985 Set
<Object
> processedDiffs
) {
986 Set
<Diff
> unmergeables
= Sets
.newHashSet();
987 Conflict conflict
= diff
.getConflict();
988 if (conflict
!= null && conflict
.getKind() == ConflictKind
.REAL
) {
989 for (Diff diffConflict
: conflict
.getDifferences()) {
991 && and(fromSide(DifferenceSource
.LEFT
),
992 or(ofKind(DifferenceKind
.ADD
), ofKind(DifferenceKind
.CHANGE
))).apply(diff
)) {
993 if (and(fromSide(DifferenceSource
.RIGHT
),
994 or(ofKind(DifferenceKind
.DELETE
), ofKind(DifferenceKind
.CHANGE
))).apply(
996 unmergeables
.add(diffConflict
);
998 } else if (leftToRight
999 && and(fromSide(DifferenceSource
.LEFT
),
1000 or(ofKind(DifferenceKind
.DELETE
), ofKind(DifferenceKind
.CHANGE
))).apply(diff
)) {
1001 if (and(fromSide(DifferenceSource
.RIGHT
),
1002 or(ofKind(DifferenceKind
.ADD
), ofKind(DifferenceKind
.CHANGE
)))
1003 .apply(diffConflict
)) {
1004 unmergeables
.add(diffConflict
);
1006 } else if (!leftToRight
1007 && and(fromSide(DifferenceSource
.RIGHT
),
1008 or(ofKind(DifferenceKind
.DELETE
), ofKind(DifferenceKind
.CHANGE
))).apply(diff
)) {
1009 if (and(fromSide(DifferenceSource
.LEFT
), not(ofKind(DifferenceKind
.DELETE
))).apply(
1011 unmergeables
.add(diffConflict
);
1013 } else if (!leftToRight
1014 && and(fromSide(DifferenceSource
.RIGHT
),
1015 or(ofKind(DifferenceKind
.ADD
), ofKind(DifferenceKind
.CHANGE
))).apply(diff
)) {
1016 if (and(fromSide(DifferenceSource
.LEFT
),
1017 or(ofKind(DifferenceKind
.DELETE
), ofKind(DifferenceKind
.CHANGE
))).apply(
1019 unmergeables
.add(diffConflict
);
1026 addAll(processedDiffs
, unmergeables
);
1027 unmergeables
.remove(diff
);
1029 return unmergeables
;
1033 * Get the list of all unmergeable differences after the merge of the given difference.
1036 * the given difference.
1037 * @param leftToRight
1039 * @param processedDiffs
1040 * the list of already processed diffs.
1041 * @return the list of all unmergeable differences.
1043 private static Set
<Diff
> getAllUnmergeables(Diff diff
, boolean leftToRight
, Set
<Object
> processedDiffs
) {
1044 Set
<Diff
> unmergeables
= Sets
.newHashSet();
1045 Set
<Diff
> firstLevelUnmergeables
= getFirstLevelUnmergeables(diff
, leftToRight
, processedDiffs
);
1046 addAll(unmergeables
, firstLevelUnmergeables
);
1047 Set
<Diff
> firstLevelRequires
= getFirstLevelRequires(diff
, diff
, leftToRight
, processedDiffs
);
1048 for (Diff require
: firstLevelRequires
) {
1049 if (require
!= diff
&& !processedDiffs
.contains(require
)) {
1050 addAll(unmergeables
, getAllUnmergeables(require
, leftToRight
, processedDiffs
));
1053 return unmergeables
;
1057 * Get the list of equivalent differences of the given difference.
1061 * @return the list of equivalent differences.
1063 private static Set
<Diff
> getEquivalences(Diff diff
) {
1064 LinkedHashSet
<Diff
> equivalences
= Sets
.newLinkedHashSet();
1065 Equivalence equivalence
= diff
.getEquivalence();
1066 if (equivalence
!= null) {
1067 equivalences
.addAll(equivalence
.getDifferences());
1068 equivalences
.remove(diff
);
1070 return equivalences
;
1074 * Retrieves the "source" list of the given {@code diff}. This will be different according to the kind of
1075 * change and the direction of the merging.
1078 * The diff for which merging we need a 'source'.
1080 * The feature on which the merging is actually taking place.
1081 * @param rightToLeft
1082 * Direction of the merging. {@code true} if the merge is to be done on the left side, making
1083 * 'source' the right side, {@code false} otherwise.
1084 * @return The list that should be used as a source for this merge. May be empty, but never
1085 * <code>null</code>.
1087 private static List
<Object
> getSourceList(Diff diff
, EStructuralFeature feature
, boolean rightToLeft
) {
1088 final Match match
= diff
.getMatch();
1089 final List
<Object
> sourceList
;
1090 final EObject expectedContainer
;
1092 if (diff
.getKind() == DifferenceKind
.MOVE
) {
1093 final boolean undoingLeft
= rightToLeft
&& diff
.getSource() == DifferenceSource
.LEFT
;
1094 final boolean undoingRight
= !rightToLeft
&& diff
.getSource() == DifferenceSource
.RIGHT
;
1096 if ((undoingLeft
|| undoingRight
) && match
.getOrigin() != null) {
1097 expectedContainer
= match
.getOrigin();
1098 } else if (rightToLeft
) {
1099 expectedContainer
= match
.getRight();
1101 expectedContainer
= match
.getLeft();
1105 if (match
.getOrigin() != null && diff
.getKind() == DifferenceKind
.DELETE
) {
1106 expectedContainer
= match
.getOrigin();
1107 } else if (rightToLeft
) {
1108 expectedContainer
= match
.getRight();
1110 expectedContainer
= match
.getLeft();
1114 sourceList
= ReferenceUtil
.getAsList(expectedContainer
, feature
);
1120 * When computing the insertion index of an element in a list, we need to ignore all elements present in
1121 * that list that feature unresolved Diffs on the same feature.
1124 * The sequence in which we need to compute an insertion index.
1126 * The diff we are computing an insertion index for.
1128 * Type of the list's content.
1129 * @return The list of elements that should be ignored when computing the insertion index for a new
1130 * element in {@code candidates}.
1132 private static <E
> Iterable
<E
> computeIgnoredElements(Iterable
<E
> candidates
, final Diff diff
) {
1133 final Match match
= diff
.getMatch();
1134 final Iterable
<?
extends Diff
> filteredCandidates
= Lists
.newArrayList(match
.getDifferences());
1135 final EStructuralFeature feature
;
1136 if (diff
instanceof AttributeChange
) {
1137 feature
= ((AttributeChange
)diff
).getAttribute();
1138 } else if (diff
instanceof ReferenceChange
) {
1139 feature
= ((ReferenceChange
)diff
).getReference();
1141 return Collections
.emptyList();
1144 final Set
<E
> ignored
= Sets
.newLinkedHashSet();
1145 for (E candidate
: candidates
) {
1146 if (candidate
instanceof EObject
) {
1147 final Iterable
<?
extends Diff
> differences
= match
.getComparison().getDifferences(
1148 (EObject
)candidate
);
1149 if (Iterables
.any(differences
, new UnresolvedDiffMatching(feature
, candidate
))) {
1150 ignored
.add(candidate
);
1153 if (Iterables
.any(filteredCandidates
, new UnresolvedDiffMatching(feature
, candidate
))) {
1154 ignored
.add(candidate
);
1162 * This can be used to check whether a given Diff affects a value for which we can find another,
1163 * unresolved Diff on a given Feature.
1165 * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
1167 private static class UnresolvedDiffMatching
implements Predicate
<Diff
> {
1168 /** Feature on which we expect an unresolved diff. */
1169 private final EStructuralFeature feature
;
1171 /** Element for which we expect an unresolved diff. */
1172 private final Object element
;
1175 * Constructs a predicate that can be used to retrieve all unresolved diffs that apply to the given
1176 * {@code feature} and {@code element}.
1179 * The feature which our diffs must concern.
1181 * The element which must be our diffs' target.
1183 public UnresolvedDiffMatching(EStructuralFeature feature
, Object element
) {
1184 this.feature
= feature
;
1185 this.element
= element
;
1191 * @see com.google.common.base.Predicate#apply(java.lang.Object)
1193 public boolean apply(Diff input
) {
1194 boolean apply
= false;
1195 if (input
instanceof AttributeChange
) {
1196 apply
= input
.getState() == DifferenceState
.UNRESOLVED
1197 && ((AttributeChange
)input
).getAttribute() == feature
1198 && matchingValues((AttributeChange
)input
, element
);
1199 } else if (input
instanceof ReferenceChange
) {
1200 apply
= input
.getState() == DifferenceState
.UNRESOLVED
1201 && ((ReferenceChange
)input
).getReference() == feature
1202 && matchingValues((ReferenceChange
)input
, element
);
1210 * Checks that the value of the given diff matches <code>value</code>, resorting to the equality
1214 * The diff which value we need to check.
1216 * The expected value of <code>diff</code>
1217 * @return <code>true</code> if the value matches.
1219 private boolean matchingValues(AttributeChange diff
, Object value
) {
1220 if (diff
.getValue() == value
) {
1223 // Only resort to the equality helper as "last resort"
1224 final IEqualityHelper helper
= diff
.getMatch().getComparison().getEqualityHelper();
1225 return helper
.matchingAttributeValues(diff
.getValue(), value
);
1229 * Checks that the value of the given diff matches <code>value</code>, resorting to the equality
1233 * The diff which value we need to check.
1235 * The expected value of <code>diff</code>
1236 * @return <code>true</code> if the value matches.
1238 private boolean matchingValues(ReferenceChange diff
, Object value
) {
1239 if (diff
.getValue() == value
) {
1242 // Only resort to the equality helper as "last resort"
1243 final IEqualityHelper helper
= diff
.getMatch().getComparison().getEqualityHelper();
1244 return helper
.matchingValues(diff
.getValue(), value
);