1 /*******************************************************************************
2 * Copyright (c) 2012, 2014 Obeo.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
9 * Obeo - initial API and implementation
10 * 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
;
63 * The conflict detector is in charge of refining the Comparison model with all detected Conflict between its
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.
71 * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
73 public class DefaultConflictDetector
implements IConflictDetector
{
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
) {
89 isRealAddContainmentConflict
= true;
92 return isRealAddContainmentConflict
;
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
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.
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.
153 * The originating comparison of those diffs.
155 * Diff for which we are to try and determine conflicts.
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
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));
181 switch (diff
.getKind()) {
183 checkFeatureDeleteConflict(comparison
, diff
, candidates
);
186 checkFeatureChangeConflict(comparison
, diff
, candidates
);
189 checkFeatureMoveConflict(comparison
, diff
, candidates
);
192 checkFeatureAddConflict(comparison
, diff
, candidates
);
201 * This will be called once for each ReferenceChange on containment references in the comparison model.
204 * The originating comparison of those diffs.
206 * The reference change for which we are to try and determine conflicts.
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
);
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.
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.
263 * The originating comparison of those diffs.
265 * Containment reference changes for which we need to check possible conflicts.
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
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
295 if (candidateIsDelete
) {
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.
308 * The originating comparison of those diffs.
310 * The feature map change for which we are to try and determine conflicts.
312 * An iterable over the FeatureMapChanges that are possible candidates for conflicts.
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
);
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
);
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.
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.
363 * The originating comparison of those diffs.
365 * Containment feature map changes for which we need to check possible conflicts.
367 * A feature map change that point to the same value as {@code diff}.
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
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
396 if (candidateIsDelete
) {
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.
409 * the left candidate.
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".
424 * Those can only conflict with other CHANGE Diffs on the same reference.
428 * The originating comparison of those diffs.
430 * The diff which we are to check for conflicts.
432 * The list of candidates for a conflict. This list only contains Diff from the side opposite
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();
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
;
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();
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.
497 * One of the diffs to check.
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}.
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
523 * @see org.eclipse.emf.compare.internal.ThreeWayTextDiff
525 * One of the diffs to check.
527 * The other diff to check.
528 * @return <code>true</code> if the diffs are mergeable changes of a string attribute, <code>false</code>
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
);
538 mergeableStringAttributeChange
= false;
540 return mergeableStringAttributeChange
;
544 * Specifies whether the given {@code diff} is a {@link AttributeChange} of a String attribute.
548 * @return <code>true</code> if it is a {@link AttributeChange} of a String attribute, <code>false</code>
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
562 * One of the attribute changes to check.
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.
585 * The original version.
586 * @return <code>true</code> if they are mergeable, false otherwise.
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}.
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());
609 changedValue
= (String
)diff
.getValue();
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".
618 * Those can only conflict with other Diffs of the same type on the same reference.
622 * The originating comparison of those diffs.
624 * The diff which we are to check for conflicts.
626 * The list of candidates for a conflict. This list only contains Diff from the side opposite
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();
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
;
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();
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
);
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.
689 * The only potential conflict for such a diff is a "MOVE" of that same value on the opposite side.
693 * The originating comparison of those diffs.
695 * The diff which we are to check for conflicts.
697 * The list of candidates for a conflict. This list only contains Diff from the side opposite
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();
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;
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
;
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();
750 if (comparison
.getEqualityHelper().matchingValues(deletedValue
, movedValue
)) {
751 if (candidate
.getKind() == DifferenceKind
.MOVE
) {
752 conflictOn(comparison
, diff
, candidate
, ConflictKind
.REAL
);
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.
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.
769 * The originating comparison of those diffs.
771 * The diff which we are to check for conflicts.
773 * The list of candidates for a conflict. This list only contains Diff from the side opposite
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();
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;
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
;
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();
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
);
845 conflictOn(comparison
, diff
, candidate
, ConflictKind
.REAL
);
852 * This will be called once for each ResourceAttachmentChange in the comparison model.
855 * The originating comparison of those diffs.
857 * The "root" difference for which we are to try and determine conflicts.
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();
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
);
904 conflictOn(comparison
, diff
, extendedCandidate
, ConflictKind
.REAL
);
911 * Returns the MatchResource corresponding to the given <code>resource</code>.
914 * the comparison to search for a MatchResource.
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$
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.
946 * The originating comparison of those diffs.
948 * Resource attachment change for which we need to check possible conflicts.
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
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
) {
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.
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.
977 * Note that no sanity checks will be made on either the match's sides or the feature.
981 * Provides us with the necessary information to match EObjects.
983 * Match for which we need to check a feature.
985 * The feature which values we need to check.
987 * First of the two values which index we are to compare.
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...
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
)) {
1008 } else if (hasDiff(match
, feature
, left
) || hasDeleteDiff(match
, feature
, left
)) {
1009 // Do not increment.
1014 for (int i
= 0; i
< rightValues
.size(); i
++) {
1015 final Object right
= rightValues
.get(i
);
1016 if (comparison
.getEqualityHelper().matchingValues(right
, value2
)) {
1018 } else if (hasDiff(match
, feature
, right
) || hasDeleteDiff(match
, feature
, right
)) {
1019 // Do not increment.
1024 matching
= leftIndex
== rightIndex
;
1032 * Checks whether the given {@code match} presents a difference of any kind on the given {@code feature}'s
1036 * The match which differences we'll check.
1038 * The feature on which we expect a difference.
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}
1052 * The match which differences we'll check.
1054 * The feature on which we expect a difference.
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();
1068 expectedValue
= value
;
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
1082 * The originating comparison of those diffs.
1084 * First of the two differences for which we detected a conflict.
1086 * Second of the two differences for which we detected a conflict.
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) {
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) {
1117 toBeMerged
= diff2
.getConflict();
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
);
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
1180 * The Diff for which we seek conflict candidates.
1182 public ConflictCandidateFilter(Diff reference
) {
1183 this.reference
= reference
;
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}.
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.
1203 * bug 381143 : we'll also remove any containment deletion diff on other Matches from here.
1207 * First of the two differences to consider for conflict detection.
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()) {
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
);
1232 * A custom iterator that will walk a Match->submatch tree, and allow iteration over the Diffs of these
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
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
1266 private Predicate
<?
super Match
> pruningFilter
= Predicates
.alwaysFalse();
1269 * Constructs our iterator given the root of the Match tree to iterate over.
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.
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
;
1304 * @see java.util.Iterator#hasNext()
1306 public boolean hasNext() {
1307 if (nextDiff
!= null) {
1310 if (!diffIterator
.hasNext()) {
1313 while (nextDiff
== null && diffIterator
.hasNext()) {
1314 final Diff next
= diffIterator
.next();
1315 if (filter
.apply(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();
1334 diffIterator
= current
.getDifferences().iterator();
1342 * @see java.util.Iterator#next()
1344 public Diff
next() {
1346 throw new NoSuchElementException();
1348 final Diff next
= nextDiff
;
1356 * @see java.util.Iterator#remove()
1358 public void remove() {
1359 diffIterator
.remove();