Package com.google.testing.compile

Source Code of com.google.testing.compile.TreeDiffer

/*
* 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 com.google.common.base.Objects;
import com.google.common.base.Optional;

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.LiteralTree;
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.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 java.util.Iterator;

import javax.annotation.Nullable;
import javax.lang.model.element.Name;

/**
* A class for determining how two compilation {@code Tree}s differ from each other.
*
* <p>This class takes source ordering into account. That is, two isomorphic
* {@code CompilationUnitTrees} will have {@code TreeDifference} entires 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.
*
* <p>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 final TreeDifference diffCompilationUnits(@Nullable CompilationUnitTree expected,
      @Nullable CompilationUnitTree actual) {
    TreeDifference.Builder diffBuilder = new TreeDifference.Builder();
    DiffVisitor diffVisitor = new DiffVisitor(diffBuilder);
    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.
   *
   * <p>Used for testing.
   */
  static final TreeDifference diffSubtrees(@Nullable TreePath pathToExpected,
      @Nullable TreePath pathToActual) {
    TreeDifference.Builder diffBuilder = new TreeDifference.Builder();
    DiffVisitor diffVisitor = new DiffVisitor(diffBuilder,
        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<Void, Tree> {
    private TreePath expectedPath;
    private TreePath actualPath;

    private final TreeDifference.Builder diffBuilder;

    public DiffVisitor(TreeDifference.Builder diffBuilder) {
      this.diffBuilder = diffBuilder;
      expectedPath = null;
      actualPath = null;
    }

    /**
     * Constructs a DiffVisitor whose {@code TreePath}s are initialized with the paths
     * provided.
     */
    public DiffVisitor(TreeDifference.Builder diffBuilder,
        TreePath pathToExpected, TreePath pathToActual) {
      this.diffBuilder = diffBuilder;
      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.
     *
     * <p>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));
    }

    public 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);
      }
      return null;
    }

    private Void parallelScan(Iterable<? extends Tree> expecteds,
        Iterable<? extends Tree> actuals) {
      if (expecteds != null && actuals != null) {
        Iterator<? extends Tree> expectedsIterator = expecteds.iterator();
        Iterator<? extends Tree> 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()));
      }
      return null;
    }

    private boolean isEmptyOrNull(Iterable<?> iterable) {
      return iterable == null || !iterable.iterator().hasNext();
    }

    @Override
    public Void visitAnnotation(AnnotationTree expected, Tree actual) {
      Optional<AnnotationTree> 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<MethodInvocationTree> 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 visitAssert(AssertTree expected, Tree actual) {
      Optional<AssertTree> 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<AssignmentTree> 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<CompoundAssignmentTree> 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<BinaryTree> 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<BlockTree> 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<BreakTree> 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<CaseTree> 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<CatchTree> 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<ClassTree> 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(), other.get().getMembers());
      return null;
    }

    @Override
    public Void visitConditionalExpression(ConditionalExpressionTree expected, Tree actual) {
      Optional<ConditionalExpressionTree> 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<ContinueTree> 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<DoWhileLoopTree> 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<ErroneousTree> 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<ExpressionStatementTree> 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<EnhancedForLoopTree> 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<ForLoopTree> 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<IdentifierTree> 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<IfTree> 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<ImportTree> 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<ArrayAccessTree> 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<LabeledStatementTree> 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<LiteralTree> 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<MethodTree> 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<ModifiersTree> 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<NewArrayTree> 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<NewClassTree> 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<ParenthesizedTree> 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<ReturnTree> 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<MemberSelectTree> 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<SwitchTree> 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<SynchronizedTree> 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<ThrowTree> 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<CompilationUnitTree> 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(), other.get().getImports());
      parallelScan(expected.getTypeDecls(), other.get().getTypeDecls());
      return null;
    }

    @Override
    public Void visitTry(TryTree expected, Tree actual) {
      Optional<TryTree> 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<ParameterizedTypeTree> 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<ArrayTypeTree> 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<TypeCastTree> 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<PrimitiveTypeTree> 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<TypeParameterTree> 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<InstanceOfTree> 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<UnaryTree> 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<VariableTree> 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<WhileLoopTree> 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<WildcardTree> 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");
    }

    private <T extends Tree> Optional<T> 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();
      }
    }
  }
}
TOP

Related Classes of com.google.testing.compile.TreeDiffer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.