Fix some of the warnings
[EMFCompare2.git] / plugins / org.eclipse.emf.compare / src / org / eclipse / emf / compare / conflict / DefaultConflictDetector.java
blobae3d25412ae020d5a2c7b2d28e32487787c6d639
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 * Philip Langer - [446947] Adds support for mergeable String attributes
11 *******************************************************************************/
12 package org.eclipse.emf.compare.conflict;
14 import static com.google.common.base.Predicates.and;
15 import static com.google.common.collect.Iterables.filter;
16 import static com.google.common.collect.Iterables.isEmpty;
17 import static org.eclipse.emf.compare.internal.utils.ComparisonUtil.isDeleteOrUnsetDiff;
18 import static org.eclipse.emf.compare.internal.utils.ComparisonUtil.isFeatureMapContainment;
19 import static org.eclipse.emf.compare.utils.EMFComparePredicates.CONTAINMENT_REFERENCE_CHANGE;
20 import static org.eclipse.emf.compare.utils.EMFComparePredicates.hasConflict;
21 import static org.eclipse.emf.compare.utils.EMFComparePredicates.ofKind;
22 import static org.eclipse.emf.compare.utils.EMFComparePredicates.onFeature;
23 import static org.eclipse.emf.compare.utils.EMFComparePredicates.valueIs;
25 import com.google.common.base.Predicate;
26 import com.google.common.base.Predicates;
27 import com.google.common.collect.Iterables;
28 import com.google.common.collect.Lists;
30 import java.util.Iterator;
31 import java.util.List;
32 import java.util.NoSuchElementException;
34 import org.eclipse.emf.common.util.Monitor;
35 import org.eclipse.emf.common.util.TreeIterator;
36 import org.eclipse.emf.compare.AttributeChange;
37 import org.eclipse.emf.compare.CompareFactory;
38 import org.eclipse.emf.compare.Comparison;
39 import org.eclipse.emf.compare.Conflict;
40 import org.eclipse.emf.compare.ConflictKind;
41 import org.eclipse.emf.compare.Diff;
42 import org.eclipse.emf.compare.DifferenceKind;
43 import org.eclipse.emf.compare.DifferenceSource;
44 import org.eclipse.emf.compare.EMFCompareMessages;
45 import org.eclipse.emf.compare.Equivalence;
46 import org.eclipse.emf.compare.FeatureMapChange;
47 import org.eclipse.emf.compare.Match;
48 import org.eclipse.emf.compare.MatchResource;
49 import org.eclipse.emf.compare.ReferenceChange;
50 import org.eclipse.emf.compare.ResourceAttachmentChange;
51 import org.eclipse.emf.compare.internal.SubMatchIterator;
52 import org.eclipse.emf.compare.internal.ThreeWayTextDiff;
53 import org.eclipse.emf.compare.utils.IEqualityHelper;
54 import org.eclipse.emf.compare.utils.ReferenceUtil;
55 import org.eclipse.emf.ecore.EAttribute;
56 import org.eclipse.emf.ecore.EObject;
57 import org.eclipse.emf.ecore.EStructuralFeature;
58 import org.eclipse.emf.ecore.resource.Resource;
59 import org.eclipse.emf.ecore.util.EcoreUtil;
60 import org.eclipse.emf.ecore.util.FeatureMap;
62 /**
63 * The conflict detector is in charge of refining the Comparison model with all detected Conflict between its
64 * differences.
65 * <p>
66 * This default implementation of {@link IConflictDetector} should detect most generic cases, but is not aimed
67 * at detecting conflicts at "business" level. For example, adding two enum literals of the same value but
68 * distinct IDs might be seen as a conflict... but that is not the "generic" case.
69 * </p>
71 * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
73 public class DefaultConflictDetector implements IConflictDetector {
75 /**
76 * This can be used to check whether a given conflict involves add containment reference changes.
78 private static final Predicate<? super Conflict> IS_REAL_CONTAINMENT_ADD_CONFLICT = new Predicate<Conflict>() {
79 public boolean apply(Conflict input) {
80 boolean isRealAddContainmentConflict = false;
81 if (input != null && input.getKind() == ConflictKind.REAL) {
82 Iterable<Diff> containmentRefs = filter(input.getDifferences(), CONTAINMENT_REFERENCE_CHANGE);
83 if (!isEmpty(containmentRefs)) {
84 for (Diff diff : containmentRefs) {
85 if (diff.getKind() != DifferenceKind.ADD) {
86 return false;
89 isRealAddContainmentConflict = true;
92 return isRealAddContainmentConflict;
96 /**
97 * {@inheritDoc}
99 * @see org.eclipse.emf.compare.conflict.IConflictDetector#detect(org.eclipse.emf.compare.Comparison,
100 * org.eclipse.emf.common.util.Monitor)
102 public void detect(Comparison comparison, Monitor monitor) {
103 final List<Diff> differences = comparison.getDifferences();
104 final int diffCount = differences.size();
106 for (int i = 0; i < diffCount; i++) {
107 final Diff diff = differences.get(i);
109 final Predicate<? super Diff> candidateFilter = new ConflictCandidateFilter(diff);
110 checkConflict(comparison, diff, Iterables.filter(differences, candidateFilter));
113 handlePseudoUnderRealAdd(comparison);
117 * If a real add conflict contains pseudo conflicts, these pseudo conflicts must be changed to real
118 * conflicts.
120 * @param comparison
121 * The originating comparison of those diffs.
123 private void handlePseudoUnderRealAdd(Comparison comparison) {
124 for (Conflict realContainmentAdd : filter(comparison.getConflicts(), IS_REAL_CONTAINMENT_ADD_CONFLICT)) {
125 changeKindOfPseudoConflictsUnder(realContainmentAdd);
130 * Change all pseudo conflicts under the given real conflict to real conflicts.
132 * @param conflict
133 * the given conflict.
135 private void changeKindOfPseudoConflictsUnder(Conflict conflict) {
136 for (Diff diff : conflict.getDifferences()) {
137 final Match realConflictMatch = diff.getMatch();
138 for (Match subMatch : realConflictMatch.getSubmatches()) {
139 for (Diff conflictDiffUnder : filter(subMatch.getDifferences(),
140 hasConflict(ConflictKind.PSEUDO))) {
141 Conflict conflictUnder = conflictDiffUnder.getConflict();
142 conflictUnder.setKind(ConflictKind.REAL);
143 changeKindOfPseudoConflictsUnder(conflictUnder);
150 * This will be called once for each difference in the comparison model.
152 * @param comparison
153 * The originating comparison of those diffs.
154 * @param diff
155 * Diff for which we are to try and determine conflicts.
156 * @param candidates
157 * An iterable over the Diffs that possible candidates for conflicts.
159 protected void checkConflict(Comparison comparison, Diff diff, Iterable<Diff> candidates) {
161 * DELETE diffs can conflict with every other if on containment references, only with MOVE or other
162 * DELETE otherwise.
165 * ADD diffs can only conflict with "DELETE" or "ADD" ones ... Most will be detected on the DELETE.
166 * However, ADD diffs on containment reference can conflict with other ADDs on the same match.
168 // CHANGE diffs can only conflict with other CHANGE or DELETE ... here again detected on the DELETE
169 // MOVE diffs can conflict with DELETE ones, detected on the delete, or with other MOVE diffs.
170 if (diff instanceof ReferenceChange && ((ReferenceChange)diff).getReference().isContainment()) {
171 checkContainmentConflict(comparison, (ReferenceChange)diff, Iterables.filter(candidates,
172 ReferenceChange.class));
173 } else if (diff instanceof ResourceAttachmentChange) {
174 // These will be handled about the same way as containment deletions,
175 // Though they can also conflict with themselves
176 checkResourceAttachmentConflict(comparison, (ResourceAttachmentChange)diff, candidates);
177 } else if (isFeatureMapContainment(diff)) {
178 checkContainmentFeatureMapConflict(comparison, (FeatureMapChange)diff, Iterables.filter(
179 candidates, FeatureMapChange.class));
180 } else {
181 switch (diff.getKind()) {
182 case DELETE:
183 checkFeatureDeleteConflict(comparison, diff, candidates);
184 break;
185 case CHANGE:
186 checkFeatureChangeConflict(comparison, diff, candidates);
187 break;
188 case MOVE:
189 checkFeatureMoveConflict(comparison, diff, candidates);
190 break;
191 case ADD:
192 checkFeatureAddConflict(comparison, diff, candidates);
193 break;
194 default:
195 break;
201 * This will be called once for each ReferenceChange on containment references in the comparison model.
203 * @param comparison
204 * The originating comparison of those diffs.
205 * @param diff
206 * The reference change for which we are to try and determine conflicts.
207 * @param candidates
208 * An iterable over the ReferenceChanges that are possible candidates for conflicts.
210 protected void checkContainmentConflict(Comparison comparison, ReferenceChange diff,
211 Iterable<ReferenceChange> candidates) {
212 final Match valueMatch = comparison.getMatch(diff.getValue());
214 for (ReferenceChange candidate : candidates) {
215 EObject candidateValue = candidate.getValue();
216 if (valueMatch.getLeft() == candidateValue || valueMatch.getRight() == candidateValue
217 || valueMatch.getOrigin() == candidateValue) {
218 checkContainmentConflict(comparison, diff, candidate);
222 // [381143] Every Diff "under" a containment deletion conflicts with it.
223 if (diff.getKind() == DifferenceKind.DELETE) {
224 final Predicate<? super Diff> candidateFilter = new ConflictCandidateFilter(diff);
225 final DiffTreeIterator diffIterator = new DiffTreeIterator(valueMatch);
226 diffIterator.setFilter(candidateFilter);
227 diffIterator.setPruningFilter(isContainmentDelete());
229 while (diffIterator.hasNext()) {
230 Diff extendedCandidate = diffIterator.next();
231 if (isDeleteOrUnsetDiff(extendedCandidate)) {
232 conflictOn(comparison, diff, extendedCandidate, ConflictKind.PSEUDO);
233 } else {
234 conflictOn(comparison, diff, extendedCandidate, ConflictKind.REAL);
241 * This predicate will be <code>true</code> for any Match which represents a containment deletion.
243 * @return A Predicate that will be met by containment deletions.
245 private Predicate<? super Match> isContainmentDelete() {
246 return new Predicate<Match>() {
247 public boolean apply(Match input) {
248 return input.getOrigin() != null && (input.getLeft() == null || input.getRight() == null);
254 * For each couple of diffs on the same value in which one is a containment reference change, we will call
255 * this in order to check for possible conflicts.
256 * <p>
257 * Once here, we know that {@code diff} is a containment reference change, and we known that {@code diff}
258 * and {@code candidate} are both pointing to the same value. {@code candidate} can be a containment
259 * reference change, but that is not a given.
260 * </p>
262 * @param comparison
263 * The originating comparison of those diffs.
264 * @param diff
265 * Containment reference changes for which we need to check possible conflicts.
266 * @param candidate
267 * A reference change that point to the same value as {@code diff}.
269 protected void checkContainmentConflict(Comparison comparison, ReferenceChange diff,
270 ReferenceChange candidate) {
271 final boolean candidateIsDelete = isDeleteOrUnsetDiff(candidate);
272 if (candidate.getReference().isContainment()) {
273 // The same value has been changed on both sides in containment references
274 // This is a conflict, but is it a pseudo-conflict?
275 ConflictKind kind = ConflictKind.REAL;
276 final boolean diffIsDelete = isDeleteOrUnsetDiff(diff);
277 if (diffIsDelete && candidateIsDelete) {
278 kind = ConflictKind.PSEUDO;
279 } else if (diff.getMatch() == candidate.getMatch()
280 && diff.getReference() == candidate.getReference()) {
281 // Same value added in the same container/reference couple
282 if (!diffIsDelete
283 && !candidateIsDelete
284 && matchingIndices(comparison, diff.getMatch(), diff.getReference(), diff.getValue(),
285 candidate.getValue())) {
286 kind = ConflictKind.PSEUDO;
289 conflictOn(comparison, diff, candidate, kind);
290 } else if (diff.getKind() == DifferenceKind.DELETE) {
292 * We removed an element from its containment difference, but it has been used in some way on the
293 * other side.
295 if (candidateIsDelete) {
296 // No conflict here
297 } else {
298 // Be it added, moved or changed, this is a REAL conflict
299 conflictOn(comparison, diff, candidate, ConflictKind.REAL);
305 * This will be called once for each FeatureMapChange on containment values in the comparison model.
307 * @param comparison
308 * The originating comparison of those diffs.
309 * @param diff
310 * The feature map change for which we are to try and determine conflicts.
311 * @param candidates
312 * An iterable over the FeatureMapChanges that are possible candidates for conflicts.
313 * @since 3.2
315 protected void checkContainmentFeatureMapConflict(Comparison comparison, FeatureMapChange diff,
316 Iterable<FeatureMapChange> candidates) {
317 final FeatureMap.Entry entry = (FeatureMap.Entry)diff.getValue();
318 final Object value = entry.getValue();
319 final Match valueMatch;
320 if (value instanceof EObject) {
321 valueMatch = comparison.getMatch((EObject)value);
322 } else {
323 valueMatch = diff.getMatch();
326 for (FeatureMapChange candidate : candidates) {
327 FeatureMap.Entry candidateEntry = (FeatureMap.Entry)candidate.getValue();
328 Object candidateValue = candidateEntry.getValue();
329 if (valueMatch.getLeft() == candidateValue || valueMatch.getRight() == candidateValue
330 || valueMatch.getOrigin() == candidateValue) {
331 checkContainmentFeatureMapConflict(comparison, diff, candidate);
335 // [381143] Every Diff "under" a containment deletion conflicts with it.
336 if (diff.getKind() == DifferenceKind.DELETE) {
337 final Predicate<? super Diff> candidateFilter = new ConflictCandidateFilter(diff);
338 final DiffTreeIterator diffIterator = new DiffTreeIterator(valueMatch);
339 diffIterator.setFilter(candidateFilter);
340 diffIterator.setPruningFilter(isContainmentDelete());
342 while (diffIterator.hasNext()) {
343 Diff extendedCandidate = diffIterator.next();
344 if (isDeleteOrUnsetDiff(extendedCandidate)) {
345 conflictOn(comparison, diff, extendedCandidate, ConflictKind.PSEUDO);
346 } else {
347 conflictOn(comparison, diff, extendedCandidate, ConflictKind.REAL);
354 * For each couple of diffs on the same value in which one is a containment feature map change, we will
355 * call this in order to check for possible conflicts.
356 * <p>
357 * Once here, we know that {@code diff} is a containment feature map change, and we known that
358 * {@code diff} and {@code candidate} are both pointing to the same value. {@code candidate} can be a
359 * containment feature map change, but that is not a given.
360 * </p>
362 * @param comparison
363 * The originating comparison of those diffs.
364 * @param diff
365 * Containment feature map changes for which we need to check possible conflicts.
366 * @param candidate
367 * A feature map change that point to the same value as {@code diff}.
368 * @since 3.2
370 protected void checkContainmentFeatureMapConflict(Comparison comparison, FeatureMapChange diff,
371 FeatureMapChange candidate) {
372 final boolean candidateIsDelete = isDeleteOrUnsetDiff(candidate);
373 if (isFeatureMapContainment(candidate)) {
374 // The same value has been changed on both sides in containment references
375 // This is a conflict, but is it a pseudo-conflict?
376 ConflictKind kind = ConflictKind.REAL;
377 final boolean diffIsDelete = isDeleteOrUnsetDiff(diff);
378 if (diffIsDelete && candidateIsDelete) {
379 kind = ConflictKind.PSEUDO;
380 } else if (diff.getMatch() == candidate.getMatch()
381 && diff.getAttribute() == candidate.getAttribute()) {
382 // Same value added in the same container/reference couple with the same key
383 if (!diffIsDelete
384 && !candidateIsDelete
385 && matchingIndices(comparison, diff.getMatch(), diff.getAttribute(), diff.getValue(),
386 candidate.getValue()) && haveSameKey(diff, candidate)) {
387 kind = ConflictKind.PSEUDO;
390 conflictOn(comparison, diff, candidate, kind);
391 } else if (diff.getKind() == DifferenceKind.DELETE) {
393 * We removed an element from its containment difference, but it has been used in some way on the
394 * other side.
396 if (candidateIsDelete) {
397 // No conflict here
398 } else {
399 // Be it added, moved or changed, this is a REAL conflict
400 conflictOn(comparison, diff, candidate, ConflictKind.REAL);
406 * Check if both feature map changes (hosting FeatureMap entries) have entries with same key.
408 * @param left
409 * the left candidate.
410 * @param right
411 * the right candiadte.
412 * @return true if both feature map changes have entries with same key, false otherwise.
414 private boolean haveSameKey(FeatureMapChange left, FeatureMapChange right) {
415 FeatureMap.Entry leftEntry = (FeatureMap.Entry)left.getValue();
416 FeatureMap.Entry rightEntry = (FeatureMap.Entry)right.getValue();
417 return leftEntry.getEStructuralFeature().equals(rightEntry.getEStructuralFeature());
421 * This will be called from {@link #checkConflict(Comparison, Diff, Iterable)} in order to detect
422 * conflicts on a Diff that is of type "CHANGE".
423 * <p>
424 * Those can only conflict with other CHANGE Diffs on the same reference.
425 * </p>
427 * @param comparison
428 * The originating comparison of those diffs.
429 * @param diff
430 * The diff which we are to check for conflicts.
431 * @param candidates
432 * The list of candidates for a conflict. This list only contains Diff from the side opposite
433 * to {@code diff}.
435 protected void checkFeatureChangeConflict(Comparison comparison, Diff diff, Iterable<Diff> candidates) {
436 final Object changedValue;
437 final EStructuralFeature feature;
438 if (diff instanceof ReferenceChange) {
439 changedValue = ((ReferenceChange)diff).getValue();
440 feature = ((ReferenceChange)diff).getReference();
441 } else if (diff instanceof AttributeChange) {
442 changedValue = ((AttributeChange)diff).getValue();
443 feature = ((AttributeChange)diff).getAttribute();
444 } else if (diff instanceof FeatureMapChange) {
445 changedValue = ((FeatureMap.Entry)((FeatureMapChange)diff).getValue()).getValue();
446 feature = ((FeatureMapChange)diff).getAttribute();
447 } else {
448 return;
451 final Iterable<Diff> refinedCandidates = Iterables.filter(candidates, new Predicate<Diff>() {
452 public boolean apply(Diff input) {
453 boolean apply = false;
454 if (input != null && input.getKind() == DifferenceKind.CHANGE) {
455 if (input instanceof ReferenceChange) {
456 apply = ((ReferenceChange)input).getReference() == feature;
457 } else if (input instanceof AttributeChange) {
458 apply = ((AttributeChange)input).getAttribute() == feature;
459 } else if (input instanceof FeatureMapChange) {
460 apply = ((FeatureMapChange)input).getAttribute() == feature;
463 return apply;
467 final IEqualityHelper equalityHelper = comparison.getEqualityHelper();
469 for (Diff candidate : refinedCandidates) {
470 final Object candidateValue;
471 if (candidate instanceof ReferenceChange) {
472 candidateValue = ((ReferenceChange)candidate).getValue();
473 } else if (candidate instanceof AttributeChange) {
474 candidateValue = ((AttributeChange)candidate).getValue();
475 } else if (candidate instanceof FeatureMapChange) {
476 candidateValue = ((FeatureMap.Entry)((FeatureMapChange)candidate).getValue()).getValue();
477 } else {
478 candidateValue = null;
481 if (diff.getMatch() == candidate.getMatch()) {
482 if (equalityHelper.matchingValues(changedValue, candidateValue)) {
483 // Same value added on both side in the same container
484 conflictOn(comparison, diff, candidate, ConflictKind.PSEUDO);
485 } else if (!isFeatureMapChangeOrMergeableStringAttributeChange(diff, candidate)) {
486 conflictOn(comparison, diff, candidate, ConflictKind.REAL);
493 * Specifies whether the given {@code diff1} and {@code diff2} are either {@link FeatureMapChange feature
494 * map changes} or mergeable {@link AttributeChange attribute changes} of String attributes.
496 * @param diff1
497 * One of the diffs to check.
498 * @param diff2
499 * The other diff to check.
500 * @return <code>true</code> if it is a {@link FeatureMapChange} or a mergeable {@link AttributeChange},
501 * <code>false</code> otherwise.
503 private boolean isFeatureMapChangeOrMergeableStringAttributeChange(Diff diff1, Diff diff2) {
504 return isFeatureMapChange(diff1) || areMergeableStringAttributeChanges(diff1, diff2);
508 * Specifies whether the given {@code diff} is a {@link FeatureMapChange}.
510 * @param diff
511 * The diff to check.
512 * @return <code>true</code> if it is a {@link FeatureMapChange}, <code>false</code> otherwise.
514 private boolean isFeatureMapChange(Diff diff) {
515 return diff instanceof FeatureMapChange;
519 * Specifies whether the two given diffs, {@code diff1} and {@code diff2}, are both
520 * {@link AttributeChange attribute changes} of String attributes and can be merged with a line-based
521 * three-way merge.
523 * @see org.eclipse.emf.compare.internal.ThreeWayTextDiff
524 * @param diff1
525 * One of the diffs to check.
526 * @param diff2
527 * The other diff to check.
528 * @return <code>true</code> if the diffs are mergeable changes of a string attribute, <code>false</code>
529 * otherwise.
531 private boolean areMergeableStringAttributeChanges(Diff diff1, Diff diff2) {
532 final boolean mergeableStringAttributeChange;
533 if (isStringAttributeChange(diff1)) {
534 final AttributeChange attributeChange1 = (AttributeChange)diff1;
535 final AttributeChange attributeChange2 = (AttributeChange)diff2;
536 mergeableStringAttributeChange = isMergeable(attributeChange1, attributeChange2);
537 } else {
538 mergeableStringAttributeChange = false;
540 return mergeableStringAttributeChange;
544 * Specifies whether the given {@code diff} is a {@link AttributeChange} of a String attribute.
546 * @param diff
547 * The diff to check.
548 * @return <code>true</code> if it is a {@link AttributeChange} of a String attribute, <code>false</code>
549 * otherwise.
551 private boolean isStringAttributeChange(Diff diff) {
552 return diff instanceof AttributeChange
553 && ((AttributeChange)diff).getAttribute().getEAttributeType().getInstanceClass() == String.class;
557 * Specifies whether the two given attribute changes, {@code diff1} and {@code diff2}, can be merged with
558 * a line-based three-way merge.
560 * @see org.eclipse.emf.compare.internal.ThreeWayTextDiff
561 * @param diff1
562 * One of the attribute changes to check.
563 * @param diff2
564 * The other attribute change to check.
565 * @return <code>true</code> if the attribute changes are mergeable, <code>false</code> otherwise.
567 private boolean isMergeable(final AttributeChange diff1, final AttributeChange diff2) {
568 final String changedValue1 = getChangedValue(diff1);
569 final String changedValue2 = getChangedValue(diff2);
570 final EObject originalContainer = diff1.getMatch().getOrigin();
571 final EAttribute changedAttribute = diff1.getAttribute();
572 final String originalValue = (String)originalContainer.eGet(changedAttribute);
573 return isMergeableText(changedValue1, changedValue2, originalValue);
577 * Specifies whether the given three versions of a text {@code left}, {@code right}, and {@code origin}
578 * are mergeable with a line-based three-way merge.
580 * @param left
581 * The left version.
582 * @param right
583 * The right version.
584 * @param origin
585 * The original version.
586 * @return <code>true</code> if they are mergeable, false otherwise.
587 * @since 3.2
589 protected boolean isMergeableText(final String left, final String right, final String origin) {
590 ThreeWayTextDiff textDiff = new ThreeWayTextDiff(origin, left, right);
591 return !textDiff.isConflicting();
595 * Returns the changed attribute value denoted by the given {@code diff}.
597 * @param diff
598 * The attribute change for which the changed value is requested.
599 * @return The changed attribute value.
601 private String getChangedValue(final AttributeChange diff) {
602 final String changedValue;
603 Match match = diff.getMatch();
604 if (DifferenceSource.LEFT.equals(diff.getSource())) {
605 changedValue = (String)match.getLeft().eGet(diff.getAttribute());
606 } else if (DifferenceSource.RIGHT.equals(diff.getSource())) {
607 changedValue = (String)match.getRight().eGet(diff.getAttribute());
608 } else {
609 changedValue = (String)diff.getValue();
611 return changedValue;
615 * This will be called from {@link #checkConflict(Comparison, Diff, Iterable)} in order to detect
616 * conflicts on a Diff that is of type "CHANGE" or "MOVE".
617 * <p>
618 * Those can only conflict with other Diffs of the same type on the same reference.
619 * </p>
621 * @param comparison
622 * The originating comparison of those diffs.
623 * @param diff
624 * The diff which we are to check for conflicts.
625 * @param candidates
626 * The list of candidates for a conflict. This list only contains Diff from the side opposite
627 * to {@code diff}.
629 protected void checkFeatureMoveConflict(Comparison comparison, Diff diff, Iterable<Diff> candidates) {
630 final Object changedValue;
631 final EStructuralFeature feature;
632 if (diff instanceof ReferenceChange) {
633 changedValue = ((ReferenceChange)diff).getValue();
634 feature = ((ReferenceChange)diff).getReference();
635 } else if (diff instanceof AttributeChange) {
636 changedValue = ((AttributeChange)diff).getValue();
637 feature = ((AttributeChange)diff).getAttribute();
638 } else if (diff instanceof FeatureMapChange) {
639 changedValue = ((FeatureMap.Entry)((FeatureMapChange)diff).getValue()).getValue();
640 feature = ((FeatureMapChange)diff).getAttribute();
641 } else {
642 return;
645 final Iterable<Diff> refinedCandidates = Iterables.filter(candidates, new Predicate<Diff>() {
646 public boolean apply(Diff input) {
647 boolean apply = false;
648 if (input != null && input.getKind() == DifferenceKind.MOVE) {
649 if (input instanceof ReferenceChange) {
650 apply = ((ReferenceChange)input).getReference() == feature;
651 } else if (input instanceof AttributeChange) {
652 apply = ((AttributeChange)input).getAttribute() == feature;
653 } else if (input instanceof FeatureMapChange) {
654 apply = ((FeatureMapChange)input).getAttribute() == feature;
657 return apply;
661 for (Diff candidate : refinedCandidates) {
662 final Object candidateValue;
663 if (candidate instanceof ReferenceChange) {
664 candidateValue = ((ReferenceChange)candidate).getValue();
665 } else if (candidate instanceof AttributeChange) {
666 candidateValue = ((AttributeChange)candidate).getValue();
667 } else if (candidate instanceof FeatureMapChange) {
668 candidateValue = ((FeatureMap.Entry)((FeatureMapChange)candidate).getValue()).getValue();
669 } else {
670 candidateValue = null;
673 if (diff.getMatch() == candidate.getMatch()
674 && comparison.getEqualityHelper().matchingValues(changedValue, candidateValue)) {
675 // Same value moved in both side of the same container
676 if (matchingIndices(comparison, diff.getMatch(), feature, changedValue, candidateValue)) {
677 conflictOn(comparison, diff, candidate, ConflictKind.PSEUDO);
678 } else {
679 conflictOn(comparison, diff, candidate, ConflictKind.REAL);
686 * This will be called from {@link #checkConflict(Comparison, Diff, Iterable)} in order to detect
687 * conflicts on a Diff that is of type "DELETE" and which is <b>not</b> a containment reference change.
688 * <p>
689 * The only potential conflict for such a diff is a "MOVE" of that same value on the opposite side.
690 * </p>
692 * @param comparison
693 * The originating comparison of those diffs.
694 * @param diff
695 * The diff which we are to check for conflicts.
696 * @param candidates
697 * The list of candidates for a conflict. This list only contains Diff from the side opposite
698 * to {@code diff}.
700 protected void checkFeatureDeleteConflict(Comparison comparison, Diff diff, Iterable<Diff> candidates) {
701 final Object deletedValue;
702 final EStructuralFeature feature;
703 if (diff instanceof ReferenceChange) {
704 deletedValue = ((ReferenceChange)diff).getValue();
705 feature = ((ReferenceChange)diff).getReference();
706 } else if (diff instanceof AttributeChange) {
707 deletedValue = ((AttributeChange)diff).getValue();
708 feature = ((AttributeChange)diff).getAttribute();
709 } else if (diff instanceof FeatureMapChange) {
710 deletedValue = ((FeatureMap.Entry)((FeatureMapChange)diff).getValue()).getValue();
711 feature = ((FeatureMapChange)diff).getAttribute();
712 } else {
713 return;
717 * The only potential conflict with the deletion of a feature value is a move or delete concerning
718 * that value on the opposite side (the "feature" cannot be a containment reference, those are handled
719 * through #checkContainmentDeleteConflict).
721 final Iterable<Diff> refinedCandidates = Iterables.filter(candidates, new Predicate<Diff>() {
722 public boolean apply(Diff input) {
723 boolean apply = false;
724 if (input != null
725 && (input.getKind() == DifferenceKind.MOVE || input.getKind() == DifferenceKind.DELETE)) {
726 if (input instanceof ReferenceChange) {
727 apply = ((ReferenceChange)input).getReference() == feature;
728 } else if (input instanceof AttributeChange) {
729 apply = ((AttributeChange)input).getAttribute() == feature;
730 } else if (input instanceof FeatureMapChange) {
731 apply = ((FeatureMapChange)input).getAttribute() == feature;
734 return apply;
738 for (Diff candidate : refinedCandidates) {
739 final Object movedValue;
740 if (candidate instanceof ReferenceChange) {
741 movedValue = ((ReferenceChange)candidate).getValue();
742 } else if (candidate instanceof AttributeChange) {
743 movedValue = ((AttributeChange)candidate).getValue();
744 } else if (candidate instanceof FeatureMapChange) {
745 movedValue = ((FeatureMap.Entry)((FeatureMapChange)candidate).getValue()).getValue();
746 } else {
747 movedValue = null;
750 if (comparison.getEqualityHelper().matchingValues(deletedValue, movedValue)) {
751 if (candidate.getKind() == DifferenceKind.MOVE) {
752 conflictOn(comparison, diff, candidate, ConflictKind.REAL);
753 } else {
754 conflictOn(comparison, diff, candidate, ConflictKind.PSEUDO);
761 * This will be called from {@link #checkConflict(Comparison, Diff, Iterable)} in order to detect
762 * conflicts on a Diff that is of type "ADD" and which is <b>not</b> a containment reference change.
763 * <p>
764 * These will conflict with Diffs on the other side on the same reference in the same container, of type
765 * ADD an on the same value.
766 * </p>
768 * @param comparison
769 * The originating comparison of those diffs.
770 * @param diff
771 * The diff which we are to check for conflicts.
772 * @param candidates
773 * The list of candidates for a conflict. This list only contains Diff from the side opposite
774 * to {@code diff}.
776 protected void checkFeatureAddConflict(final Comparison comparison, final Diff diff,
777 Iterable<Diff> candidates) {
778 final Object addedValue;
779 final EStructuralFeature feature;
780 if (diff instanceof ReferenceChange) {
781 addedValue = ((ReferenceChange)diff).getValue();
782 feature = ((ReferenceChange)diff).getReference();
783 } else if (diff instanceof AttributeChange) {
784 addedValue = ((AttributeChange)diff).getValue();
785 feature = ((AttributeChange)diff).getAttribute();
786 } else if (diff instanceof FeatureMapChange) {
787 addedValue = ((FeatureMap.Entry)((FeatureMapChange)diff).getValue()).getValue();
788 feature = ((FeatureMapChange)diff).getAttribute();
789 } else {
790 return;
794 * Can only conflict on Diffs : of type ADD, on the opposite side, in the same container and the same
795 * reference, with the same added value.
797 final Iterable<Diff> refinedCandidates = Iterables.filter(candidates, new Predicate<Diff>() {
798 public boolean apply(Diff input) {
799 boolean apply = false;
800 if (input != null
801 && (input.getKind() == DifferenceKind.ADD && diff.getMatch() == input.getMatch())) {
802 if (input instanceof ReferenceChange) {
803 apply = ((ReferenceChange)input).getReference() == feature;
804 } else if (input instanceof AttributeChange) {
805 apply = ((AttributeChange)input).getAttribute() == feature;
806 } else if (input instanceof FeatureMapChange) {
807 apply = ((FeatureMapChange)input).getAttribute() == feature;
810 return apply;
814 for (Diff candidate : refinedCandidates) {
815 final Object candidateValue;
816 if (candidate instanceof ReferenceChange) {
817 candidateValue = ((ReferenceChange)candidate).getValue();
818 } else if (candidate instanceof AttributeChange) {
819 candidateValue = ((AttributeChange)candidate).getValue();
820 } else if (candidate instanceof FeatureMapChange) {
821 candidateValue = ((FeatureMap.Entry)((FeatureMapChange)candidate).getValue()).getValue();
822 } else {
823 candidateValue = null;
825 // No diff on non unique features : multiple same values can coexist
826 if (feature.isUnique()
827 && comparison.getEqualityHelper().matchingValues(addedValue, candidateValue)) {
828 // This is a conflict. Is it real?
829 if (diff instanceof FeatureMapChange) {
831 // If the key changed, this is a real conflict
832 EStructuralFeature key1 = ((FeatureMap.Entry)((FeatureMapChange)diff).getValue())
833 .getEStructuralFeature();
834 EStructuralFeature key2 = ((FeatureMap.Entry)((FeatureMapChange)candidate).getValue())
835 .getEStructuralFeature();
836 if (key1.equals(key2)) {
837 conflictOn(comparison, diff, candidate, ConflictKind.PSEUDO);
838 } else if (isFeatureMapContainment(diff)) { // If the feature map is non-containment, the
839 // same value can appear twice.
840 conflictOn(comparison, diff, candidate, ConflictKind.REAL);
842 } else if (matchingIndices(comparison, diff.getMatch(), feature, addedValue, candidateValue)) {
843 conflictOn(comparison, diff, candidate, ConflictKind.PSEUDO);
844 } else {
845 conflictOn(comparison, diff, candidate, ConflictKind.REAL);
852 * This will be called once for each ResourceAttachmentChange in the comparison model.
854 * @param comparison
855 * The originating comparison of those diffs.
856 * @param diff
857 * The "root" difference for which we are to try and determine conflicts.
858 * @param candidates
859 * An iterable over the Diffs that are possible candidates for conflicts.
861 protected void checkResourceAttachmentConflict(Comparison comparison, ResourceAttachmentChange diff,
862 Iterable<Diff> candidates) {
863 final Match match = diff.getMatch();
864 final EObject leftVal = match.getLeft();
865 final EObject rightVal = match.getRight();
866 final EObject originVal = match.getOrigin();
867 for (Diff candidate : candidates) {
868 if (candidate instanceof ReferenceChange) {
869 // Any ReferenceChange that references the affected root is a possible conflict
870 final EObject candidateValue = ((ReferenceChange)candidate).getValue();
871 if (candidateValue == leftVal || candidateValue == rightVal || candidateValue == originVal) {
872 checkResourceAttachmentConflict(comparison, diff, (ReferenceChange)candidate);
874 } else if (candidate instanceof ResourceAttachmentChange && match == candidate.getMatch()) {
875 // This can only be a conflict. All we need to know is its kind.
876 ConflictKind kind = ConflictKind.REAL;
877 if (candidate.getKind() == DifferenceKind.DELETE && diff.getKind() == DifferenceKind.DELETE) {
878 kind = ConflictKind.PSEUDO;
879 } else if (candidate.getKind() == DifferenceKind.ADD && diff.getKind() == DifferenceKind.ADD) {
880 final Resource diffRes;
881 final Resource candidateRes;
882 if (diff.getSource() == DifferenceSource.LEFT) {
883 diffRes = match.getLeft().eResource();
884 candidateRes = match.getRight().eResource();
885 } else {
886 diffRes = match.getRight().eResource();
887 candidateRes = match.getLeft().eResource();
889 if (getMatchResource(comparison, diffRes) == getMatchResource(comparison, candidateRes)) {
890 kind = ConflictKind.PSEUDO;
893 conflictOn(comparison, diff, candidate, kind);
897 // [381143] Every Diff "under" a root deletion conflicts with it.
898 if (diff.getKind() == DifferenceKind.DELETE) {
899 final Predicate<? super Diff> candidateFilter = new ConflictCandidateFilter(diff);
900 for (Diff extendedCandidate : Iterables.filter(match.getAllDifferences(), candidateFilter)) {
901 if (isDeleteOrUnsetDiff(extendedCandidate)) {
902 conflictOn(comparison, diff, extendedCandidate, ConflictKind.PSEUDO);
903 } else {
904 conflictOn(comparison, diff, extendedCandidate, ConflictKind.REAL);
911 * Returns the MatchResource corresponding to the given <code>resource</code>.
913 * @param comparison
914 * the comparison to search for a MatchResource.
915 * @param resource
916 * Resource for which we need a MatchResource.
917 * @return The MatchResource corresponding to the given <code>resource</code>.
919 protected MatchResource getMatchResource(Comparison comparison, Resource resource) {
920 final List<MatchResource> matchedResources = comparison.getMatchedResources();
921 final int size = matchedResources.size();
922 MatchResource soughtMatch = null;
923 for (int i = 0; i < size && soughtMatch == null; i++) {
924 final MatchResource matchRes = matchedResources.get(i);
925 if (matchRes.getRight() == resource || matchRes.getLeft() == resource
926 || matchRes.getOrigin() == resource) {
927 soughtMatch = matchRes;
931 if (soughtMatch == null) {
932 // This should never happen
933 throw new RuntimeException(EMFCompareMessages.getString(
934 "ResourceAttachmentChangeSpec.MissingMatch", resource.getURI().lastSegment())); //$NON-NLS-1$
937 return soughtMatch;
941 * This will be called from
942 * {@link #checkResourceAttachmentConflict(Comparison, ResourceAttachmentChange, Iterable)} for each
943 * ReferenceChange in the comparison model that is on the other side and that impacts the changed root.
945 * @param comparison
946 * The originating comparison of those diffs.
947 * @param diff
948 * Resource attachment change for which we need to check possible conflicts.
949 * @param candidate
950 * A reference change that point to the same value as {@code diff}.
952 protected void checkResourceAttachmentConflict(Comparison comparison, ResourceAttachmentChange diff,
953 ReferenceChange candidate) {
954 if (candidate.getReference().isContainment()) {
955 // The element is a new root on one side, but it has been moved to an EObject container on the
956 // other
957 conflictOn(comparison, diff, candidate, ConflictKind.REAL);
958 } else if (diff.getKind() == DifferenceKind.DELETE) {
959 // The root has been deleted.
960 // Anything other than a delete of this value in a reference is a conflict.
961 if (candidate.getKind() == DifferenceKind.DELETE) {
962 // No conflict here
963 } else {
964 conflictOn(comparison, diff, candidate, ConflictKind.REAL);
970 * This will be used whenever we check for conflictual MOVEs in order to determine whether we have a
971 * pseudo conflict or a real conflict.
972 * <p>
973 * Namely, this will retrieve the value of the given {@code feature} on the right and left sides of the
974 * given {@code match}, then check whether the two given values are on the same index.
975 * </p>
976 * <p>
977 * Note that no sanity checks will be made on either the match's sides or the feature.
978 * </p>
980 * @param comparison
981 * Provides us with the necessary information to match EObjects.
982 * @param match
983 * Match for which we need to check a feature.
984 * @param feature
985 * The feature which values we need to check.
986 * @param value1
987 * First of the two values which index we are to compare.
988 * @param value2
989 * Second of the two values which index we are to compare.
990 * @return {@code true} if the two given values are located at the same index in the given feature's
991 * values list, {@code false} otherwise.
993 @SuppressWarnings("unchecked")
994 private boolean matchingIndices(Comparison comparison, Match match, EStructuralFeature feature,
995 Object value1, Object value2) {
996 boolean matching = false;
997 if (feature.isMany()) {
998 final List<Object> leftValues = (List<Object>)ReferenceUtil.safeEGet(match.getLeft(), feature);
999 final List<Object> rightValues = (List<Object>)ReferenceUtil.safeEGet(match.getRight(), feature);
1001 // FIXME the detection _will_ fail for non-unique lists with multiple identical values...
1002 int leftIndex = -1;
1003 int rightIndex = -1;
1004 for (int i = 0; i < leftValues.size(); i++) {
1005 final Object left = leftValues.get(i);
1006 if (comparison.getEqualityHelper().matchingValues(left, value1)) {
1007 break;
1008 } else if (hasDiff(match, feature, left) || hasDeleteDiff(match, feature, left)) {
1009 // Do not increment.
1010 } else {
1011 leftIndex++;
1014 for (int i = 0; i < rightValues.size(); i++) {
1015 final Object right = rightValues.get(i);
1016 if (comparison.getEqualityHelper().matchingValues(right, value2)) {
1017 break;
1018 } else if (hasDiff(match, feature, right) || hasDeleteDiff(match, feature, right)) {
1019 // Do not increment.
1020 } else {
1021 rightIndex++;
1024 matching = leftIndex == rightIndex;
1025 } else {
1026 matching = true;
1028 return matching;
1032 * Checks whether the given {@code match} presents a difference of any kind on the given {@code feature}'s
1033 * {@code value}.
1035 * @param match
1036 * The match which differences we'll check.
1037 * @param feature
1038 * The feature on which we expect a difference.
1039 * @param value
1040 * The value we expect to have changed inside {@code feature}.
1041 * @return <code>true</code> if there is such a Diff on {@code match}, <code>false</code> otherwise.
1043 private boolean hasDiff(Match match, EStructuralFeature feature, Object value) {
1044 return Iterables.any(match.getDifferences(), and(onFeature(feature.getName()), valueIs(value)));
1048 * Checks whether the given {@code value} has been deleted from the given {@code feature} of {@code match}
1051 * @param match
1052 * The match which differences we'll check.
1053 * @param feature
1054 * The feature on which we expect a difference.
1055 * @param value
1056 * The value we expect to have been removed from {@code feature}.
1057 * @return <code>true</code> if there is such a Diff on {@code match}, <code>false</code> otherwise.
1059 @SuppressWarnings("unchecked")
1060 private boolean hasDeleteDiff(Match match, EStructuralFeature feature, Object value) {
1061 final Comparison comparison = match.getComparison();
1062 final Object expectedValue;
1063 if (value instanceof EObject && comparison.isThreeWay()) {
1064 final Match valueMatch = comparison.getMatch((EObject)value);
1065 if (valueMatch != null) {
1066 expectedValue = valueMatch.getOrigin();
1067 } else {
1068 expectedValue = value;
1070 } else {
1071 expectedValue = value;
1073 return Iterables.any(match.getDifferences(), and(onFeature(feature.getName()),
1074 valueIs(expectedValue), ofKind(DifferenceKind.DELETE)));
1078 * This will be called whenever we detect a new conflict in order to create (or update) the actual
1079 * association.
1081 * @param comparison
1082 * The originating comparison of those diffs.
1083 * @param diff1
1084 * First of the two differences for which we detected a conflict.
1085 * @param diff2
1086 * Second of the two differences for which we detected a conflict.
1087 * @param kind
1088 * Kind of this conflict.
1090 protected void conflictOn(Comparison comparison, Diff diff1, Diff diff2, ConflictKind kind) {
1091 Conflict conflict = null;
1092 Conflict toBeMerged = null;
1093 if (diff1.getConflict() != null) {
1094 conflict = diff1.getConflict();
1095 if (conflict.getKind() == ConflictKind.PSEUDO && conflict.getKind() != kind) {
1096 conflict.setKind(kind);
1098 if (diff2.getConflict() != null) {
1099 // Merge the two
1100 toBeMerged = diff2.getConflict();
1102 } else if (diff2.getConflict() != null) {
1103 conflict = diff2.getConflict();
1104 if (conflict.getKind() == ConflictKind.PSEUDO && conflict.getKind() != kind) {
1105 conflict.setKind(kind);
1107 } else if (diff1.getEquivalence() != null) {
1108 Equivalence equivalence = diff1.getEquivalence();
1109 for (Diff equ : equivalence.getDifferences()) {
1110 if (equ.getConflict() != null) {
1111 conflict = equ.getConflict();
1112 if (conflict.getKind() == ConflictKind.PSEUDO && conflict.getKind() != kind) {
1113 conflict.setKind(kind);
1115 if (diff2.getConflict() != null) {
1116 // Merge the two
1117 toBeMerged = diff2.getConflict();
1119 break;
1122 } else if (diff2.getEquivalence() != null) {
1123 Equivalence equivalence = diff2.getEquivalence();
1124 for (Diff equ : equivalence.getDifferences()) {
1125 if (equ.getConflict() != null) {
1126 conflict = equ.getConflict();
1127 if (conflict.getKind() == ConflictKind.PSEUDO && conflict.getKind() != kind) {
1128 conflict.setKind(kind);
1130 break;
1135 if (conflict == null) {
1136 conflict = CompareFactory.eINSTANCE.createConflict();
1137 conflict.setKind(kind);
1138 comparison.getConflicts().add(conflict);
1141 final List<Diff> conflictDiffs = conflict.getDifferences();
1142 if (toBeMerged != null) {
1143 // These references are opposite. We can't simply iterate
1144 for (Diff aDiff : Lists.newArrayList(toBeMerged.getDifferences())) {
1145 if (!conflictDiffs.contains(aDiff)) {
1146 conflictDiffs.add(aDiff);
1149 if (toBeMerged.getKind() == ConflictKind.REAL && conflict.getKind() != ConflictKind.REAL) {
1150 conflict.setKind(ConflictKind.REAL);
1152 EcoreUtil.remove(toBeMerged);
1153 toBeMerged.getDifferences().clear();
1156 if (!conflict.getDifferences().contains(diff1)) {
1157 conflict.getDifferences().add(diff1);
1159 if (!conflict.getDifferences().contains(diff2)) {
1160 conflict.getDifferences().add(diff2);
1163 // This diff may have equivalences. These equivalences
1167 * This will be used to filter out the list of potential candidates for conflict with a given Diff.
1169 * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
1171 private static final class ConflictCandidateFilter implements Predicate<Diff> {
1172 /** The Diff for which we seek conflict candidates. */
1173 private final Diff reference;
1176 * Instantiates our filtering Predicate given the reference Diff for which to seek potential
1177 * conflicts.
1179 * @param reference
1180 * The Diff for which we seek conflict candidates.
1182 public ConflictCandidateFilter(Diff reference) {
1183 this.reference = reference;
1187 * {@inheritDoc}
1189 * @see com.google.common.base.Predicate#apply(java.lang.Object)
1191 public boolean apply(Diff input) {
1192 return canConflictWith(reference, input);
1196 * Checks if the given {@link Diff diff1} can be in conflict with the given {@link Diff diff2}.
1197 * <p>
1198 * Notably, we don't need to try and detect a conflict between two diffs if they're one and the same
1199 * or if they have already been detected as a conflicting couple. Likewise, there can be no conflict
1200 * if the two diffs originate from the same side.
1201 * </p>
1202 * <p>
1203 * bug 381143 : we'll also remove any containment deletion diff on other Matches from here.
1204 * </p>
1206 * @param diff1
1207 * First of the two differences to consider for conflict detection.
1208 * @param diff2
1209 * Second of the two differences to consider for conflict detection.
1210 * @return {@code true} if the two given diffs can conflict, {@code false} otherwise.
1212 private boolean canConflictWith(Diff diff1, Diff diff2) {
1213 if (diff1 == diff2 || diff1.getSource() == diff2.getSource()) {
1214 return false;
1216 final Conflict conflict = diff1.getConflict();
1218 boolean canConflict = false;
1219 if (conflict == null || !conflict.getDifferences().contains(diff2)) {
1220 if (diff1.getMatch() != diff2.getMatch() && diff2 instanceof ReferenceChange
1221 && ((ReferenceChange)diff2).getReference().isContainment()) {
1222 canConflict = !isDeleteOrUnsetDiff(diff2);
1223 } else {
1224 canConflict = true;
1227 return canConflict;
1232 * A custom iterator that will walk a Match->submatch tree, and allow iteration over the Diffs of these
1233 * Matches.
1234 * <p>
1235 * Since we're walking over Matches but returning Diffs, this is not a good candidate for guava's filters.
1236 * We're providing the custom {@link DiffTreeIterator#setFilter(Predicate)} and
1237 * {@link DiffTreeIterator#setPruningFilter(Predicate)} to allow for filtering or pruning the the
1238 * iteration.
1239 * </p>
1241 * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
1243 private static class DiffTreeIterator implements Iterator<Diff> {
1245 * The tree iterator that will walk over our Match tree. Some of the paths can be pruned through the
1246 * use of a {@link #pruningFilter}.
1248 private final TreeIterator<Match> subMatchIterator;
1250 /** An iterator over the differences of the current Match. */
1251 private Iterator<Diff> diffIterator;
1253 /** Current match. */
1254 private Match current;
1256 /** The Diff that will be returned by the next call to {@link #next()}. */
1257 private Diff nextDiff;
1259 /** Only Diffs that meet this criterion will be returned by this iterator. */
1260 private Predicate<? super Diff> filter = Predicates.alwaysTrue();
1263 * This particular filter can be used in order to prune a given Match and all of its differences and
1264 * sub-differences.
1266 private Predicate<? super Match> pruningFilter = Predicates.alwaysFalse();
1269 * Constructs our iterator given the root of the Match tree to iterate over.
1271 * @param start
1272 * Starting match of the tree we'll iterate over.
1274 public DiffTreeIterator(Match start) {
1275 this.current = start;
1276 this.subMatchIterator = new SubMatchIterator(start);
1277 this.diffIterator = start.getDifferences().iterator();
1281 * Sets the criterion that Diffs must meet to be returned by this iterator.
1283 * @param filter
1284 * The filter differences must meet.
1286 public void setFilter(Predicate<? super Diff> filter) {
1287 this.filter = filter;
1291 * Sets the pruning filter for this iterator. Any Match that meets this criterion will be pruned along
1292 * with all of its differences and sub-differences.
1294 * @param pruningFilter
1295 * The pruning filter for this iterator.
1297 public void setPruningFilter(Predicate<? super Match> pruningFilter) {
1298 this.pruningFilter = pruningFilter;
1302 * {@inheritDoc}
1304 * @see java.util.Iterator#hasNext()
1306 public boolean hasNext() {
1307 if (nextDiff != null) {
1308 return true;
1310 if (!diffIterator.hasNext()) {
1311 computeNextMatch();
1313 while (nextDiff == null && diffIterator.hasNext()) {
1314 final Diff next = diffIterator.next();
1315 if (filter.apply(next)) {
1316 nextDiff = next;
1319 return nextDiff != null;
1323 * Computes the next match within the sub-match tree, pruning those that may meet
1324 * {@link #pruningFilter}.
1326 private void computeNextMatch() {
1327 final Match old = current;
1328 while (current == old && subMatchIterator.hasNext()) {
1329 final Match next = subMatchIterator.next();
1330 if (pruningFilter.apply(next)) {
1331 subMatchIterator.prune();
1332 } else {
1333 current = next;
1334 diffIterator = current.getDifferences().iterator();
1340 * {@inheritDoc}
1342 * @see java.util.Iterator#next()
1344 public Diff next() {
1345 if (!hasNext()) {
1346 throw new NoSuchElementException();
1348 final Diff next = nextDiff;
1349 nextDiff = null;
1350 return next;
1354 * {@inheritDoc}
1356 * @see java.util.Iterator#remove()
1358 public void remove() {
1359 diffIterator.remove();