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

org.checkerframework.javacutil.TreeUtils Maven / Gradle / Ivy

package org.checkerframework.javacutil;

import com.sun.source.tree.AnnotatedTypeTree;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ArrayAccessTree;
import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompoundAssignmentTree;
import com.sun.source.tree.ConditionalExpressionTree;
import com.sun.source.tree.ExpressionStatementTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
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.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.StatementTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.tree.TypeCastTree;
import com.sun.source.tree.TypeParameterTree;
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.Symbol;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Types;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCAnnotatedType;
import com.sun.tools.javac.tree.JCTree.JCAnnotation;
import com.sun.tools.javac.tree.JCTree.JCExpressionStatement;
import com.sun.tools.javac.tree.JCTree.JCMemberReference;
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
import com.sun.tools.javac.tree.JCTree.JCNewArray;
import com.sun.tools.javac.tree.JCTree.JCNewClass;
import com.sun.tools.javac.tree.JCTree.JCTypeParameter;
import com.sun.tools.javac.tree.TreeInfo;
import com.sun.tools.javac.util.Context;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;

/*>>>
import org.checkerframework.checker.nullness.qual.*;
*/

/** A utility class made for helping to analyze a given {@code Tree}. */
// TODO: This class needs significant restructuring
public final class TreeUtils {

    // Class cannot be instantiated.
    private TreeUtils() {
        throw new AssertionError("Class TreeUtils cannot be instantiated.");
    }

    /**
     * Checks if the provided method is a constructor method or no.
     *
     * @param tree a tree defining the method
     * @return true iff tree describes a constructor
     */
    public static boolean isConstructor(final MethodTree tree) {
        return tree.getName().contentEquals("");
    }

    /**
     * Checks if the method invocation is a call to super.
     *
     * @param tree a tree defining a method invocation
     * @return true iff tree describes a call to super
     */
    public static boolean isSuperCall(MethodInvocationTree tree) {
        return isNamedMethodCall("super", tree);
    }

    /**
     * Checks if the method invocation is a call to this.
     *
     * @param tree a tree defining a method invocation
     * @return true iff tree describes a call to this
     */
    public static boolean isThisCall(MethodInvocationTree tree) {
        return isNamedMethodCall("this", tree);
    }

    protected static boolean isNamedMethodCall(String name, MethodInvocationTree tree) {
        /*@Nullable*/ ExpressionTree mst = tree.getMethodSelect();
        assert mst != null; /*nninvariant*/

        if (mst.getKind() == Tree.Kind.IDENTIFIER) {
            return ((IdentifierTree) mst).getName().contentEquals(name);
        }

        if (mst.getKind() == Tree.Kind.MEMBER_SELECT) {
            MemberSelectTree selectTree = (MemberSelectTree) mst;

            if (selectTree.getExpression().getKind() != Tree.Kind.IDENTIFIER) {
                return false;
            }

            return ((IdentifierTree) selectTree.getExpression()).getName().contentEquals(name);
        }

        return false;
    }

