[434822] Conflict between move and delete
[EMFCompare2.git] / plugins / org.eclipse.emf.compare / src / org / eclipse / emf / compare / internal / utils / DiffUtil.java
blobd0a9f6bd0194d1fe6f1b6e0a31352b6f369480b0
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
7 *
8 * Contributors:
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;
34 import java.util.Set;
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;
54 /**
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. */
61 private DiffUtil() {
62 // Hides default constructor
65 /**
66 * Computes the dice coefficient between the two given String's bigrams.
67 * <p>
68 * This implementation is case sensitive.
69 * </p>
71 * @param first
72 * First of the two Strings to compare.
73 * @param second
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)) {
84 coefficient = 1d;
85 } else if (str1.length <= 2 || str2.length <= 2) {
86 int equalChars = 0;
88 for (int i = 0; i < Math.min(str1.length, str2.length); i++) {
89 if (str1[i] == str2[i]) {
90 equalChars++;
94 int union = str1.length + str2.length;
95 if (str1.length != str2.length) {
96 coefficient = (double)equalChars / union;
97 } else {
98 coefficient = ((double)equalChars * 2) / union;
100 } else {
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());
117 return coefficient;
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
126 * returned.
127 * <p>
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.
132 * </p>
133 * <p>
134 * Please see {@link #longestCommonSubsequence(Comparison, List, List)} for a more complete description.
135 * </p>
137 * @param comparison
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.
141 * @param sequence1
142 * First of the two sequences to consider.
143 * @param sequence2
144 * Second of the two sequences to consider.
145 * @param <E>
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
148 * sequences.
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);
156 // Reduce sets
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
162 // implementations.
163 if (copy1.size() > Short.MAX_VALUE || copy2.size() > Short.MAX_VALUE) {
164 subLCS = intLongestCommonSubsequence(comparison, ignoredElements, copy1, copy2);
165 } else {
166 subLCS = shortLongestCommonSubsequence(comparison, ignoredElements, copy1, copy2);
169 final List<E> lcs = new ArrayList<E>(prefix.size() + subLCS.size() + suffix.size());
170 lcs.addAll(prefix);
171 lcs.addAll(subLCS);
172 lcs.addAll(suffix);
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
181 * returned.
182 * <p>
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>.
188 * </p>
189 * <p>
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.
194 * </p>
195 * <p>
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
200 * contract.
201 * </p>
203 * @param comparison
204 * This will be used in order to retrieve the Match for EObjects when comparing them.
205 * @param sequence1
206 * First of the two sequences to consider.
207 * @param sequence2
208 * Second of the two sequences to consider.
209 * @param <E>
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
212 * sequences.
214 public static <E> List<E> longestCommonSubsequence(Comparison comparison, List<E> sequence1,
215 List<E> sequence2) {
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.
222 * <p>
223 * Note that the two given sequences will be modified in-place.
224 * </p>
226 * @param comparison
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.
230 * @param sequence1
231 * First of the two sequences to consider.
232 * @param sequence2
233 * Second of the two sequences to consider.
234 * @param <E>
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
238 * returns.
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();
247 int start1 = 1;
248 int start2 = 1;
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)) {
254 prefix.add(first);
255 start1++;
256 start2++;
257 } else {
258 boolean ignore1 = contains(comparison, equalityHelper, ignoredElements, first);
259 boolean ignore2 = contains(comparison, equalityHelper, ignoredElements, second);
260 if (ignore1) {
261 start1++;
263 if (ignore2) {
264 start2++;
266 if (!ignore1 && !ignore2) {
267 matching = false;
271 sequence1.subList(0, start1 - 1).clear();
272 sequence2.subList(0, start2 - 1).clear();
274 return prefix;
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.
280 * <p>
281 * Note that the two given sequences will be modified in-place.
282 * </p>
284 * @param comparison
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.
288 * @param sequence1
289 * First of the two sequences to consider.
290 * @param sequence2
291 * Second of the two sequences to consider.
292 * @param <E>
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
296 * returns.
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();
305 int end1 = size1;
306 int end2 = size2;
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)) {
312 suffix.add(first);
313 end1--;
314 end2--;
315 } else {
316 boolean ignore1 = contains(comparison, equalityHelper, ignoredElements, first);
317 boolean ignore2 = contains(comparison, equalityHelper, ignoredElements, second);
318 if (ignore1) {
319 end1--;
321 if (ignore2) {
322 end2--;
324 if (!ignore1 && !ignore2) {
325 matching = false;
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}.
339 * @param comparison
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.
343 * @param sequence
344 * The sequence which elements we need to compare with {@code element}.
345 * @param element
346 * The element we are seeking in {@code sequence}.
347 * @param <E>
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)) {
359 return true;
362 return false;
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).
369 * @param comparison
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.
373 * @param sequence1
374 * First of the two sequences to consider.
375 * @param sequence2
376 * Second of the two sequences to consider.
377 * @param <E>
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
380 * sequences.
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;
400 } else {
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);
405 } else {
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));
423 current1--;
424 current2--;
425 } else if (nextLeft >= nextUp) {
426 current2--;
427 } else {
428 current1--;
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.
439 * @param comparison
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.
443 * @param sequence1
444 * First of the two sequences to consider.
445 * @param sequence2
446 * Second of the two sequences to consider.
447 * @param <E>
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
450 * sequences.
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;
470 } else {
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;
475 } else {
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));
493 current1--;
494 current2--;
495 } else if (nextLeft >= nextUp) {
496 current2--;
497 } else {
498 current1--;
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
513 * elements.
514 * <p>
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.
519 * </p>
520 * <p>
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
525 * contract.
526 * </p>
528 * @param comparison
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.
533 * @param source
534 * The List from which one element has to be added to the {@code target} list.
535 * @param target
536 * The List into which one element from {@code source} has to be added.
537 * @param newElement
538 * The element from {@code source} that needs to be added into {@code target}.
539 * @param <E>
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();
549 final List<E> lcs;
550 if (ignoredElements != null) {
551 lcs = longestCommonSubsequence(comparison, ignoredElements, source, target);
552 } else {
553 lcs = longestCommonSubsequence(comparison, source, target);
556 E firstLCS = null;
557 E lastLCS = null;
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) {
568 // We have no LCS
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)) {
577 currentIndex = i;
579 if (firstLCSIndex == -1 && equalityHelper.matchingValues(sourceElement, firstLCS)) {
580 firstLCSIndex = i;
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)) {
589 lastLCSIndex = i;
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);
603 } else {
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}.
620 * @param source
621 * The List from which one element has to be added to the {@code target} list.
622 * @param target
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.
626 * @param lcs
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}.
630 * @param <E>
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)) {
653 isInLCS = true;
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) {
671 duplicatesToGo--;
672 } else {
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.
685 * @param target
686 * The List into which one element has to be added.
687 * @param equalityHelper
688 * The equality helper to use for this computation.
689 * @param lastLCS
690 * The last element of the LCS.
691 * @param <E>
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
694 * LCS.
696 private static <E> int findInsertionIndexAfterLCS(List<E> target, IEqualityHelper equalityHelper,
697 E lastLCS) {
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.
717 * @param target
718 * The List into which one element has to be added.
719 * @param equalityHelper
720 * The equality helper to use for this computation.
721 * @param firstLCS
722 * The first element of the LCS.
723 * @param <E>
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
726 * LCS.
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
740 insertionIndex = i;
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
750 * elements.
751 * <p>
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.
755 * </p>
756 * <p>
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"}.
764 * </p>
765 * <p>
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
770 * contract.
771 * </p>
773 * @param comparison
774 * This will be used in order to retrieve the Match for EObjects when comparing them.
775 * @param source
776 * The List from which one element has to be added to the {@code target} list.
777 * @param target
778 * The List into which one element from {@code source} has to be added.
779 * @param newElement
780 * The element from {@code source} that needs to be added into {@code target}.
781 * @param <E>
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,
787 E newElement) {
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.
796 * @param comparison
797 * This will be used in order to retrieve the Match for EObjects when comparing them.
798 * @param diff
799 * The diff which merging will trigger the need for an insertion index in its target list.
800 * @param rightToLeft
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;
809 final Object value;
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();
816 } else {
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());
839 } else {
840 // Otherwise, the value we're moving on one side has been removed from its source side.
841 targetContainerMatch = comparison.getMatch(valueMatch.getOrigin().eContainer());
843 if (rightToLeft) {
844 expectedContainer = targetContainerMatch.getLeft();
845 } else {
846 expectedContainer = targetContainerMatch.getRight();
848 } else if (rightToLeft) {
849 expectedContainer = match.getLeft();
850 } else {
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).
869 * @param diff
870 * the given difference.
871 * @param leftToRight
872 * the way of merge.
873 * @return the list of all required differences.
874 * @since 3.0
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.
883 * @param currentDiff
884 * the current difference being processed.
885 * @param originalDiff
886 * the original given difference.
887 * @param leftToRight
888 * the way of merge.
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.
898 if (leftToRight) {
899 if (DifferenceSource.LEFT == originalDiffSource) {
900 addAll(requires, currentDiff.getRequires());
901 } else if (DifferenceSource.RIGHT == originalDiffSource) {
902 addAll(requires, currentDiff.getRequiredBy());
903 } else {
904 // Do nothing.
906 } else {
907 if (DifferenceSource.RIGHT == originalDiffSource) {
908 addAll(requires, currentDiff.getRequires());
909 } else if (DifferenceSource.LEFT == originalDiffSource) {
910 addAll(requires, currentDiff.getRequiredBy());
911 } else {
912 // Do nothing.
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);
929 return requires;
933 * Get the list of required differences (only the first level) for merge of the given original difference.
935 * @param currentDiff
936 * the current difference being processed.
937 * @param originalDiff
938 * the original given difference.
939 * @param leftToRight
940 * the way of merge.
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,
949 processedDiffs);
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));
956 return requires;
960 * Get the list of all unmergeable differences after the merge of the given difference.
962 * @param diff
963 * the given difference.
964 * @param leftToRight
965 * the way of merge.
966 * @return the list of all unmergeable differences.
967 * @since 3.0
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.
976 * @param diff
977 * the given difference.
978 * @param leftToRight
979 * the way of merge.
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()) {
990 if (leftToRight
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(
995 diffConflict)) {
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(
1010 diffConflict)) {
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(
1018 diffConflict)) {
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.
1035 * @param diff
1036 * the given difference.
1037 * @param leftToRight
1038 * the way of merge.
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.
1059 * @param diff
1060 * the given Diff.
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.
1077 * @param diff
1078 * The diff for which merging we need a 'source'.
1079 * @param feature
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();
1100 } else {
1101 expectedContainer = match.getLeft();
1104 } else {
1105 if (match.getOrigin() != null && diff.getKind() == DifferenceKind.DELETE) {
1106 expectedContainer = match.getOrigin();
1107 } else if (rightToLeft) {
1108 expectedContainer = match.getRight();
1109 } else {
1110 expectedContainer = match.getLeft();
1114 sourceList = ReferenceUtil.getAsList(expectedContainer, feature);
1116 return sourceList;
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.
1123 * @param candidates
1124 * The sequence in which we need to compute an insertion index.
1125 * @param diff
1126 * The diff we are computing an insertion index for.
1127 * @param <E>
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();
1140 } else {
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);
1152 } else {
1153 if (Iterables.any(filteredCandidates, new UnresolvedDiffMatching(feature, candidate))) {
1154 ignored.add(candidate);
1158 return ignored;
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}.
1178 * @param feature
1179 * The feature which our diffs must concern.
1180 * @param element
1181 * The element which must be our diffs' target.
1183 public UnresolvedDiffMatching(EStructuralFeature feature, Object element) {
1184 this.feature = feature;
1185 this.element = element;
1189 * {@inheritDoc}
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);
1203 } else {
1204 apply = false;
1206 return apply;
1210 * Checks that the value of the given diff matches <code>value</code>, resorting to the equality
1211 * helper if needed.
1213 * @param diff
1214 * The diff which value we need to check.
1215 * @param value
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) {
1221 return true;
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
1230 * helper if needed.
1232 * @param diff
1233 * The diff which value we need to check.
1234 * @param value
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) {
1240 return true;
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);