Package com.google.errorprone.util

Source Code of com.google.errorprone.util.ASTHelpers

/*
* Copyright 2012 Google Inc. All Rights Reserved.
*
* 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.errorprone.util;

import com.google.common.base.CharMatcher;
import com.google.errorprone.VisitorState;
import com.google.errorprone.matchers.Matcher;

import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Scope;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import com.sun.tools.javac.code.Symbol.TypeSymbol;
import com.sun.tools.javac.code.Symbol.VarSymbol;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Types;
import com.sun.tools.javac.tree.JCTree.JCArrayTypeTree;
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
import com.sun.tools.javac.tree.JCTree.JCIdent;
import com.sun.tools.javac.tree.JCTree.JCLiteral;
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
import com.sun.tools.javac.tree.JCTree.JCNewClass;
import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree;
import com.sun.tools.javac.tree.JCTree.JCTypeApply;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;

import java.lang.annotation.Annotation;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import javax.lang.model.element.ElementKind;

/**
* This class contains utility methods to work with the javac AST.
*/
public class ASTHelpers {

  /**
   * Determines whether two expressions refer to the same variable. Note that returning false
   * doesn't necessarily mean the expressions do *not* refer to the same field. We don't attempt
   * to do any complex analysis here, just catch the obvious cases.
   */
  public static boolean sameVariable(ExpressionTree expr1, ExpressionTree expr2) {
    // Throw up our hands if we're not comparing identifiers and/or field accesses.
    if ((expr1.getKind() != Kind.IDENTIFIER && expr1.getKind() != Kind.MEMBER_SELECT) ||
        (expr2.getKind() != Kind.IDENTIFIER && expr2.getKind() != Kind.MEMBER_SELECT)) {
      return false;
    }

    Symbol sym1 = getSymbol(expr1);
    Symbol sym2 = getSymbol(expr2);
    if (sym1 == null) {
      throw new IllegalStateException("Couldn't get symbol for " + expr1);
    } else if (sym2 == null) {
      throw new IllegalStateException("Couldn't get symbol for " + expr2);
    }

    if (expr1.getKind() == Kind.IDENTIFIER && expr2.getKind() == Kind.IDENTIFIER) {
      // foo == foo?
      return sym1.equals(sym2);
    } else if (expr1.getKind() == Kind.MEMBER_SELECT && expr2.getKind() == Kind.MEMBER_SELECT) {
      // foo.baz.bar == foo.baz.bar?
      return sym1.equals(sym2) &&
          sameVariable(((JCFieldAccess) expr1).selected,((JCFieldAccess) expr2).selected);
    } else {
      // this.foo == foo?
      ExpressionTree selected = null;
      if (expr1.getKind() == Kind.IDENTIFIER) {
        selected = ((JCFieldAccess) expr2).selected;
      } else {
        selected = ((JCFieldAccess) expr1).selected;
      }
      // TODO(user): really shouldn't be relying on .toString()
      return selected.toString().equals("this") && sym1.equals(sym2);
    }
  }

  /**
   * Gets the symbol for a tree. Returns null if this tree does not have a symbol because it is
   * of the wrong type, or if {@code tree} is null.
   */
  // TODO(user): refactor other code that accesses symbols to use this method
  public static Symbol getSymbol(Tree tree) {
    if (tree instanceof ClassTree) {
      return getSymbol((ClassTree) tree);
    }
    if (tree instanceof MethodTree) {
      return getSymbol((MethodTree) tree);
    }
    if (tree instanceof VariableTree) {
      return getSymbol((VariableTree) tree);
    }
    if (tree instanceof JCFieldAccess) {
      return ((JCFieldAccess) tree).sym;
    }
    if (tree instanceof JCIdent) {
      return ((JCIdent) tree).sym;
    }
    if (tree instanceof JCMethodInvocation) {
      return ASTHelpers.getSymbol((MethodInvocationTree) tree);
    }
    if (tree instanceof JCNewClass) {
      return ((JCNewClass) tree).constructor;
    }
    if (tree instanceof AnnotationTree) {
      return getSymbol(((AnnotationTree) tree).getAnnotationType());
    }
    return null;
  }

  /** Gets the symbol for a class. */
  public static ClassSymbol getSymbol(ClassTree tree) {
    return ((JCClassDecl) tree).sym;
  }