    /**
     * Returns true if the tree is a tree that 'looks like' either an access of a field or an
     * invocation of a method that are owned by the same accessing instance.
     *
     * 

It would only return true if the access tree is of the form: * *

     *   field
     *   this.field
     *
     *   method()
     *   this.method()
     * 
* * It does not perform any semantical check to differentiate between fields and local variables; * local methods or imported static methods. * * @param tree expression tree representing an access to object member * @return {@code true} iff the member is a member of {@code this} instance */ public static boolean isSelfAccess(final ExpressionTree tree) { ExpressionTree tr = TreeUtils.skipParens(tree); // If method invocation check the method select if (tr.getKind() == Tree.Kind.ARRAY_ACCESS) { return false; } if (tree.getKind() == Tree.Kind.METHOD_INVOCATION) { tr = ((MethodInvocationTree) tree).getMethodSelect(); } tr = TreeUtils.skipParens(tr); if (tr.getKind() == Tree.Kind.TYPE_CAST) { tr = ((TypeCastTree) tr).getExpression(); } tr = TreeUtils.skipParens(tr); if (tr.getKind() == Tree.Kind.IDENTIFIER) { return true; } if (tr.getKind() == Tree.Kind.MEMBER_SELECT) { tr = ((MemberSelectTree) tr).getExpression(); if (tr.getKind() == Tree.Kind.IDENTIFIER) { Name ident = ((IdentifierTree) tr).getName(); return ident.contentEquals("this") || ident.contentEquals("super"); } } return false; } /** * Gets the first enclosing tree in path, of the specified kind. * * @param path the path defining the tree node * @param kind the kind of the desired tree * @return the enclosing tree of the given type as given by the path */ public static Tree enclosingOfKind(final TreePath path, final Tree.Kind kind) { return enclosingOfKind(path, EnumSet.of(kind)); } /** * Gets the first enclosing tree in path, with any one of the specified kinds. * * @param path the path defining the tree node * @param kinds the set of kinds of the desired tree * @return the enclosing tree of the given type as given by the path */ public static Tree enclosingOfKind(final TreePath path, final Set kinds) { TreePath p = path; while (p != null) { Tree leaf = p.getLeaf(); assert leaf != null; /*nninvariant*/ if (kinds.contains(leaf.getKind())) { return leaf; } p = p.getParentPath(); } return null; } /** * Gets path to the first enclosing class tree, where class is defined by the classTreeKinds * method. * * @param path the path defining the tree node * @return the path to the enclosing class tree */ public static TreePath pathTillClass(final TreePath path) { return pathTillOfKind(path, classTreeKinds()); } /** * Gets path to the first enclosing tree of the specified kind. * * @param path the path defining the tree node * @param kind the kind of the desired tree * @return the path to the enclosing tree of the given type */ public static TreePath pathTillOfKind(final TreePath path, final Tree.Kind kind) { return pathTillOfKind(path, EnumSet.of(kind)); } /** * Gets path to the first enclosing tree with any one of the specified kinds. * * @param path the path defining the tree node * @param kinds the set of kinds of the desired tree * @return the path to the enclosing tree of the given type */ public static TreePath pathTillOfKind(final TreePath path, final Set kinds) { TreePath p = path; while (p != null) { Tree leaf = p.getLeaf(); assert leaf != null; /*nninvariant*/ if (kinds.contains(leaf.getKind())) { return p; } p = p.getParentPath(); } return null; } /** * Gets the first enclosing tree in path, of the specified class * * @param path the path defining the tree node * @param treeClass the class of the desired tree * @return the enclosing tree of the given type as given by the path */ public static T enclosingOfClass( final TreePath path, final Class treeClass) { TreePath p = path; while (p != null) { Tree leaf = p.getLeaf(); if (treeClass.isInstance(leaf)) { return treeClass.cast(leaf); } p = p.getParentPath(); } return null; } /** * Gets the enclosing class of the tree node defined by the given {@link TreePath}. It returns a * {@link Tree}, from which {@code checkers.types.AnnotatedTypeMirror} or {@link Element} can be * obtained. * * @param path the path defining the tree node * @return the enclosing class (or interface) as given by the path, or null if one does not * exist */ public static /*@Nullable*/ ClassTree enclosingClass(final /*@Nullable*/ TreePath path) { return (ClassTree) enclosingOfKind(path, classTreeKinds()); } /** * Gets the enclosing variable of a tree node defined by the given {@link TreePath}. * * @param path the path defining the tree node * @return the enclosing variable as given by the path, or null if one does not exist */ public static VariableTree enclosingVariable(final TreePath path) { return (VariableTree) enclosingOfKind(path, Tree.Kind.VARIABLE); } /** * Gets the enclosing method of the tree node defined by the given {@link TreePath}. It returns * a {@link Tree}, from which an {@code checkers.types.AnnotatedTypeMirror} or {@link Element} * can be obtained. * * @param path the path defining the tree node * @return the enclosing method as given by the path, or null if one does not exist */ public static /*@Nullable*/ MethodTree enclosingMethod(final /*@Nullable*/ TreePath path) { return (MethodTree) enclosingOfKind(path, Tree.Kind.METHOD); } public static /*@Nullable*/ BlockTree enclosingTopLevelBlock(TreePath path) { TreePath parpath = path.getParentPath(); while (parpath != null && !classTreeKinds.contains(parpath.getLeaf().getKind())) { path = parpath; parpath = parpath.getParentPath(); } if (path.getLeaf().getKind() == Tree.Kind.BLOCK) { return (BlockTree) path.getLeaf(); } return null; } /** * If the given tree is a parenthesized tree, it returns the enclosed non-parenthesized tree. * Otherwise, it returns the same tree. * * @param tree an expression tree * @return the outermost non-parenthesized tree enclosed by the given tree */ public static ExpressionTree skipParens(final ExpressionTree tree) { ExpressionTree t = tree; while (t.getKind() == Tree.Kind.PARENTHESIZED) { t = ((ParenthesizedTree) t).getExpression(); } return t; } /** * Returns the tree with the assignment context for the treePath leaf node. (Does not handle * pseudo-assignment of an argument to a parameter or a receiver expression to a receiver.) * *

The assignment context for the {@code treePath} is the leaf of its parent, if the parent * is one of the following trees: * *

    *
  • AssignmentTree *
  • CompoundAssignmentTree *
  • MethodInvocationTree *
  • NewArrayTree *
  • NewClassTree *
  • ReturnTree *
  • VariableTree *
* * If the parent is a ConditionalExpressionTree we need to distinguish two cases: If the leaf is * either the then or else branch of the ConditionalExpressionTree, then recurse on the parent. * If the leaf is the condition of the ConditionalExpressionTree, then return null to not * consider this assignment context. * *

If the leaf is a ParenthesizedTree, then recurse on the parent. * *

Otherwise, null is returned. * * @return the assignment context as described */ public static Tree getAssignmentContext(final TreePath treePath) { TreePath parentPath = treePath.getParentPath(); if (parentPath == null) { return null; } Tree parent = parentPath.getLeaf(); switch (parent.getKind()) { case PARENTHESIZED: return getAssignmentContext(parentPath); case CONDITIONAL_EXPRESSION: ConditionalExpressionTree cet = (ConditionalExpressionTree) parent; if (cet.getCondition() == treePath.getLeaf()) { // The assignment context for the condition is simply boolean. // No point in going on. return null; } // Otherwise use the context of the ConditionalExpressionTree. return getAssignmentContext(parentPath); case ASSIGNMENT: case METHOD_INVOCATION: case NEW_ARRAY: case NEW_CLASS: case RETURN: case VARIABLE: return parent; default: // 11 Tree.Kinds are CompoundAssignmentTrees, // so use instanceof rather than listing all 11. if (parent instanceof CompoundAssignmentTree) { return parent; } return null; } } /** * Gets the {@link Element} for the given Tree API node. For an object instantiation returns the * value of the {@link JCNewClass#constructor} field. Note that this result might differ from * the result of {@link TreeUtils#constructor(NewClassTree)}. * * @param tree the {@link Tree} node to get the symbol for * @throws IllegalArgumentException if {@code tree} is null or is not a valid javac-internal * tree (JCTree) * @return the {@link Symbol} for the given tree, or null if one could not be found */ public static /*@Nullable*/ Element elementFromTree(Tree tree) { if (tree == null) { ErrorReporter.errorAbort("InternalUtils.symbol: tree is null"); return null; // dead code } if (!(tree instanceof JCTree)) { ErrorReporter.errorAbort("InternalUtils.symbol: tree is not a valid Javac tree"); return null; // dead code } if (isExpressionTree(tree)) { tree = skipParens((ExpressionTree) tree); } switch (tree.getKind()) { case VARIABLE: case METHOD: case CLASS: case ENUM: case INTERFACE: case ANNOTATION_TYPE: case TYPE_PARAMETER: return TreeInfo.symbolFor((JCTree) tree); // symbol() only works on MethodSelects, so we need to get it manually // for method invocations. case METHOD_INVOCATION: return TreeInfo.symbol(((JCMethodInvocation) tree).getMethodSelect()); case ASSIGNMENT: return TreeInfo.symbol((JCTree) ((AssignmentTree) tree).getVariable()); case ARRAY_ACCESS: return elementFromTree(((ArrayAccessTree) tree).getExpression()); case NEW_CLASS: return ((JCNewClass) tree).constructor; case MEMBER_REFERENCE: // TreeInfo.symbol, which is used in the default case, didn't handle // member references until JDK8u20. So handle it here. return ((JCMemberReference) tree).sym; default: return TreeInfo.symbol((JCTree) tree); } } /** * Gets the element for a class corresponding to a declaration. * * @return the element for the given class */ public static final TypeElement elementFromDeclaration(ClassTree node) { TypeElement elt = (TypeElement) TreeUtils.elementFromTree(node); return elt; } /** * Gets the element for a method corresponding to a declaration. * * @return the element for the given method */ public static final ExecutableElement elementFromDeclaration(MethodTree node) { ExecutableElement elt = (ExecutableElement) TreeUtils.elementFromTree(node); return elt; } /** * Gets the element for a variable corresponding to its declaration. * * @return the element for the given variable */ public static final VariableElement elementFromDeclaration(VariableTree node) { VariableElement elt = (VariableElement) TreeUtils.elementFromTree(node); return elt; } /** * Gets the element for the declaration corresponding to this use of an element. To get the * element for a declaration, use {@link #elementFromDeclaration(ClassTree)}, {@link * #elementFromDeclaration(MethodTree)}, or {@link #elementFromDeclaration(VariableTree)} * instead. * *

This method is just a wrapper around {@link TreeUtils#elementFromTree(Tree)}, but this * class might be the first place someone looks for this functionality. * * @param node the tree corresponding to a use of an element * @return the element for the corresponding declaration */ public static final Element elementFromUse(ExpressionTree node) { return TreeUtils.elementFromTree(node); } // Specialization for return type. // Might return null if element wasn't found. public static final ExecutableElement elementFromUse(MethodInvocationTree node) { Element el = TreeUtils.elementFromTree(node); if (el instanceof ExecutableElement) { return (ExecutableElement) el; } else { return null; } } /** * Specialization for return type. Might return null if element wasn't found. * * @see #constructor(NewClassTree) */ public static final ExecutableElement elementFromUse(NewClassTree node) { Element el = TreeUtils.elementFromTree(node); if (el instanceof ExecutableElement) { return (ExecutableElement) el; } else { return null; } } /** * Determines the symbol for a constructor given an invocation via {@code new}. * *

If the tree is a declaration of an anonymous class, then method returns constructor that * gets invoked in the extended class, rather than the anonymous constructor implicitly added by * the constructor (JLS 15.9.5.1) * * @see #elementFromUse(NewClassTree) * @param tree the constructor invocation * @return the {@link ExecutableElement} corresponding to the constructor call in {@code tree} */ public static ExecutableElement constructor(NewClassTree tree) { if (!(tree instanceof JCTree.JCNewClass)) { ErrorReporter.errorAbort("InternalUtils.constructor: not a javac internal tree"); return null; // dead code } JCNewClass newClassTree = (JCNewClass) tree; if (tree.getClassBody() != null) { // anonymous constructor bodies should contain exactly one statement // in the form: // super(arg1, ...) // or // o.super(arg1, ...) // // which is a method invocation (!) to the actual constructor // the method call is guaranteed to return nonnull JCMethodDecl anonConstructor = (JCMethodDecl) TreeInfo.declarationFor(newClassTree.constructor, newClassTree); assert anonConstructor != null; assert anonConstructor.body.stats.size() == 1; JCExpressionStatement stmt = (JCExpressionStatement) anonConstructor.body.stats.head; JCTree.JCMethodInvocation superInvok = (JCMethodInvocation) stmt.expr; return (ExecutableElement) TreeInfo.symbol(superInvok.meth); } else { Element e = newClassTree.constructor; return (ExecutableElement) e; } } /** * Determine whether the given ExpressionTree has an underlying element. * * @param node the ExpressionTree to test * @return whether the tree refers to an identifier, member select, or method invocation */ public static final boolean isUseOfElement(ExpressionTree node) { node = TreeUtils.skipParens(node); switch (node.getKind()) { case IDENTIFIER: case MEMBER_SELECT: case METHOD_INVOCATION: case NEW_CLASS: return true; default: return false; } } /** @return the name of the invoked method */ public static final Name methodName(MethodInvocationTree node) { ExpressionTree expr = node.getMethodSelect(); if (expr.getKind() == Tree.Kind.IDENTIFIER) { return ((IdentifierTree) expr).getName(); } else if (expr.getKind() == Tree.Kind.MEMBER_SELECT) { return ((MemberSelectTree) expr).getIdentifier(); } ErrorReporter.errorAbort("TreeUtils.methodName: cannot be here: " + node); return null; // dead code } /** * @return true if the first statement in the body is a self constructor invocation within a * constructor */ public static final boolean containsThisConstructorInvocation(MethodTree node) { if (!TreeUtils.isConstructor(node) || node.getBody().getStatements().isEmpty()) return false; StatementTree st = node.getBody().getStatements().get(0); if (!(st instanceof ExpressionStatementTree) || !(((ExpressionStatementTree) st).getExpression() instanceof MethodInvocationTree)) { return false; } MethodInvocationTree invocation = (MethodInvocationTree) ((ExpressionStatementTree) st).getExpression(); return "this".contentEquals(TreeUtils.methodName(invocation)); } public static final Tree firstStatement(Tree tree) { Tree first; if (tree.getKind() == Tree.Kind.BLOCK) { BlockTree block = (BlockTree) tree; if (block.getStatements().isEmpty()) { first = block; } else { first = block.getStatements().iterator().next(); } } else { first = tree; } return first; } /** * Determine whether the given class contains an explicit constructor. * * @param node a class tree * @return true, iff there is an explicit constructor */ public static boolean hasExplicitConstructor(ClassTree node) { TypeElement elem = TreeUtils.elementFromDeclaration(node); for (ExecutableElement ee : ElementFilter.constructorsIn(elem.getEnclosedElements())) { MethodSymbol ms = (MethodSymbol) ee; long mod = ms.flags(); if ((mod & Flags.SYNTHETIC) == 0) { return true; } } return false; } /** * Returns true if the tree is of a diamond type. In contrast to the implementation in TreeInfo, * this version works on Trees. * * @see com.sun.tools.javac.tree.TreeInfo#isDiamond(JCTree) */ public static final boolean isDiamondTree(Tree tree) { switch (tree.getKind()) { case ANNOTATED_TYPE: return isDiamondTree(((AnnotatedTypeTree) tree).getUnderlyingType()); case PARAMETERIZED_TYPE: return ((ParameterizedTypeTree) tree).getTypeArguments().isEmpty(); case NEW_CLASS: return isDiamondTree(((NewClassTree) tree).getIdentifier()); default: return false; } } /** Returns true if the tree represents a {@code String} concatenation operation */ public static final boolean isStringConcatenation(Tree tree) { return (tree.getKind() == Tree.Kind.PLUS && TypesUtils.isString(TreeUtils.typeOf(tree))); } /** Returns true if the compound assignment tree is a string concatenation */ public static final boolean isStringCompoundConcatenation(CompoundAssignmentTree tree) { return (tree.getKind() == Tree.Kind.PLUS_ASSIGNMENT && TypesUtils.isString(TreeUtils.typeOf(tree))); } /** * Returns true if the node is a constant-time expression. * *

A tree is a constant-time expression if it is: * *

    *
  1. a literal tree *
  2. a reference to a final variable initialized with a compile time constant *
  3. a String concatenation of two compile time constants *
*/ public static boolean isCompileTimeString(ExpressionTree node) { ExpressionTree tree = TreeUtils.skipParens(node); if (tree instanceof LiteralTree) { return true; } if (TreeUtils.isUseOfElement(tree)) { Element elt = TreeUtils.elementFromUse(tree); return ElementUtils.isCompileTimeConstant(elt); } else if (TreeUtils.isStringConcatenation(tree)) { BinaryTree binOp = (BinaryTree) tree; return isCompileTimeString(binOp.getLeftOperand()) && isCompileTimeString(binOp.getRightOperand()); } else { return false; } } /** Returns the receiver tree of a field access or a method invocation */ public static ExpressionTree getReceiverTree(ExpressionTree expression) { ExpressionTree receiver = TreeUtils.skipParens(expression); if (!(receiver.getKind() == Tree.Kind.METHOD_INVOCATION || receiver.getKind() == Tree.Kind.MEMBER_SELECT || receiver.getKind() == Tree.Kind.IDENTIFIER || receiver.getKind() == Tree.Kind.ARRAY_ACCESS)) { // No receiver tree for anything but these four kinds. return null; } if (receiver.getKind() == Tree.Kind.METHOD_INVOCATION) { // Trying to handle receiver calls to trees of the form // ((m).getArray()) // returns the type of 'm' in this case receiver = ((MethodInvocationTree) receiver).getMethodSelect(); if (receiver.getKind() == Tree.Kind.IDENTIFIER) { // It's a method call "m(foo)" without an explicit receiver return null; } else if (receiver.getKind() == Tree.Kind.MEMBER_SELECT) { receiver = ((MemberSelectTree) receiver).getExpression(); } else { // Otherwise, e.g. a NEW_CLASS: nothing to do. } } else if (receiver.getKind() == Tree.Kind.IDENTIFIER) { // It's a field access on implicit this or a local variable/parameter. return null; } else if (receiver.getKind() == Tree.Kind.ARRAY_ACCESS) { return TreeUtils.skipParens(((ArrayAccessTree) receiver).getExpression()); } else if (receiver.getKind() == Tree.Kind.MEMBER_SELECT) { receiver = ((MemberSelectTree) receiver).getExpression(); // Avoid int.class if (receiver instanceof PrimitiveTypeTree) { return null; } } // Receiver is now really just the receiver tree. return TreeUtils.skipParens(receiver); } // TODO: What about anonymous classes? // Adding Tree.Kind.NEW_CLASS here doesn't work, because then a // tree gets cast to ClassTree when it is actually a NewClassTree, // for example in enclosingClass above. private static final Set classTreeKinds = EnumSet.of( Tree.Kind.CLASS, Tree.Kind.ENUM, Tree.Kind.INTERFACE, Tree.Kind.ANNOTATION_TYPE); public static Set classTreeKinds() { return classTreeKinds; } /** * Is the given tree kind a class, i.e. a class, enum, interface, or annotation type. * * @param tree the tree to test * @return true, iff the given kind is a class kind */ public static boolean isClassTree(Tree tree) { return classTreeKinds().contains(tree.getKind()); } private static final Set typeTreeKinds = EnumSet.of( Tree.Kind.PRIMITIVE_TYPE, Tree.Kind.PARAMETERIZED_TYPE, Tree.Kind.TYPE_PARAMETER, Tree.Kind.ARRAY_TYPE, Tree.Kind.UNBOUNDED_WILDCARD, Tree.Kind.EXTENDS_WILDCARD, Tree.Kind.SUPER_WILDCARD, Tree.Kind.ANNOTATED_TYPE); public static Set typeTreeKinds() { return typeTreeKinds; } /** * Is the given tree a type instantiation? * *

TODO: this is an under-approximation: e.g. an identifier could be either a type use or an * expression. How can we distinguish. * * @param tree the tree to test * @return true, iff the given tree is a type */ public static boolean isTypeTree(Tree tree) { return typeTreeKinds().contains(tree.getKind()); } /** * Returns true if the given element is an invocation of the method, or of any method that * overrides that one. */ public static boolean isMethodInvocation( Tree tree, ExecutableElement method, ProcessingEnvironment env) { if (!(tree instanceof MethodInvocationTree)) { return false; } MethodInvocationTree methInvok = (MethodInvocationTree) tree; ExecutableElement invoked = TreeUtils.elementFromUse(methInvok); return ElementUtils.isMethod(invoked, method, env); } /** * Returns the ExecutableElement for the method declaration of methodName, in class typeName, * with params formal parameters. Errs if there is not exactly one matching method. If more than * one method takes the same number of formal parameters, then use {@link #getMethod(String, * String, ProcessingEnvironment, String...)}. */ public static ExecutableElement getMethod( String typeName, String methodName, int params, ProcessingEnvironment env) { List methods = getMethodList(typeName, methodName, params, env); if (methods.size() == 1) { return methods.get(0); } ErrorReporter.errorAbort("TreeUtils.getMethod: expected 1 match, found " + methods.size()); return null; // dead code } /** * Returns all ExecutableElements for method declarations of methodName, in class typeName, with * params formal parameters. */ public static List getMethodList( String typeName, String methodName, int params, ProcessingEnvironment env) { List methods = new ArrayList<>(1); TypeElement typeElt = env.getElementUtils().getTypeElement(typeName); for (ExecutableElement exec : ElementFilter.methodsIn(typeElt.getEnclosedElements())) { if (exec.getSimpleName().contentEquals(methodName) && exec.getParameters().size() == params) { methods.add(exec); } } return methods; } /** * Returns the ExecutableElement for a method declaration of methodName, in class typeName, with * formal parameters of the given types. Errs if there is no matching method. */ public static ExecutableElement getMethod( String typeName, String methodName, ProcessingEnvironment env, String... paramTypes) { TypeElement typeElt = env.getElementUtils().getTypeElement(typeName); for (ExecutableElement exec : ElementFilter.methodsIn(typeElt.getEnclosedElements())) { if (exec.getSimpleName().contentEquals(methodName) && exec.getParameters().size() == paramTypes.length) { boolean typesMatch = true; List params = exec.getParameters(); for (int i = 0; i < paramTypes.length; i++) { VariableElement ve = params.get(i); TypeMirror tm = ve.asType(); if (!tm.toString().equals(paramTypes[i])) { typesMatch = false; break; } } if (typesMatch) { return exec; } } } ErrorReporter.errorAbort( "TreeUtils.getMethod: found no match for " + typeName + "." + methodName + "(" + Arrays.toString(paramTypes)); return null; // dead code } /** * Determine whether the given expression is either "this" or an outer "C.this". * *

TODO: Should this also handle "super"? */ public static final boolean isExplicitThisDereference(ExpressionTree tree) { if (tree.getKind() == Tree.Kind.IDENTIFIER && ((IdentifierTree) tree).getName().contentEquals("this")) { // Explicit this reference "this" return true; } if (tree.getKind() != Tree.Kind.MEMBER_SELECT) { return false; } MemberSelectTree memSelTree = (MemberSelectTree) tree; if (memSelTree.getIdentifier().contentEquals("this")) { // Outer this reference "C.this" return true; } return false; } /** * Determine whether {@code tree} is a class literal, such as * *

     *   Object . class
     * 
* * @return true iff if tree is a class literal */ public static boolean isClassLiteral(Tree tree) { if (tree.getKind() != Tree.Kind.MEMBER_SELECT) { return false; } return "class".equals(((MemberSelectTree) tree).getIdentifier().toString()); } /** * Determine whether {@code tree} is a field access expressions, such as * *
     *   f
     *   obj . f
     * 
* * @return true iff if tree is a field access expression (implicit or explicit) */ public static boolean isFieldAccess(Tree tree) { if (tree.getKind().equals(Tree.Kind.MEMBER_SELECT)) { // explicit field access MemberSelectTree memberSelect = (MemberSelectTree) tree; Element el = TreeUtils.elementFromUse(memberSelect); return el.getKind().isField(); } else if (tree.getKind().equals(Tree.Kind.IDENTIFIER)) { // implicit field access IdentifierTree ident = (IdentifierTree) tree; Element el = TreeUtils.elementFromUse(ident); return el.getKind().isField() && !ident.getName().contentEquals("this") && !ident.getName().contentEquals("super"); } return false; } /** * Compute the name of the field that the field access {@code tree} accesses. Requires {@code * tree} to be a field access, as determined by {@code isFieldAccess}. * * @return the name of the field accessed by {@code tree}. */ public static String getFieldName(Tree tree) { assert isFieldAccess(tree); if (tree.getKind().equals(Tree.Kind.MEMBER_SELECT)) { MemberSelectTree mtree = (MemberSelectTree) tree; return mtree.getIdentifier().toString(); } else { IdentifierTree itree = (IdentifierTree) tree; return itree.getName().toString(); } } /** * Determine whether {@code tree} refers to a method element, such as * *
     *   m(...)
     *   obj . m(...)
     * 
* * @return true iff if tree is a method access expression (implicit or explicit) */ public static boolean isMethodAccess(Tree tree) { if (tree.getKind().equals(Tree.Kind.MEMBER_SELECT)) { // explicit method access MemberSelectTree memberSelect = (MemberSelectTree) tree; Element el = TreeUtils.elementFromUse(memberSelect); return el.getKind() == ElementKind.METHOD || el.getKind() == ElementKind.CONSTRUCTOR; } else if (tree.getKind().equals(Tree.Kind.IDENTIFIER)) { // implicit method access IdentifierTree ident = (IdentifierTree) tree; // The field "super" and "this" are also legal methods if (ident.getName().contentEquals("super") || ident.getName().contentEquals("this")) { return true; } Element el = TreeUtils.elementFromUse(ident); return el.getKind() == ElementKind.METHOD || el.getKind() == ElementKind.CONSTRUCTOR; } return false; } /** * Compute the name of the method that the method access {@code tree} accesses. Requires {@code * tree} to be a method access, as determined by {@code isMethodAccess}. * * @return the name of the method accessed by {@code tree}. */ public static String getMethodName(Tree tree) { assert isMethodAccess(tree); if (tree.getKind().equals(Tree.Kind.MEMBER_SELECT)) { MemberSelectTree mtree = (MemberSelectTree) tree; return mtree.getIdentifier().toString(); } else { IdentifierTree itree = (IdentifierTree) tree; return itree.getName().toString(); } } /** * @return {@code true} if and only if {@code tree} can have a type annotation. *

TODO: is this implementation precise enough? E.g. does a .class literal work * correctly? */ public static boolean canHaveTypeAnnotation(Tree tree) { return ((JCTree) tree).type != null; } /** * Returns true if and only if the given {@code tree} represents a field access of the given * {@link VariableElement}. */ public static boolean isSpecificFieldAccess(Tree tree, VariableElement var) { if (tree instanceof MemberSelectTree) { MemberSelectTree memSel = (MemberSelectTree) tree; Element field = TreeUtils.elementFromUse(memSel); return field.equals(var); } else if (tree instanceof IdentifierTree) { IdentifierTree idTree = (IdentifierTree) tree; Element field = TreeUtils.elementFromUse(idTree); return field.equals(var); } else { return false; } } /** * Returns the VariableElement for a field declaration. * * @param typeName the class where the field is declared * @param fieldName the name of the field * @param env the processing environment * @return the VariableElement for typeName.fieldName */ public static VariableElement getField( String typeName, String fieldName, ProcessingEnvironment env) { TypeElement mapElt = env.getElementUtils().getTypeElement(typeName); for (VariableElement var : ElementFilter.fieldsIn(mapElt.getEnclosedElements())) { if (var.getSimpleName().contentEquals(fieldName)) { return var; } } ErrorReporter.errorAbort("TreeUtils.getField: shouldn't be here!"); return null; // dead code } /** * Determine whether the given tree represents an ExpressionTree. * *

TODO: is there a nicer way than an instanceof? * * @param tree the Tree to test * @return whether the tree is an ExpressionTree */ public static boolean isExpressionTree(Tree tree) { return tree instanceof ExpressionTree; } /** * @param node the method invocation to check * @return true if this is a super call to the {@link Enum} constructor */ public static boolean isEnumSuper(MethodInvocationTree node) { ExecutableElement ex = TreeUtils.elementFromUse(node); Name name = ElementUtils.getQualifiedClassName(ex); boolean correctClass = "java.lang.Enum".contentEquals(name); boolean correctMethod = "".contentEquals(ex.getSimpleName()); return correctClass && correctMethod; } /** * Determine whether the given tree represents a declaration of a type (including type * parameters). * * @param node the Tree to test * @return true if the tree is a type declaration */ public static boolean isTypeDeclaration(Tree node) { switch (node.getKind()) { // These tree kinds are always declarations. Uses of the declared // types have tree kind IDENTIFIER. case ANNOTATION_TYPE: case CLASS: case ENUM: case INTERFACE: case TYPE_PARAMETER: return true; default: return false; } } /** * @see Object#getClass() * @return true iff invocationTree is an instance of getClass() */ public static boolean isGetClassInvocation(MethodInvocationTree invocationTree) { final Element declarationElement = elementFromUse(invocationTree); String ownerName = ElementUtils.getQualifiedClassName(declarationElement.getEnclosingElement()) .toString(); return ownerName.equals("java.lang.Object") && declarationElement.getSimpleName().contentEquals("getClass"); } /** * Returns whether or not the leaf of the tree path is in a static scope. * * @param path TreePath whose leaf may or may not be in static scope * @return returns whether or not the leaf of the tree path is in a static scope */ public static boolean isTreeInStaticScope(TreePath path) { MethodTree enclosingMethod = TreeUtils.enclosingMethod(path); if (enclosingMethod != null) { return enclosingMethod.getModifiers().getFlags().contains(Modifier.STATIC); } // no enclosing method, check for static or initializer block BlockTree block = enclosingTopLevelBlock(path); if (block != null) { return block.isStatic(); } // check if its in a variable initializer Tree t = enclosingVariable(path); if (t != null) { return ((VariableTree) t).getModifiers().getFlags().contains((Modifier.STATIC)); } ClassTree classTree = enclosingClass(path); if (classTree != null) { return classTree.getModifiers().getFlags().contains((Modifier.STATIC)); } return false; } /** * Returns whether or not tree is an access of array length. * * @param tree tree to check * @return true if tree is an access of array length */ public static boolean isArrayLengthAccess(Tree tree) { if (tree.getKind() == Kind.MEMBER_SELECT && isFieldAccess(tree) && getFieldName(tree).equals("length")) { ExpressionTree expressionTree = ((MemberSelectTree) tree).getExpression(); if (TreeUtils.typeOf(expressionTree).getKind() == TypeKind.ARRAY) { return true; } } return false; } /** * Determines whether or not the node referred to by the given {@link TreePath} is an anonymous * constructor (the constructor for an anonymous class. * * @param method the {@link TreePath} for a node that may be an anonymous constructor * @return true if the given path points to an anonymous constructor, false if it does not */ public static boolean isAnonymousConstructor(final MethodTree method) { /*@Nullable*/ Element e = elementFromTree(method); if (e == null || !(e instanceof Symbol)) { return false; } if ((((/*@NonNull*/ Symbol) e).flags() & Flags.ANONCONSTR) != 0) { return true; } return false; } public static final List annotationsFromTypeAnnotationTrees( List annos) { List annotations = new ArrayList(annos.size()); for (AnnotationTree anno : annos) { annotations.add(TreeUtils.annotationFromAnnotationTree(anno)); } return annotations; } public static AnnotationMirror annotationFromAnnotationTree(AnnotationTree tree) { return ((JCAnnotation) tree).attribute; } public static final List annotationsFromTree( AnnotatedTypeTree node) { return annotationsFromTypeAnnotationTrees(((JCAnnotatedType) node).annotations); } public static final List annotationsFromTree( TypeParameterTree node) { return annotationsFromTypeAnnotationTrees(((JCTypeParameter) node).annotations); } public static final List annotationsFromArrayCreation( NewArrayTree node, int level) { assert node instanceof JCNewArray; final JCNewArray newArray = ((JCNewArray) node); if (level == -1) { return annotationsFromTypeAnnotationTrees(newArray.annotations); } if (newArray.dimAnnotations.length() > 0 && (level >= 0) && (level < newArray.dimAnnotations.size())) return annotationsFromTypeAnnotationTrees(newArray.dimAnnotations.get(level)); return Collections.emptyList(); } public static TypeMirror typeOf(Tree tree) { return ((JCTree) tree).type; } /** * The type of the lambda or method reference tree is a functional interface type. This method * returns the single abstract method declared by that functional interface. (The type of this * method is referred to as the function type.) * * @param tree lambda or member reference tree * @param env ProcessingEnvironment * @return the single abstract method declared by the type of the tree */ public static Symbol findFunction(Tree tree, ProcessingEnvironment env) { Context ctx = ((JavacProcessingEnvironment) env).getContext(); Types javacTypes = Types.instance(ctx); return javacTypes.findDescriptorSymbol(((Type) typeOf(tree)).asElement()); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy