All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.testing.compile.TreeDiffer Maven / Gradle / Ivy

/*
 * Copyright (C) 2014 Google, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.testing.compile;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;

import com.google.auto.common.MoreTypes;
import com.google.auto.value.AutoValue;
import com.google.common.base.Equivalence;
import com.google.common.base.Joiner;
import com.google.common.base.Objects;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ArrayAccessTree;
import com.sun.source.tree.ArrayTypeTree;
import com.sun.source.tree.AssertTree;
import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.BreakTree;
import com.sun.source.tree.CaseTree;
import com.sun.source.tree.CatchTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.CompoundAssignmentTree;
import com.sun.source.tree.ConditionalExpressionTree;
import com.sun.source.tree.ContinueTree;
import com.sun.source.tree.DoWhileLoopTree;
import com.sun.source.tree.EmptyStatementTree;
import com.sun.source.tree.EnhancedForLoopTree;
import com.sun.source.tree.ErroneousTree;
import com.sun.source.tree.ExpressionStatementTree;
import com.sun.source.tree.ForLoopTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.IfTree;
import com.sun.source.tree.ImportTree;
import com.sun.source.tree.InstanceOfTree;
import com.sun.source.tree.LabeledStatementTree;
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.LiteralTree;
import com.sun.source.tree.MemberReferenceTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ModifiersTree;
import com.sun.source.tree.NewArrayTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.ParameterizedTypeTree;
import com.sun.source.tree.ParenthesizedTree;
import com.sun.source.tree.PrimitiveTypeTree;
import com.sun.source.tree.ReturnTree;
import com.sun.source.tree.SwitchTree;
import com.sun.source.tree.SynchronizedTree;
import com.sun.source.tree.ThrowTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.tree.TreeVisitor;
import com.sun.source.tree.TryTree;
import com.sun.source.tree.TypeCastTree;
import com.sun.source.tree.TypeParameterTree;
import com.sun.source.tree.UnaryTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.tree.WhileLoopTree;
import com.sun.source.tree.WildcardTree;
import com.sun.source.util.SimpleTreeVisitor;
import com.sun.source.util.TreePath;
import com.sun.source.util.Trees;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import javax.annotation.Nullable;
import javax.lang.model.element.Name;
import javax.lang.model.type.TypeMirror;
import javax.tools.JavaFileObject;

/**
 * A class for determining how two compilation {@code Tree}s differ from each other.
 *
 * 

This class takes source ordering into account. That is, two isomorphic * {@code CompilationUnitTrees} will have {@code TreeDifference} entries if their child * nodes do not appear in the same order. However, the ordering of the {@code TreeDifference} * entries that this class produces is always unspecified. * *

Future releases of this class will likely attempt to match corresponding sub{@code Tree}s * and ensure that those similarities are reflected in the diff report. No such attempt is made in * this release. * * @author Stephen Pratt */ @SuppressWarnings("restriction") // Sun APIs usage intended final class TreeDiffer { private TreeDiffer() {} /** * Returns a {@code TreeDifference} describing the difference between the two {@code * CompilationUnitTree}s provided. */ static TreeDifference diffCompilationUnits( CompilationUnitTree expected, CompilationUnitTree actual) { return createDiff(checkNotNull(expected), checkNotNull(actual), TreeFilter.KEEP_ALL); } /** * Returns a {@code TreeDifference} describing the difference between the actual {@code * CompilationUnitTree} provided and the pattern. See {@link * JavaFileObjectSubject#containsElementsIn(JavaFileObject)} for more details on how the pattern * is used. */ static TreeDifference matchCompilationUnits( CompilationUnitTree pattern, Trees patternTrees, CompilationUnitTree actual, Trees actualTrees) { checkNotNull(pattern); checkNotNull(actual); return createDiff( pattern, actual, new MatchExpectedTreesFilter(pattern, patternTrees, actual, actualTrees)); } private static TreeDifference createDiff( @Nullable CompilationUnitTree expected, @Nullable CompilationUnitTree actual, TreeFilter treeFilter) { TreeDifference.Builder diffBuilder = new TreeDifference.Builder(); DiffVisitor diffVisitor = new DiffVisitor(diffBuilder, treeFilter); diffVisitor.scan(expected, actual); return diffBuilder.build(); } /** * Returns a {@link TreeDifference} describing the difference between the two * sub-{@code Tree}s. The trees diffed are the leaves of the {@link TreePath}s * provided. * *

Used for testing. */ static final TreeDifference diffSubtrees(@Nullable TreePath pathToExpected, @Nullable TreePath pathToActual) { TreeDifference.Builder diffBuilder = new TreeDifference.Builder(); DiffVisitor diffVisitor = new DiffVisitor(diffBuilder, TreeFilter.KEEP_ALL, pathToExpected, pathToActual); diffVisitor.scan(pathToExpected.getLeaf(), pathToActual.getLeaf()); return diffBuilder.build(); } /** * A {@code SimpleTreeVisitor} that traverses a {@link Tree} and an argument {@link Tree}, * verifying equality along the way. Appends each diff it finds to a * {@link TreeDifference.Builder}. */ static final class DiffVisitor extends SimpleTreeVisitor { private TreePath expectedPath; private TreePath actualPath; private final TreeDifference.Builder diffBuilder; private final TreeFilter filter; DiffVisitor(TreeDifference.Builder diffBuilder, TreeFilter filter) { this(diffBuilder, filter, null, null); } /** * Constructs a DiffVisitor whose {@code TreePath}s are initialized with the paths * provided. */ private DiffVisitor(TreeDifference.Builder diffBuilder, TreeFilter filter, TreePath pathToExpected, TreePath pathToActual) { this.diffBuilder = diffBuilder; this.filter = filter; expectedPath = pathToExpected; actualPath = pathToActual; } /** * Adds a {@code TwoWayDiff} that results from two {@code Tree}s having different * {@code Tree.Kind}s. */ public void addTypeMismatch(Tree expected, Tree actual) { diffBuilder.addDifferingNodes(expectedPathPlus(expected), actualPathPlus(actual), String.format("Expected node kind to be <%s> but was <%s>.", expected.getKind(), actual.getKind())); } /** * Adds a {@code TwoWayDiff} if the predicate given evaluates to false. The {@code TwoWayDiff} * is parameterized by the {@code Tree}s and message format provided. */ private void checkForDiff(boolean p, String message, Object... formatArgs) { if (!p) { diffBuilder.addDifferingNodes(expectedPath, actualPath, String.format(message, formatArgs)); } } private TreePath actualPathPlus(Tree actual) { checkNotNull(actual, "Tried to push null actual tree onto path."); return new TreePath(actualPath, actual); } private TreePath expectedPathPlus(Tree expected) { checkNotNull(expected, "Tried to push null expected tree onto path."); return new TreePath(expectedPath, expected); } /** * Pushes the {@code expected} and {@code actual} {@link Tree}s onto their respective * {@link TreePath}s and recurses with {@code expected.accept(this, actual)}, popping the * stack when the call completes. * *

This should be the ONLY place where either {@link TreePath} is mutated. */ private Void pushPathAndAccept(Tree expected, Tree actual) { TreePath prevExpectedPath = expectedPath; TreePath prevActualPath = actualPath; expectedPath = expectedPathPlus(expected); actualPath = actualPathPlus(actual); try { return expected.accept(this, actual); } finally { expectedPath = prevExpectedPath; actualPath = prevActualPath; } } private boolean namesEqual(@Nullable Name expected, @Nullable Name actual) { return (expected == null) ? actual == null : (actual != null && expected.contentEquals(actual)); } private void scan(@Nullable Tree expected, @Nullable Tree actual) { if (expected == null && actual != null) { diffBuilder.addExtraActualNode(actualPathPlus(actual)); } else if (expected != null && actual == null) { diffBuilder.addExtraExpectedNode(expectedPathPlus(expected)); } else if (actual != null && expected != null) { pushPathAndAccept(expected, actual); } } private void parallelScan( Iterable expecteds, Iterable actuals) { if (expecteds != null && actuals != null) { Iterator expectedsIterator = expecteds.iterator(); Iterator actualsIterator = actuals.iterator(); while (expectedsIterator.hasNext() && actualsIterator.hasNext()) { pushPathAndAccept(expectedsIterator.next(), actualsIterator.next()); } if (!expectedsIterator.hasNext() && actualsIterator.hasNext()) { diffBuilder.addExtraActualNode(actualPathPlus(actualsIterator.next())); } else if (expectedsIterator.hasNext() && !actualsIterator.hasNext()) { diffBuilder.addExtraExpectedNode(expectedPathPlus(expectedsIterator.next())); } } else if (expecteds == null && !isEmptyOrNull(actuals)) { diffBuilder.addExtraActualNode(actualPathPlus(actuals.iterator().next())); } else if (actuals == null && !isEmptyOrNull(expecteds)) { diffBuilder.addExtraExpectedNode(expectedPathPlus(expecteds.iterator().next())); } } private boolean isEmptyOrNull(Iterable iterable) { return iterable == null || !iterable.iterator().hasNext(); } @Override public Void visitAnnotation(AnnotationTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } scan(expected.getAnnotationType(), other.get().getAnnotationType()); parallelScan(expected.getArguments(), other.get().getArguments()); return null; } @Override public Void visitMethodInvocation(MethodInvocationTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } parallelScan(expected.getTypeArguments(), other.get().getTypeArguments()); scan(expected.getMethodSelect(), other.get().getMethodSelect()); parallelScan(expected.getArguments(), other.get().getArguments()); return null; } @Override public Void visitLambdaExpression(LambdaExpressionTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } parallelScan(expected.getParameters(), other.get().getParameters()); scan(expected.getBody(), other.get().getBody()); return null; } @Override public Void visitMemberReference(MemberReferenceTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } scan(expected.getQualifierExpression(), other.get().getQualifierExpression()); parallelScan(expected.getTypeArguments(), other.get().getTypeArguments()); checkForDiff(expected.getName().contentEquals(other.get().getName()), "Expected identifier to be <%s> but was <%s>.", expected.getName(), other.get().getName()); return null; } @Override public Void visitAssert(AssertTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } scan(expected.getCondition(), other.get().getCondition()); scan(expected.getDetail(), other.get().getDetail()); return null; } @Override public Void visitAssignment(AssignmentTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } scan(expected.getVariable(), other.get().getVariable()); scan(expected.getExpression(), other.get().getExpression()); return null; } @Override public Void visitCompoundAssignment(CompoundAssignmentTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } scan(expected.getVariable(), other.get().getVariable()); scan(expected.getExpression(), other.get().getExpression()); return null; } @Override public Void visitBinary(BinaryTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } scan(expected.getLeftOperand(), other.get().getLeftOperand()); scan(expected.getRightOperand(), other.get().getRightOperand()); return null; } @Override public Void visitBlock(BlockTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } checkForDiff(expected.isStatic() == other.get().isStatic(), "Expected block to be <%s> but was <%s>.", expected.isStatic() ? "static" : "non-static", other.get().isStatic() ? "static" : "non-static"); parallelScan(expected.getStatements(), other.get().getStatements()); return null; } @Override public Void visitBreak(BreakTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } checkForDiff(namesEqual(expected.getLabel(), other.get().getLabel()), "Expected label on break statement to be <%s> but was <%s>.", expected.getLabel(), other.get().getLabel()); return null; } @Override public Void visitCase(CaseTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } scan(expected.getExpression(), other.get().getExpression()); parallelScan(expected.getStatements(), other.get().getStatements()); return null; } @Override public Void visitCatch(CatchTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } scan(expected.getParameter(), other.get().getParameter()); scan(expected.getBlock(), other.get().getBlock()); return null; } @Override public Void visitClass(ClassTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } checkForDiff(expected.getSimpleName().contentEquals(other.get().getSimpleName()), "Expected name of type to be <%s> but was <%s>.", expected.getSimpleName(), other.get().getSimpleName()); scan(expected.getModifiers(), other.get().getModifiers()); parallelScan(expected.getTypeParameters(), other.get().getTypeParameters()); scan(expected.getExtendsClause(), other.get().getExtendsClause()); parallelScan(expected.getImplementsClause(), other.get().getImplementsClause()); parallelScan( expected.getMembers(), filter.filterActualMembers( ImmutableList.copyOf(expected.getMembers()), ImmutableList.copyOf(other.get().getMembers()))); return null; } @Override public Void visitConditionalExpression(ConditionalExpressionTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } scan(expected.getCondition(), other.get().getCondition()); scan(expected.getTrueExpression(), other.get().getTrueExpression()); scan(expected.getFalseExpression(), other.get().getFalseExpression()); return null; } @Override public Void visitContinue(ContinueTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } checkForDiff(namesEqual(expected.getLabel(), other.get().getLabel()), "Expected label on continue statement to be <%s> but was <%s>.", expected.getLabel(), other.get().getLabel()); return null; } @Override public Void visitDoWhileLoop(DoWhileLoopTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } scan(expected.getCondition(), other.get().getCondition()); scan(expected.getStatement(), other.get().getStatement()); return null; } @Override public Void visitErroneous(ErroneousTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } parallelScan(expected.getErrorTrees(), other.get().getErrorTrees()); return null; } @Override public Void visitExpressionStatement(ExpressionStatementTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } scan(expected.getExpression(), other.get().getExpression()); return null; } @Override public Void visitEnhancedForLoop(EnhancedForLoopTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } scan(expected.getVariable(), other.get().getVariable()); scan(expected.getExpression(), other.get().getExpression()); scan(expected.getStatement(), other.get().getStatement()); return null; } @Override public Void visitForLoop(ForLoopTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } parallelScan(expected.getInitializer(), other.get().getInitializer()); scan(expected.getCondition(), other.get().getCondition()); parallelScan(expected.getUpdate(), other.get().getUpdate()); scan(expected.getStatement(), other.get().getStatement()); return null; } @Override public Void visitIdentifier(IdentifierTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } checkForDiff(expected.getName().contentEquals(other.get().getName()), "Expected identifier to be <%s> but was <%s>.", expected.getName(), other.get().getName()); return null; } @Override public Void visitIf(IfTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } scan(expected.getCondition(), other.get().getCondition()); scan(expected.getThenStatement(), other.get().getThenStatement()); scan(expected.getElseStatement(), other.get().getElseStatement()); return null; } @Override public Void visitImport(ImportTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } checkForDiff(expected.isStatic() == other.get().isStatic(), "Expected import to be <%s> but was <%s>.", expected.isStatic() ? "static" : "non-static", other.get().isStatic() ? "static" : "non-static"); scan(expected.getQualifiedIdentifier(), other.get().getQualifiedIdentifier()); return null; } @Override public Void visitArrayAccess(ArrayAccessTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } scan(expected.getExpression(), other.get().getExpression()); scan(expected.getIndex(), other.get().getIndex()); return null; } @Override public Void visitLabeledStatement(LabeledStatementTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } checkForDiff(expected.getLabel().contentEquals(other.get().getLabel()), "Expected statement label to be <%s> but was <%s>.", expected.getLabel(), other.get().getLabel()); scan(expected.getStatement(), other.get().getStatement()); return null; } @Override public Void visitLiteral(LiteralTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } checkForDiff(Objects.equal(expected.getValue(), other.get().getValue()), "Expected literal value to be <%s> but was <%s>.", expected.getValue(), other.get().getValue()); return null; } @Override public Void visitMethod(MethodTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } checkForDiff(expected.getName().contentEquals(other.get().getName()), "Expected method name to be <%s> but was <%s>.", expected.getName(), other.get().getName()); scan(expected.getModifiers(), other.get().getModifiers()); scan(expected.getReturnType(), other.get().getReturnType()); parallelScan(expected.getTypeParameters(), other.get().getTypeParameters()); parallelScan(expected.getParameters(), other.get().getParameters()); parallelScan(expected.getThrows(), other.get().getThrows()); scan(expected.getBody(), other.get().getBody()); scan(expected.getDefaultValue(), other.get().getDefaultValue()); return null; } @Override public Void visitModifiers(ModifiersTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } checkForDiff(expected.getFlags().equals(other.get().getFlags()), "Expected modifier set to be <%s> but was <%s>.", expected.getFlags(), other.get().getFlags()); parallelScan(expected.getAnnotations(), other.get().getAnnotations()); return null; } @Override public Void visitNewArray(NewArrayTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } scan(expected.getType(), other.get().getType()); parallelScan(expected.getDimensions(), other.get().getDimensions()); parallelScan(expected.getInitializers(), other.get().getInitializers()); return null; } @Override public Void visitNewClass(NewClassTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } scan(expected.getEnclosingExpression(), other.get().getEnclosingExpression()); parallelScan(expected.getTypeArguments(), other.get().getTypeArguments()); scan(expected.getIdentifier(), other.get().getIdentifier()); parallelScan(expected.getArguments(), other.get().getArguments()); scan(expected.getClassBody(), other.get().getClassBody()); return null; } @Override public Void visitParenthesized(ParenthesizedTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } scan(expected.getExpression(), other.get().getExpression()); return null; } @Override public Void visitReturn(ReturnTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } scan(expected.getExpression(), other.get().getExpression()); return null; } @Override public Void visitMemberSelect(MemberSelectTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } checkForDiff(expected.getIdentifier().contentEquals(other.get().getIdentifier()), "Expected member identifier to be <%s> but was <%s>.", expected.getIdentifier(), other.get().getIdentifier()); scan(expected.getExpression(), other.get().getExpression()); return null; } @Override public Void visitEmptyStatement(EmptyStatementTree expected, Tree actual) { if (!checkTypeAndCast(expected, actual).isPresent()) { addTypeMismatch(expected, actual); return null; } return null; } @Override public Void visitSwitch(SwitchTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } scan(expected.getExpression(), other.get().getExpression()); parallelScan(expected.getCases(), other.get().getCases()); return null; } @Override public Void visitSynchronized(SynchronizedTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } scan(expected.getExpression(), other.get().getExpression()); scan(expected.getBlock(), other.get().getBlock()); return null; } @Override public Void visitThrow(ThrowTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } scan(expected.getExpression(), other.get().getExpression()); return null; } @Override public Void visitCompilationUnit(CompilationUnitTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } parallelScan(expected.getPackageAnnotations(), other.get().getPackageAnnotations()); scan(expected.getPackageName(), other.get().getPackageName()); parallelScan( expected.getImports(), filter.filterImports( ImmutableList.copyOf(expected.getImports()), ImmutableList.copyOf(other.get().getImports()))); parallelScan(expected.getTypeDecls(), other.get().getTypeDecls()); return null; } @Override public Void visitTry(TryTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } scan(expected.getBlock(), other.get().getBlock()); parallelScan(expected.getCatches(), other.get().getCatches()); scan(expected.getFinallyBlock(), other.get().getFinallyBlock()); return null; } @Override public Void visitParameterizedType(ParameterizedTypeTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } scan(expected.getType(), other.get().getType()); parallelScan(expected.getTypeArguments(), other.get().getTypeArguments()); return null; } @Override public Void visitArrayType(ArrayTypeTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } scan(expected.getType(), other.get().getType()); return null; } @Override public Void visitTypeCast(TypeCastTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } scan(expected.getType(), other.get().getType()); scan(expected.getExpression(), other.get().getExpression()); return null; } @Override public Void visitPrimitiveType(PrimitiveTypeTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } checkForDiff(expected.getPrimitiveTypeKind() == other.get().getPrimitiveTypeKind(), "Expected primitive type kind to be <%s> but was <%s>.", expected.getPrimitiveTypeKind(), other.get().getPrimitiveTypeKind()); return null; } @Override public Void visitTypeParameter(TypeParameterTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } checkForDiff(expected.getName().contentEquals(other.get().getName()), "Expected type parameter name to be <%s> but was <%s>.", expected.getName(), other.get().getName()); parallelScan(expected.getBounds(), other.get().getBounds()); return null; } @Override public Void visitInstanceOf(InstanceOfTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } scan(expected.getExpression(), other.get().getExpression()); scan(expected.getType(), other.get().getType()); return null; } @Override public Void visitUnary(UnaryTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } scan(expected.getExpression(), other.get().getExpression()); return null; } @Override public Void visitVariable(VariableTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } checkForDiff(expected.getName().contentEquals(other.get().getName()), "Expected variable name to be <%s> but was <%s>.", expected.getName(), other.get().getName()); scan(expected.getModifiers(), other.get().getModifiers()); scan(expected.getType(), other.get().getType()); scan(expected.getInitializer(), other.get().getInitializer()); return null; } @Override public Void visitWhileLoop(WhileLoopTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } scan(expected.getCondition(), other.get().getCondition()); scan(expected.getStatement(), other.get().getStatement()); return null; } @Override public Void visitWildcard(WildcardTree expected, Tree actual) { Optional other = checkTypeAndCast(expected, actual); if (!other.isPresent()) { addTypeMismatch(expected, actual); return null; } scan(expected.getBound(), other.get().getBound()); return null; } @Override public Void visitOther(Tree expected, Tree actual) { throw new UnsupportedOperationException("cannot compare unknown trees"); } // TODO(dpb,ronshapiro): rename this method and document which one is cast private Optional checkTypeAndCast(T expected, Tree actual) { Kind expectedKind = checkNotNull(expected).getKind(); Kind treeKind = checkNotNull(actual).getKind(); if (expectedKind == treeKind) { @SuppressWarnings("unchecked") // checked by Kind T treeAsExpectedType = (T) actual; return Optional.of(treeAsExpectedType); } else { return Optional.absent(); } } } /** Strategy for determining which {link Tree}s should be diffed in {@link DiffVisitor}. */ private interface TreeFilter { /** Returns the subset of {@code actualMembers} that should be diffed by {@link DiffVisitor}. */ ImmutableList filterActualMembers( ImmutableList expectedMembers, ImmutableList actualMembers); /** Returns the subset of {@code actualImports} that should be diffed by {@link DiffVisitor}. */ ImmutableList filterImports( ImmutableList expectedImports, ImmutableList actualImports); /** A {@link TreeFilter} that doesn't filter any subtrees out of the actual source AST. */ TreeFilter KEEP_ALL = new TreeFilter() { @Override public ImmutableList filterActualMembers( ImmutableList expectedMembers, ImmutableList actualMembers) { return actualMembers; } @Override public ImmutableList filterImports( ImmutableList expectedImports, ImmutableList actualImports) { return actualImports; } }; } /** * A {@link TreeFilter} that ignores all {@link Tree}s that don't have a matching {@link Tree} in * a pattern. For more information on what trees are filtered, see {@link * JavaFileObjectSubject#containsElementsIn(JavaFileObject)}. */ private static class MatchExpectedTreesFilter implements TreeFilter { private final CompilationUnitTree pattern; private final Trees patternTrees; private final CompilationUnitTree actual; private final Trees actualTrees; MatchExpectedTreesFilter( CompilationUnitTree pattern, Trees patternTrees, CompilationUnitTree actual, Trees actualTrees) { this.pattern = pattern; this.patternTrees = patternTrees; this.actual = actual; this.actualTrees = actualTrees; } @Override public ImmutableList filterActualMembers( ImmutableList patternMembers, ImmutableList actualMembers) { Set patternVariableNames = new HashSet<>(); Set patternNestedTypeNames = new HashSet<>(); Set patternMethods = new HashSet<>(); for (Tree patternTree : patternMembers) { patternTree.accept( new SimpleTreeVisitor() { @Override public Void visitVariable(VariableTree variable, Void p) { patternVariableNames.add(variable.getName().toString()); return null; } @Override public Void visitMethod(MethodTree method, Void p) { patternMethods.add(MethodSignature.create(pattern, method, patternTrees)); return null; } @Override public Void visitClass(ClassTree clazz, Void p) { patternNestedTypeNames.add(clazz.getSimpleName().toString()); return null; } }, null); } ImmutableList.Builder filteredActualTrees = ImmutableList.builder(); for (Tree actualTree : actualMembers) { actualTree.accept(new SimpleTreeVisitor(){ @Override public Void visitVariable(VariableTree variable, Void p) { if (patternVariableNames.contains(variable.getName().toString())) { filteredActualTrees.add(actualTree); } return null; } @Override public Void visitMethod(MethodTree method, Void p) { if (patternMethods.contains(MethodSignature.create(actual, method, actualTrees))) { filteredActualTrees.add(method); } return null; } @Override public Void visitClass(ClassTree clazz, Void p) { if (patternNestedTypeNames.contains(clazz.getSimpleName().toString())) { filteredActualTrees.add(clazz); } return null; } @Override protected Void defaultAction(Tree tree, Void p) { filteredActualTrees.add(tree); return null; } }, null); } return filteredActualTrees.build(); } @Override public ImmutableList filterImports( ImmutableList patternImports, ImmutableList actualImports) { ImmutableSet patternImportsAsStrings = patternImports.stream().map(this::fullyQualifiedImport).collect(toImmutableSet()); return actualImports .stream() .filter(importTree -> patternImportsAsStrings.contains(fullyQualifiedImport(importTree))) .collect(toImmutableList()); } private String fullyQualifiedImport(ImportTree importTree) { ImmutableList.Builder names = ImmutableList.builder(); importTree.getQualifiedIdentifier().accept(IMPORT_NAMES_ACCUMULATOR, names); return Joiner.on('.').join(names.build().reverse()); } } private static final TreeVisitor> IMPORT_NAMES_ACCUMULATOR = new SimpleTreeVisitor>() { @Override public Void visitMemberSelect( MemberSelectTree memberSelectTree, ImmutableList.Builder names) { names.add(memberSelectTree.getIdentifier()); return memberSelectTree.getExpression().accept(this, names); } @Override public Void visitIdentifier( IdentifierTree identifierTree, ImmutableList.Builder names) { names.add(identifierTree.getName()); return null; } }; @AutoValue abstract static class MethodSignature { abstract String name(); abstract ImmutableList> parameterTypes(); static MethodSignature create( CompilationUnitTree compilationUnitTree, MethodTree tree, Trees trees) { ImmutableList.Builder> parameterTypes = ImmutableList.builder(); for (VariableTree parameter : tree.getParameters()) { parameterTypes.add( MoreTypes.equivalence() .wrap(trees.getTypeMirror(trees.getPath(compilationUnitTree, parameter)))); } return new AutoValue_TreeDiffer_MethodSignature( tree.getName().toString(), parameterTypes.build()); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy