diff options
Diffstat (limited to 'third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/TreeUtils.java')
-rw-r--r-- | third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/TreeUtils.java | 967 |
1 files changed, 967 insertions, 0 deletions
diff --git a/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/TreeUtils.java b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/TreeUtils.java new file mode 100644 index 0000000000..5ceb5450dd --- /dev/null +++ b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/TreeUtils.java @@ -0,0 +1,967 @@ +package org.checkerframework.javacutil; + +/*>>> +import org.checkerframework.checker.nullness.qual.*; +*/ + +import java.util.EnumSet; +import java.util.Set; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.util.ElementFilter; + +import com.sun.source.tree.AnnotatedTypeTree; +import com.sun.source.tree.ArrayAccessTree; +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.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.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.TypeCastTree; +import com.sun.source.tree.VariableTree; +import com.sun.source.util.TreePath; +import com.sun.source.util.Trees; +import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.code.Symbol.MethodSymbol; +import com.sun.tools.javac.tree.JCTree; + +/** + * 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("<init>"); + } + + /** + * 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: + * <pre> + * field + * this.field + * + * method() + * this.method() + * </pre> + * + * 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<Tree.Kind> 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<Tree.Kind> 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 extends Tree> T enclosingOfClass(final TreePath path, final Class<T> 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 + * {@code {@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 + * {@code {@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 && parpath.getLeaf().getKind() != Tree.Kind.CLASS) { + 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 leaf is one of the following trees: + * <ul> + * <li>AssignmentTree </li> + * <li>CompoundAssignmentTree </li> + * <li>MethodInvocationTree</li> + * <li>NewArrayTree</li> + * <li>NewClassTree</li> + * <li>ReturnTree</li> + * <li>VariableTree</li> + * </ul> + * + * If the leaf is a ConditionalExpressionTree or ParenthesizedTree, then recur on the leaf. + * + * 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: + case CONDITIONAL_EXPRESSION: + 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 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) InternalUtils.symbol(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) InternalUtils.symbol(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) InternalUtils.symbol(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 + * Trees#getElement(TreePath)} instead. + * + * TODO: remove this method, as it really doesn't do anything. + * + * @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 InternalUtils.symbol(node); + } + + // Specialization for return type. + public static final ExecutableElement elementFromUse(MethodInvocationTree node) { + return (ExecutableElement) elementFromUse((ExpressionTree) node); + } + + // Specialization for return type. + public static final ExecutableElement elementFromUse(NewClassTree node) { + return (ExecutableElement) elementFromUse((ExpressionTree) node); + } + + + /** + * 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(InternalUtils.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(InternalUtils.typeOf(tree))); + } + + /** + * Returns true if the node is a constant-time expression. + * + * A tree is a constant-time expression if it is: + * <ol> + * <li>a literal tree + * <li>a reference to a final variable initialized with a compile time + * constant + * <li>a String concatenation of two compile time constants + * </ol> + */ + 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 final static Set<Tree.Kind> classTreeKinds = EnumSet.of( + Tree.Kind.CLASS, + Tree.Kind.ENUM, + Tree.Kind.INTERFACE, + Tree.Kind.ANNOTATION_TYPE + ); + + public static Set<Tree.Kind> 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 final static Set<Tree.Kind> 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<Tree.Kind> 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 isMethod(invoked, method, env); + } + + /** Returns true if the given element is, or overrides, method. */ + private static boolean isMethod(ExecutableElement questioned, ExecutableElement method, ProcessingEnvironment env) { + return (questioned.equals(method) + || env.getElementUtils().overrides(questioned, method, + (TypeElement)questioned.getEnclosingElement())); + } + + /** + * Returns the ExecutableElement for a method declaration of + * methodName, in class typeName, with params parameters. + * + * TODO: to precisely resolve method overloading, we should use parameter types and not just + * the number of parameters! + */ + public static ExecutableElement getMethod(String typeName, String methodName, int params, ProcessingEnvironment env) { + TypeElement mapElt = env.getElementUtils().getTypeElement(typeName); + for (ExecutableElement exec : ElementFilter.methodsIn(mapElt.getEnclosedElements())) { + if (exec.getSimpleName().contentEquals(methodName) + && exec.getParameters().size() == params) + return exec; + } + ErrorReporter.errorAbort("TreeUtils.getMethod: shouldn't be here!"); + return null; // dead code + } + + /** + * Determine whether the given expression is either "this" or an outer + * "C.this". + * + * <p> + * 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 + * + * <pre> + * <em>Object</em> . <em>class</em> + * </pre> + * + * @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 + * + * <pre> + * <em>f</em> + * <em>obj</em> . <em>f</em> + * </pre> + * + * @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 + * + * <pre> + * <em>m</em>(...) + * <em>obj</em> . <em>m</em>(...) + * </pre> + * + * @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 = "<init>".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().toString().equals("getClass"); + } +} |