diff --git a/src/main/java/spoon/reflect/visitor/CtAbstractBiScanner.java b/src/main/java/spoon/reflect/visitor/CtAbstractBiScanner.java index b310f47968b..2ad63b37b89 100644 --- a/src/main/java/spoon/reflect/visitor/CtAbstractBiScanner.java +++ b/src/main/java/spoon/reflect/visitor/CtAbstractBiScanner.java @@ -19,81 +19,15 @@ import spoon.reflect.declaration.CtElement; import spoon.reflect.path.CtRole; -import java.util.ArrayDeque; -import java.util.Collection; -import java.util.Deque; -import java.util.Iterator; - /** - * This abstract bi scanner class declares all scan methods useful for CtBiScannerDefault + * Defines the core bi-scan responsibility. */ public abstract class CtAbstractBiScanner extends CtAbstractVisitor { - protected Deque stack = new ArrayDeque<>(); - - protected void enter(CtElement e) { - } - - protected void exit(CtElement e) { - } - - protected boolean isNotEqual = false; - - public boolean biScan(Collection elements, Collection others) { - return biScan(null, elements, others); - } - public boolean biScan(CtRole role, Collection elements, Collection others) { - if (isNotEqual) { - return isNotEqual; - } - if (elements == null) { - if (others != null) { - return fail(); - } - return isNotEqual; - } else if (others == null) { - return fail(); - } - if ((elements.size()) != (others.size())) { - return fail(); - } - for (Iterator firstIt = elements.iterator(), secondIt = others.iterator(); (firstIt.hasNext()) && (secondIt.hasNext());) { - biScan(role, firstIt.next(), secondIt.next()); - } - return isNotEqual; - } - public boolean biScan(CtRole role, CtElement element, CtElement other) { - return biScan(element, other); - } - public boolean biScan(CtElement element, CtElement other) { - if (isNotEqual) { - return isNotEqual; - } - if (element == null) { - if (other != null) { - return fail(); - } - return isNotEqual; - } else if (other == null) { - return fail(); - } - if (element == other) { - return isNotEqual; - } + /** This method is called to compare `element` and `other` when traversing two trees in parallel.*/ + public abstract void biScan(CtElement element, CtElement other); - stack.push(other); - try { - element.accept(this); - } catch (java.lang.ClassCastException e) { - return fail(); - } finally { - stack.pop(); - } - return isNotEqual; - } + /** This method is called to compare `element` and `other` according to the role when traversing two trees in parallel. */ + public abstract void biScan(CtRole role, CtElement element, CtElement other); - public boolean fail() { - isNotEqual = true; - return true; - } } diff --git a/src/main/java/spoon/reflect/visitor/CtBiScannerDefault.java b/src/main/java/spoon/reflect/visitor/CtBiScannerDefault.java index 6431b0ef007..8392cdf8bda 100644 --- a/src/main/java/spoon/reflect/visitor/CtBiScannerDefault.java +++ b/src/main/java/spoon/reflect/visitor/CtBiScannerDefault.java @@ -17,6 +17,14 @@ package spoon.reflect.visitor; +import spoon.reflect.declaration.CtElement; +import spoon.reflect.path.CtRole; + +import java.util.ArrayDeque; +import java.util.Collection; +import java.util.Deque; +import java.util.Iterator; + /** * This visitor implements a deep-search scan on the model for 2 elements. * @@ -28,7 +36,37 @@ * Is used by EqualsVisitor. */ // autogenerated by CtBiScannerGenerator -public abstract class CtBiScannerDefault extends spoon.reflect.visitor.CtAbstractBiScanner { +public class CtBiScannerDefault extends spoon.reflect.visitor.CtAbstractBiScanner { + protected Deque stack = new ArrayDeque<>(); + + protected void enter(CtElement e) { + } + + protected void exit(CtElement e) { + } + + public void biScan(CtElement element, CtElement other) { + if (other == null) { + return; + } + stack.push(other); + try { + element.accept(this); + } finally { + stack.pop(); + } + } + + public void biScan(CtRole role, CtElement element, CtElement other) { + biScan(element, other); + } + + protected void biScan(CtRole role, Collection elements, Collection others) { + for (Iterator firstIt = elements.iterator(), secondIt = others.iterator(); (firstIt.hasNext()) && (secondIt.hasNext());) { + biScan(role, firstIt.next(), secondIt.next()); + } + } + // autogenerated by CtBiScannerGenerator public void visitCtAnnotation(final spoon.reflect.declaration.CtAnnotation annotation) { spoon.reflect.declaration.CtAnnotation other = ((spoon.reflect.declaration.CtAnnotation) (this.stack.peek())); diff --git a/src/main/java/spoon/support/visitor/equals/EqualsVisitor.java b/src/main/java/spoon/support/visitor/equals/EqualsVisitor.java index b3d7f790ae6..728909fb935 100644 --- a/src/main/java/spoon/support/visitor/equals/EqualsVisitor.java +++ b/src/main/java/spoon/support/visitor/equals/EqualsVisitor.java @@ -19,15 +19,22 @@ import spoon.reflect.declaration.CtElement; +import spoon.reflect.path.CtRole; import spoon.reflect.visitor.CtBiScannerDefault; +import java.util.Collection; + /** * Used to check equality between an element and another one. * */ public class EqualsVisitor extends CtBiScannerDefault { public static boolean equals(CtElement element, CtElement other) { - return !new EqualsVisitor().biScan(element, other); + EqualsVisitor equalsVisitor = new EqualsVisitor(); + equalsVisitor.biScan(element, other); + + // double negation is always hard to understand, but this is legacy :-) + return !equalsVisitor.isNotEqual; } private final EqualsChecker checker = new EqualsChecker(); @@ -41,5 +48,62 @@ protected void enter(CtElement e) { fail(); } } + protected boolean isNotEqual = false; + + @Override + protected void biScan(CtRole role, Collection elements, Collection others) { + + if (isNotEqual) { + return; + } + if (elements == null) { + if (others != null) { + fail(); + } + return; + } else if (others == null) { + fail(); + return; + } + if ((elements.size()) != (others.size())) { + fail(); + return; + } + super.biScan(role, elements, others); + } + + @Override + public void biScan(CtElement element, CtElement other) { + if (isNotEqual) { + return; + } + if (element == null) { + if (other != null) { + fail(); + return; + } + return; + } else if (other == null) { + fail(); + return; + } + if (element == other) { + return; + } + + try { + super.biScan(element, other); + } catch (java.lang.ClassCastException e) { + fail(); + } + + return; + } + + private boolean fail() { + isNotEqual = true; + return true; + } + } diff --git a/src/test/java/spoon/generating/scanner/CtBiScannerTemplate.java b/src/test/java/spoon/generating/scanner/CtBiScannerTemplate.java index 96db821ac5d..27f49b03f89 100644 --- a/src/test/java/spoon/generating/scanner/CtBiScannerTemplate.java +++ b/src/test/java/spoon/generating/scanner/CtBiScannerTemplate.java @@ -16,8 +16,15 @@ */ package spoon.generating.scanner; +import spoon.reflect.declaration.CtElement; +import spoon.reflect.path.CtRole; import spoon.reflect.visitor.CtAbstractBiScanner; +import java.util.ArrayDeque; +import java.util.Collection; +import java.util.Deque; +import java.util.Iterator; + /** * This visitor implements a deep-search scan on the model for 2 elements. * @@ -28,5 +35,35 @@ * * Is used by EqualsVisitor. */ -abstract class CtBiScannerTemplate extends CtAbstractBiScanner { +class CtBiScannerTemplate extends CtAbstractBiScanner { + protected Deque stack = new ArrayDeque<>(); + + protected void enter(CtElement e) { + } + + protected void exit(CtElement e) { + } + + public void biScan(CtElement element, CtElement other) { + if (other == null) { + return; + } + stack.push(other); + try { + element.accept(this); + } finally { + stack.pop(); + } + } + + public void biScan(CtRole role, CtElement element, CtElement other) { + biScan(element, other); + } + + protected void biScan(CtRole role, Collection elements, Collection others) { + for (Iterator firstIt = elements.iterator(), secondIt = others.iterator(); (firstIt.hasNext()) && (secondIt.hasNext());) { + biScan(role, firstIt.next(), secondIt.next()); + } + } + } diff --git a/src/test/java/spoon/test/main/MainTest.java b/src/test/java/spoon/test/main/MainTest.java index ee5e6b6f2e1..ff5a096d7de 100644 --- a/src/test/java/spoon/test/main/MainTest.java +++ b/src/test/java/spoon/test/main/MainTest.java @@ -106,9 +106,6 @@ public void testMain_checkParentConsistency() { } public void checkGenericContracts(CtPackage pack) { - // clone - checkEqualityBetweenOriginalAndClone(pack); - // parent ParentTest.checkParentContract(pack); @@ -135,27 +132,6 @@ public void visitCtTypeParameterReference(CtTypeParameterReference ref) { }.scan(pack); } - private void checkEqualityBetweenOriginalAndClone(CtPackage pack) { - class ActualCounterScanner extends CtBiScannerDefault { - @Override - public boolean biScan(CtElement element, CtElement other) { - if (element == null) { - if (other != null) { - Assert.fail("element can't be null if other isn't null."); - } - } else if (other == null) { - Assert.fail("other can't be null if element isn't null."); - } else { - assertEquals(element, other); - assertFalse(element == other); - } - return super.biScan(element, other); - } - } - final ActualCounterScanner actual = new ActualCounterScanner(); - actual.biScan(pack, pack.clone()); - } - private void checkShadow(CtPackage pack) { new CtScanner() { @Override @@ -285,11 +261,13 @@ class Counter { } final Counter counter = new Counter(); + final Counter counterInclNull = new Counter(); new CtScanner() { @Override public void scan(CtElement element) { + counterInclNull.scan++; if (element != null) { counter.scan++; } @@ -308,11 +286,52 @@ public void exit(CtElement element) { super.exit(element); } - }.visitCtPackage(pack); + }.scan(pack); + // contract: when enter is called, exit is also called assertTrue(counter.enter == counter.exit); - // there is one scan less, because we start with visit - assertTrue(counter.enter == counter.scan + 1); + + // contract: all scanned elements call enter + assertTrue(counter.enter == counter.scan); + + Counter counterBiScan = new Counter(); + class ActualCounterScanner extends CtBiScannerDefault { + @Override + public void biScan(CtElement element, CtElement other) { + counterBiScan.scan++; + if (element == null) { + if (other != null) { + Assert.fail("element can't be null if other isn't null."); + } + } else if (other == null) { + Assert.fail("other can't be null if element isn't null."); + } else { + // contract: all elements have been cloned and are still equal + assertEquals(element, other); + assertFalse(element == other); + } + super.biScan(element, other); + } + } + final ActualCounterScanner actual = new ActualCounterScanner(); + actual.biScan(pack, pack.clone()); + + // contract: scan and biscan are executed the same number of times + assertEquals(counterInclNull.scan, counterBiScan.scan); + + // for pure beauty: parallel visit of the same tree! + Counter counterBiScan2 = new Counter(); + new CtBiScannerDefault() { + @Override + public void biScan(CtElement element, CtElement other) { + counterBiScan2.scan++; + // we have the exact same element + assertSame(element, other); + super.biScan(element, other); + } + }.biScan(pack, pack); + // contract: scan and biscan are executed the same number of times + assertEquals(counterInclNull.scan, counterBiScan2.scan); } public static void checkAssignmentContracts(CtElement pack) {