  /** Gets the symbol for a method. */
  public static MethodSymbol getSymbol(MethodTree tree) {
    return ((JCMethodDecl) tree).sym;
  }

  /** Gets the symbol for a variable. */
  public static VarSymbol getSymbol(VariableTree tree) {
    return ((JCVariableDecl) tree).sym;
  }

  /** Gets the symbol for a method invocation. */
  public static MethodSymbol getSymbol(MethodInvocationTree tree) {
    Symbol sym = ASTHelpers.getSymbol(tree.getMethodSelect());
    if (!(sym instanceof MethodSymbol)) {
      // Defensive. Would only occur if there are errors in the AST.
      return null;
    }
    return (MethodSymbol) sym;
  }

  /**
   * Given a TreePath, walks up the tree until it finds a node of the given type and returns
   * the path from that node to the top-level node in the path (typically a
   * {@code CompilationUnitTree}).
   */
  public static <T> TreePath findPathFromEnclosingNodeToTopLevel(TreePath path,
        Class<T> klass) {
    while (path != null && !(klass.isInstance(path.getLeaf()))) {
      path = path.getParentPath();
    }
    return path;
  }

  /**
   * Given a TreePath, walks up the tree until it finds a node of the given type.
   */
  public static <T> T findEnclosingNode(TreePath path, Class<T> klass) {
    path = findPathFromEnclosingNodeToTopLevel(path, klass);
    return (path == null) ? null : klass.cast(path.getLeaf());
  }

  /**
   * Find the root assignable expression of a chain of field accesses.  If there is no root
   * (i.e, a bare method call or a static method call), return null.
   *
   * Examples:
   *    a.trim().intern() ==> a
   *    a.b.trim().intern() ==> a.b
   *    this.intValue.foo() ==> this.intValue
   *    this.foo() ==> this
   *    intern() ==> null
   *    String.format() ==> null
   *    java.lang.String.format() ==> null
   */
  public static ExpressionTree getRootAssignable(MethodInvocationTree methodInvocationTree) {
    if (!(methodInvocationTree instanceof JCMethodInvocation)) {
      throw new IllegalArgumentException("Expected type to be JCMethodInvocation, but was "
          + methodInvocationTree.getClass());
    }

    // Check for bare method call, e.g. intern().
    if (((JCMethodInvocation) methodInvocationTree).getMethodSelect() instanceof JCIdent) {
      return null;
    }

    // Unwrap the field accesses until you get to an identifier.
    ExpressionTree expr = methodInvocationTree;
    while (expr instanceof JCMethodInvocation) {
      expr = ((JCMethodInvocation) expr).getMethodSelect();
      if (expr instanceof JCFieldAccess) {
        expr = ((JCFieldAccess) expr).getExpression();
      }
    }

    // We only want assignable identifiers.
    Symbol sym = getSymbol(expr);
    if (sym instanceof VarSymbol) {
      return expr;
    }
    return null;
  }

  /**
   * Gives the return type of an ExpressionTree that represents a method select.
   *
   * TODO(user): Are there other places this could be used?
   */
  public static Type getReturnType(ExpressionTree expressionTree) {
    if (expressionTree instanceof JCFieldAccess) {
      JCFieldAccess methodCall = (JCFieldAccess) expressionTree;
      return methodCall.type.getReturnType();
    } else if (expressionTree instanceof JCIdent) {
      JCIdent methodCall = (JCIdent) expressionTree;
      return methodCall.type.getReturnType();
    }
    throw new IllegalArgumentException("Expected a JCFieldAccess or JCIdent");
  }

  /**
   * Returns the type of a receiver of a method call expression.
   * Precondition: the expressionTree corresponds to a method call.
   *
   * Examples:
   *    a.b.foo() ==> type of a.b
   *    a.bar().foo() ==> type of a.bar()
   *    this.foo() ==> type of this
   */
  public static Type getReceiverType(ExpressionTree expressionTree) {
    if (expressionTree instanceof JCFieldAccess) {
      JCFieldAccess methodSelectFieldAccess = (JCFieldAccess) expressionTree;
      return methodSelectFieldAccess.selected.type;
    } else if (expressionTree instanceof JCIdent) {
      JCIdent methodCall = (JCIdent) expressionTree;
      return methodCall.sym.owner.type;
    }
    throw new IllegalArgumentException(
        "Expected a JCFieldAccess or JCIdent from expression " + expressionTree);
  }

  /**
   * Returns the receiver of an expression.
   *
   * Examples:
   *    a.foo() ==> a
   *    a.b.foo() ==> a.b
   *    a.bar().foo() ==> a.bar()
   *    a.b.c ==> a.b
   *    a.b().c ==> a.b()
   */
  public static ExpressionTree getReceiver(ExpressionTree expressionTree) {
    if (expressionTree instanceof MethodInvocationTree) {
      return getReceiver(((MethodInvocationTree) expressionTree).getMethodSelect());
    } else if (expressionTree instanceof MemberSelectTree) {
      return ((MemberSelectTree) expressionTree).getExpression();
    } else {
      throw new IllegalStateException("Expected expression to be a method invocation or "
          + "field access, but was " + expressionTree.getKind());
    }
  }

  /**
   * Given a BinaryTree to match against and a list of two matchers, applies the matchers to the
   * operands in both orders.  If both matchers match, returns a list with the operand that
   * matched each matcher in the corresponding position.
   *
   * @param tree a BinaryTree AST node
   * @param matchers a list of matchers
   * @param state the VisitorState
   * @return a list of matched operands, or null if at least one did not match
   */
  public static List<ExpressionTree> matchBinaryTree(BinaryTree tree,
      List<Matcher<ExpressionTree>> matchers, VisitorState state) {
    ExpressionTree leftOperand = tree.getLeftOperand();
    ExpressionTree rightOperand = tree.getRightOperand();
    if (matchers.get(0).matches(leftOperand, state) &&
        matchers.get(1).matches(rightOperand, state)) {
      return Arrays.asList(leftOperand, rightOperand);
    } else if (matchers.get(0).matches(rightOperand, state) &&
        matchers.get(1).matches(leftOperand, state)) {
      return Arrays.asList(rightOperand, leftOperand);
    }
    return null;
  }

  /**
   * A collection of Java whitespace characters, as defined by JLS 3.6.
   */
  private static final CharMatcher WHITESPACE_CHARS = CharMatcher.anyOf(" \t\f\n\r");

  /**
   * Hacky fix for poor javac 6 literal parsing.  javac 6 doesn't set the AST node start
   * position correctly when a numeric literal is preceded by -. So we scan the source
   * backwards starting at the provided start position, looking for whitespace, until we find
   * the true start position.  javac 7 gets this right.
   *
   * @return The actual start position of the literal. May be the same as the start position
   * given by the tree node itself.
   */
  public static int getActualStartPosition(JCLiteral tree, CharSequence source) {
    // This only applies to negative numeric literals.
    Object value = tree.getValue();
    if ((value instanceof Number) && (((Number) value).doubleValue() < 0)) {
      int start = tree.getStartPosition() - 1;
      while (WHITESPACE_CHARS.matches(source.charAt(start))) {
        start--;
      }
      if (source.charAt(start) == '-') {
        return start;
      }
    }
    return tree.getStartPosition();
  }

  public static Set<MethodSymbol> findSuperMethods(MethodSymbol methodSymbol, Types types) {
    Set<MethodSymbol> supers = new HashSet<>();
    if (methodSymbol.isStatic()) {
      return supers;
    }

    TypeSymbol owner = (TypeSymbol) methodSymbol.owner;
    // Iterates over an ordered list of all super classes and interfaces.
    for (Type sup : types.closure(owner.type)) {
      if (sup == owner.type) {
        continue; // Skip the owner of the method
      }
      Scope scope = sup.tsym.members();
      for (Scope.Entry e = scope.lookup(methodSymbol.name); e.scope != null; e = e.next()) {
        if (e.sym != null
            && !e.sym.isStatic()
            && ((e.sym.flags() & Flags.SYNTHETIC) == 0)
            && e.sym.name.contentEquals(methodSymbol.name)
            && methodSymbol.overrides(e.sym, owner, types, true)) {
          supers.add((MethodSymbol) e.sym);
        }
      }
    }
    return supers;
  }

  /**
   * Find a method in the enclosing class's superclass that this method overrides.
   *
   * @return A superclass method that is overridden by {@code method}
   */
  public static MethodSymbol findSuperMethod(MethodSymbol method, Types types) {
    TypeSymbol superClass = method.enclClass().getSuperclass().tsym;
    if (superClass == null) {
      return null;
    }
    for (Symbol sym : superClass.members().getElements()) {
      if (sym.name.contentEquals(method.name)
          && method.overrides(sym, superClass, types, true)) {
        return (MethodSymbol) sym;
      }
    }
    return null;
  }

  /**
   * Check for the presence of an annotation, considering annotation inheritance.
   *
   * @return true if the symbol is annotated with given type.
   */
  public static boolean hasAnnotation(Symbol sym, Class<? extends Annotation> annotationType) {
    return getAnnotation(sym, annotationType) != null;
  }

  /**
   * Check for the presence of an annotation, considering annotation inheritance.
   *
   * @return true if the tree's symbol has an annotation of the given type.
   */
  public static boolean hasAnnotation(Tree tree, Class<? extends Annotation> annotationType) {
    return getAnnotation(tree, annotationType) != null;
  }

  /**
   * Retrieve an annotation, considering annotation inheritance.
   *
   * @return the annotation of given type on the tree's symbol, or null.
   */
  public static <T extends Annotation> T getAnnotation(Tree tree, Class<T> annotationType) {
    Symbol sym = getSymbol(tree);
    return sym == null ? null : getAnnotation(sym, annotationType);
  }

  /**
   * Retrieve an annotation, considering annotation inheritance.
   *
   * @return the annotation of given type on the symbol, or null.
   */
  // Symbol#getAnnotation is not intended for internal javac use, but because error-prone is run
  // after attribution it's safe to use here.
  @SuppressWarnings("deprecation")
  public static <T extends Annotation> T getAnnotation(Symbol sym, Class<T> annotationType) {
    return sym.getAnnotation(annotationType);
  }

  /** @return all values of the given enum type, in declaration order. */
  public static LinkedHashSet<String> enumValues(TypeSymbol enumType) {
    if (enumType.getKind() != ElementKind.ENUM) {
      throw new IllegalStateException();
    }
    Scope scope = enumType.members();
    Deque<String> values = new ArrayDeque<>();
    for (Scope.Entry e = scope.elems; e != null; e = e.sibling) {
      if (e.sym instanceof VarSymbol) {
        VarSymbol var = (VarSymbol) e.sym;
        if ((var.flags() & Flags.ENUM) != 0) {
          /**
           * Javac gives us the members backwards, apparently. It's worth making an effort to
           * preserve declaration order because it's useful for diagnostics (e.g. in
           * {@link MissingCasesInEnumSwitch}).
           */
          values.push(e.sym.name.toString());
        }
      }
    }
    return new LinkedHashSet<>(values);
  }

  /** Returns true if the given tree is a generated contructor. **/
  public static boolean isGeneratedConstructor(MethodTree tree) {
    if (!(tree instanceof JCMethodDecl)) {
        return false;
    }
    return (((JCMethodDecl) tree).mods.flags & Flags.GENERATEDCONSTR) != 0;
  }

  /**
   * Returns the {@code Type} for the given type {@code Tree} or {@code null} if the type could not
   * be determined. The input {@code Tree} typically comes from a method like
   * {@link VariableTree#getType()} or {@link MethodTree#getReturnType()}.
   */
  public static Type getType(Tree tree) {
    switch (tree.getKind()) {
      case ARRAY_TYPE:
        return ((JCArrayTypeTree) tree).type;
      case PRIMITIVE_TYPE:
        return ((JCPrimitiveTypeTree) tree).type;
      case PARAMETERIZED_TYPE:
        return ((JCTypeApply) tree).type;
      case IDENTIFIER:
        return ((JCIdent) tree).type;
      case MEMBER_SELECT:
        return ((JCFieldAccess) tree).sym.type;
      default:
        return null;
    }
  }

  public static String getAnnotationName(AnnotationTree tree) {
    Symbol sym = getSymbol(tree);
    return sym == null ? null : sym.name.toString();
  }
}
TOP

Related Classes of com.google.errorprone.util.ASTHelpers

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.