blob: fb4b4eb14a64b69359dd3871c94ee831f6db31d7 [file] [log] [blame]
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.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.TreeVisitor;
import com.sun.source.tree.TypeCastTree;
import com.sun.source.tree.TypeParameterTree;
import com.sun.source.tree.UnionTypeTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.SimpleTreeVisitor;
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.TypeTag;
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.JCBinary;
import com.sun.tools.javac.tree.JCTree.JCExpression;
import com.sun.tools.javac.tree.JCTree.JCExpressionStatement;
import com.sun.tools.javac.tree.JCTree.JCLambda;
import com.sun.tools.javac.tree.JCTree.JCLambda.ParameterKind;
import com.sun.tools.javac.tree.JCTree.JCLiteral;
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.tree.TreeMaker;
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 java.util.StringJoiner;
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.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.interning.qual.PolyInterned;
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.signature.qual.FullyQualifiedName;
import org.checkerframework.dataflow.qual.Pure;
import org.plumelib.util.CollectionsPlume;
import org.plumelib.util.UniqueIdMap;
/**
* Utility methods for analyzing a javac {@code Tree}.
*
* @see TreePathUtil
*/
public final class TreeUtils {
// Class cannot be instantiated.
private TreeUtils() {
throw new AssertionError("Class TreeUtils cannot be instantiated.");
}
/** Unique IDs for trees. */
public static final UniqueIdMap<Tree> treeUids = new UniqueIdMap<>();
/**
* 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 isSuperConstructorCall(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 isThisConstructorCall(MethodInvocationTree tree) {
return isNamedMethodCall("this", tree);
}
/**
* Checks if the method call is a call to the given method name.
*
* @param name a method name
* @param tree a tree defining a method invocation
* @return true iff tree is a call to the given method
*/
private static boolean isNamedMethodCall(String name, MethodInvocationTree tree) {
return getMethodName(tree.getMethodSelect()).contentEquals(name);
}
/**
* 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.
*
* <p>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.withoutParens(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.withoutParens(tr);
if (tr.getKind() == Tree.Kind.TYPE_CAST) {
tr = ((TypeCastTree) tr).getExpression();
}
tr = TreeUtils.withoutParens(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;
}
/**
* If the given tree is a parenthesized tree, return the enclosed non-parenthesized tree.
* Otherwise, return the same tree.
*
* @param tree an expression tree
* @return the outermost non-parenthesized tree enclosed by the given tree
*/
@SuppressWarnings("interning:return") // polymorphism implementation
public static @PolyInterned ExpressionTree withoutParens(
final @PolyInterned ExpressionTree tree) {
ExpressionTree t = tree;
while (t.getKind() == Tree.Kind.PARENTHESIZED) {
t = ((ParenthesizedTree) t).getExpression();
}
return t;
}
/**
* 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
*/
@Pure
public static @Nullable Element elementFromTree(Tree tree) {
if (tree == null) {
throw new BugInCF("InternalUtils.symbol: tree is null");
}
if (!(tree instanceof JCTree)) {
throw new BugInCF("InternalUtils.symbol: tree is not a valid Javac tree");
}
if (isExpressionTree(tree)) {
tree = withoutParens((ExpressionTree) tree);
}
switch (tree.getKind()) {
// 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:
if (isTypeDeclaration(tree)
|| tree.getKind() == Tree.Kind.VARIABLE
|| tree.getKind() == Tree.Kind.METHOD) {
return TreeInfo.symbolFor((JCTree) tree);
}
return TreeInfo.symbol((JCTree) tree);
}
}
/**
* Gets the element for a class corresponding to a declaration.
*
* @param node class declaration
* @return the element for the given class
*/
public static TypeElement elementFromDeclaration(ClassTree node) {
TypeElement elt = (TypeElement) TreeUtils.elementFromTree(node);
assert elt != null : "@AssumeAssertion(nullness): tree kind";
return elt;
}
/**
* Gets the element for a method corresponding to a declaration.
*
* @return the element for the given method
*/
public static ExecutableElement elementFromDeclaration(MethodTree node) {
ExecutableElement elt = (ExecutableElement) TreeUtils.elementFromTree(node);
assert elt != null : "@AssumeAssertion(nullness): tree kind";
return elt;
}
/**
* Gets the element for a variable corresponding to its declaration.
*
* @return the element for the given variable
*/
public static VariableElement elementFromDeclaration(VariableTree node) {
VariableElement elt = (VariableElement) TreeUtils.elementFromTree(node);
assert elt != null : "@AssumeAssertion(nullness): tree kind";
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.
*
* <p>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, {@code null} otherwise
*/
@Pure
public static @Nullable Element elementFromUse(ExpressionTree node) {
return TreeUtils.elementFromTree(node);
}
/**
* Returns the ExecutableElement for the called method, from a call.
*
* @param node a method call
* @return the ExecutableElement for the called method
*/
@Pure
public static ExecutableElement elementFromUse(MethodInvocationTree node) {
Element el = TreeUtils.elementFromTree(node);
if (!(el instanceof ExecutableElement)) {
throw new BugInCF("Method elements should be ExecutableElement. Found: %s", el);
}
return (ExecutableElement) el;
}
/**
* Gets the ExecutableElement for the called constructor, from a constructor invocation.
*
* @param node a constructor invocation
* @return the ExecutableElement for the called constructor
* @see #constructor(NewClassTree)
*/
@Pure
public static ExecutableElement elementFromUse(NewClassTree node) {
Element el = TreeUtils.elementFromTree(node);
if (!(el instanceof ExecutableElement)) {
throw new BugInCF("Constructor elements should be ExecutableElement. Found: %s", el);
}
return (ExecutableElement) el;
}
/**
* Determines the symbol for a constructor given an invocation via {@code new}.
*
* <p>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)) {
throw new BugInCF("InternalUtils.constructor: not a javac internal tree");
}
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
*/
@EnsuresNonNullIf(result = true, expression = "elementFromUse(#1)")
@Pure
public static boolean isUseOfElement(ExpressionTree node) {
ExpressionTree realnode = TreeUtils.withoutParens(node);
switch (realnode.getKind()) {
case IDENTIFIER:
case MEMBER_SELECT:
case METHOD_INVOCATION:
case NEW_CLASS:
assert elementFromUse(node) != null : "@AssumeAssertion(nullness): inspection";
return true;
default:
return false;
}
}
/**
* Returns true if {@code tree} has a synthetic argument.
*
* <p>For some anonymous classes with an explicit enclosing expression, javac creates a synthetic
* argument to the constructor that is the enclosing expression of the NewClassTree. Suppose a
* programmer writes:
*
* <pre><code>
* class Outer {
* class Inner { }
* void method() {
* this.new Inner(){};
* }
* }
* </code></pre>
*
* Java 9 javac creates the following synthetic tree for {@code this.new Inner(){}}:
*
* <pre><code>
* new Inner(this) {
* (.Outer x0) {
* x0.super();
* }
* }
* </code></pre>
*
* Java 11 javac creates a different tree without the synthetic argument for {@code this.new
* Inner(){}}:
*
* <pre><code>
* this.new Inner() {
* (.Outer x0) {
* x0.super();
* }
* }
* </code></pre>
*
* @param tree a new class tree
* @return true if {@code tree} has a synthetic argument
*/
public static boolean hasSyntheticArgument(NewClassTree tree) {
if (tree.getClassBody() == null || tree.getEnclosingExpression() != null) {
return false;
}
for (Tree member : tree.getClassBody().getMembers()) {
if (member.getKind() == Kind.METHOD && isConstructor((MethodTree) member)) {
MethodTree methodTree = (MethodTree) member;
StatementTree f = methodTree.getBody().getStatements().get(0);
return TreeUtils.getReceiverTree(((ExpressionStatementTree) f).getExpression()) != null;
}
}
return false;
}
/**
* Returns the name of the invoked method.
*
* @return the name of the invoked method
*/
public static 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();
}
throw new BugInCF("TreeUtils.methodName: cannot be here: " + node);
}
/**
* Returns true if the first statement in the body is a self constructor invocation within a
* constructor.
*
* @return true if the first statement in the body is a self constructor invocation within a
* constructor
*/
public static 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));
}
/**
* Returns the first statement of the tree if it is a block. If it is not a block or an empty
* block, tree is returned.
*
* @param tree any kind of tree
* @return the first statement of the tree if it is a block. If it is not a block or an empty
* block, tree is returned.
*/
public static 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 constructorElt :
ElementFilter.constructorsIn(elem.getEnclosedElements())) {
if (!isSynthetic(constructorElt)) {
return true;
}
}
return false;
}
/**
* Returns true if the given method is synthetic. Also returns true if the method is a generated
* default constructor, which does not appear in source code but is not considered synthetic.
*
* @param ee a method or constructor element
* @return true iff the given method is synthetic
*/
public static boolean isSynthetic(ExecutableElement ee) {
MethodSymbol ms = (MethodSymbol) ee;
long mod = ms.flags();
// GENERATEDCONSTR is for generated constructors, which do not have SYNTHETIC set.
return (mod & (Flags.SYNTHETIC | Flags.GENERATEDCONSTR)) != 0;
}
/**
* Returns true if the given method is synthetic.
*
* @param node a method declaration tree
* @return true iff the given method is synthetic
*/
public static boolean isSynthetic(MethodTree node) {
ExecutableElement ee = TreeUtils.elementFromDeclaration(node);
return isSynthetic(ee);
}
/**
* 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 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 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 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.
*
* <p>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.withoutParens(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.
*
* @param expression a field access or a method invocation
* @return the expression's receiver tree, or null if it does not have an explicit receiver
*/
public static @Nullable ExpressionTree getReceiverTree(ExpressionTree expression) {
ExpressionTree receiver;
switch (expression.getKind()) {
case METHOD_INVOCATION:
// Trying to handle receiver calls to trees of the form
// ((m).getArray())
// returns the type of 'm' in this case
receiver = ((MethodInvocationTree) expression).getMethodSelect();
if (receiver.getKind() == Tree.Kind.MEMBER_SELECT) {
receiver = ((MemberSelectTree) receiver).getExpression();
} else {
// It's a method call "m(foo)" without an explicit receiver
return null;
}
break;
case NEW_CLASS:
receiver = ((NewClassTree) expression).getEnclosingExpression();
break;
case ARRAY_ACCESS:
receiver = ((ArrayAccessTree) expression).getExpression();
break;
case MEMBER_SELECT:
receiver = ((MemberSelectTree) expression).getExpression();
// Avoid int.class
if (receiver instanceof PrimitiveTypeTree) {
return null;
}
break;
case IDENTIFIER:
// It's a field access on implicit this or a local variable/parameter.
return null;
default:
return null;
}
if (receiver == null) {
return null;
}
return TreeUtils.withoutParens(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.
/** The set of kinds that represent classes. */
private static final Set<Tree.Kind> classTreeKinds;
static {
classTreeKinds = EnumSet.noneOf(Tree.Kind.class);
for (Tree.Kind kind : Tree.Kind.values()) {
if (kind.asInterface() == ClassTree.class) {
classTreeKinds.add(kind);
}
}
}
/**
* Return the set of kinds that represent classes.
*
* @return the set of kinds that represent classes
*/
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 static final 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?
*
* <p>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);
if (invoked == null) {
return false;
}
return ElementUtils.isMethod(invoked, method, env);
}
/**
* Returns true if the argument is an invocation of one of the given methods, or of any method
* that overrides them.
*/
public static boolean isMethodInvocation(
Tree methodTree, List<ExecutableElement> methods, ProcessingEnvironment processingEnv) {
if (!(methodTree instanceof MethodInvocationTree)) {
return false;
}
for (ExecutableElement Method : methods) {
if (isMethodInvocation(methodTree, Method, processingEnv)) {
return true;
}
}
return false;
}
/**
* Returns the ExecutableElement for a method declaration. 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...)}.
*
* @param type the class that contains the method
* @param methodName the name of the method
* @param params the number of formal parameters
* @param env the processing environment
* @return the ExecutableElement for the specified method
*/
public static ExecutableElement getMethod(
Class<?> type, String methodName, int params, ProcessingEnvironment env) {
String typeName = type.getCanonicalName();
if (typeName == null) {
throw new BugInCF("class %s has no canonical name", type);
}
return getMethod(typeName, methodName, params, env);
}
/**
* Returns the ExecutableElement for a method declaration. 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...)}.
*
* @param typeName the class that contains the method
* @param methodName the name of the method
* @param params the number of formal parameters
* @param env the processing environment
* @return the ExecutableElement for the specified method
*/
public static ExecutableElement getMethod(
@FullyQualifiedName String typeName,
String methodName,
int params,
ProcessingEnvironment env) {
List<ExecutableElement> methods = getMethods(typeName, methodName, params, env);
if (methods.size() == 1) {
return methods.get(0);
}
throw new BugInCF(
"TreeUtils.getMethod(%s, %s, %d): expected 1 match, found %d",
typeName, methodName, params, methods.size());
}
/**
* Returns the ExecutableElement for a method declaration. Returns null there is no matching
* method. Errs if there is more than one matching method. If more than one method takes the same
* number of formal parameters, then use {@link #getMethod(String, String, ProcessingEnvironment,
* String...)}.
*
* @param typeName the class that contains the method
* @param methodName the name of the method
* @param params the number of formal parameters
* @param env the processing environment
* @return the ExecutableElement for the specified method, or null
*/
public static @Nullable ExecutableElement getMethodOrNull(
@FullyQualifiedName String typeName,
String methodName,
int params,
ProcessingEnvironment env) {
List<ExecutableElement> methods = getMethods(typeName, methodName, params, env);
if (methods.size() == 0) {
return null;
} else if (methods.size() == 1) {
return methods.get(0);
} else {
throw new BugInCF(
"TreeUtils.getMethod(%s, %s, %d): expected 0 or 1 match, found %d",
typeName, methodName, params, methods.size());
}
}
/**
* Returns all ExecutableElements for method declarations of methodName, in class typeName, with
* params formal parameters.
*
* @param typeName the class that contains the method
* @param methodName the name of the method
* @param params the number of formal parameters
* @param env the processing environment
* @return the ExecutableElements for all matching methods
*/
public static List<ExecutableElement> getMethods(
@FullyQualifiedName String typeName,
String methodName,
int params,
ProcessingEnvironment env) {
List<ExecutableElement> methods = new ArrayList<>(1);
TypeElement typeElt = env.getElementUtils().getTypeElement(typeName);
if (typeElt == null) {
throw new UserError("Configuration problem! Could not load type: " + 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. Errs if there is no matching method.
*
* @param type the class that contains the method
* @param methodName the name of the method
* @param env the processing environment
* @param paramTypes the method's formal parameter types
* @return the ExecutableElement for the specified method
*/
public static ExecutableElement getMethod(
Class<?> type, String methodName, ProcessingEnvironment env, String... paramTypes) {
String typeName = type.getCanonicalName();
if (typeName == null) {
throw new BugInCF("class %s has no canonical name", type);
}
return getMethod(typeName, methodName, env, paramTypes);
}
/**
* Returns the ExecutableElement for a method declaration. Errs if there is no matching method.
*
* @param typeName the class that contains the method
* @param methodName the name of the method
* @param env the processing environment
* @param paramTypes the method's formal parameter types
* @return the ExecutableElement for the specified method
*/
public static ExecutableElement getMethod(
@FullyQualifiedName 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<? extends VariableElement> params = exec.getParameters();
for (int i = 0; i < paramTypes.length; i++) {
VariableElement ve = params.get(i);
TypeMirror tm = TypeAnnotationUtils.unannotatedType(ve.asType());
if (!tm.toString().equals(paramTypes[i])) {
typesMatch = false;
break;
}
}
if (typesMatch) {
return exec;
}
}
}
List<String> candidates = new ArrayList<>();
for (ExecutableElement exec : ElementFilter.methodsIn(typeElt.getEnclosedElements())) {
if (exec.getSimpleName().contentEquals(methodName)) {
candidates.add(executableElementToString(exec));
}
}
if (candidates.isEmpty()) {
for (ExecutableElement exec : ElementFilter.methodsIn(typeElt.getEnclosedElements())) {
candidates.add(executableElementToString(exec));
}
}
throw new BugInCF(
"TreeUtils.getMethod: found no match for %s.%s(%s); candidates: %s",
typeName, methodName, Arrays.toString(paramTypes), candidates);
}
/**
* Formats the ExecutableElement in the way that getMethod() expects it.
*
* @param exec an executable element
* @return the ExecutableElement, formatted in the way that getMethod() expects it
*/
private static String executableElementToString(ExecutableElement exec) {
StringJoiner result = new StringJoiner(", ", exec.getSimpleName() + "(", ")");
for (VariableElement param : exec.getParameters()) {
result.add(TypeAnnotationUtils.unannotatedType(param.asType()).toString());
}
return result.toString();
}
/**
* Determine whether the given expression is either "this" or an outer "C.this".
*
* <p>TODO: Should this also handle "super"?
*/
public static 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 expression, such as
*
* <pre>
* <em>f</em>
* <em>obj</em> . <em>f</em>
* </pre>
*
* This method currently also returns true for class literals and qualified this.
*
* @param tree a tree that might be a field access
* @return true iff if tree is a field access expression (implicit or explicit)
*/
public static boolean isFieldAccess(Tree tree) {
if (tree.getKind() == Tree.Kind.MEMBER_SELECT) {
// explicit member access (or a class literal or a qualified this)
MemberSelectTree memberSelect = (MemberSelectTree) tree;
assert isUseOfElement(memberSelect) : "@AssumeAssertion(nullness): tree kind";
Element el = TreeUtils.elementFromUse(memberSelect);
return el.getKind().isField();
} else if (tree.getKind() == Tree.Kind.IDENTIFIER) {
// implicit field access
IdentifierTree ident = (IdentifierTree) tree;
assert isUseOfElement(ident) : "@AssumeAssertion(nullness): tree kind";
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} (which currently also
* returns true for class literals and qualified this).
*
* @param tree a field access tree
* @return the name of the field accessed by {@code tree}
*/
public static String getFieldName(Tree tree) {
assert isFieldAccess(tree);
if (tree.getKind() == 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() == Tree.Kind.MEMBER_SELECT) {
// explicit method access
MemberSelectTree memberSelect = (MemberSelectTree) tree;
assert isUseOfElement(memberSelect) : "@AssumeAssertion(nullness): tree kind";
Element el = TreeUtils.elementFromUse(memberSelect);
return el.getKind() == ElementKind.METHOD || el.getKind() == ElementKind.CONSTRUCTOR;
} else if (tree.getKind() == 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;
}
assert isUseOfElement(ident) : "@AssumeAssertion(nullness): tree kind";
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}.
*
* @param tree a method access tree
* @return the name of the method accessed by {@code tree}
*/
public static String getMethodName(Tree tree) {
assert isMethodAccess(tree);
if (tree.getKind() == 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.
*
* @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;
assert isUseOfElement(memSel) : "@AssumeAssertion(nullness): tree kind";
Element field = TreeUtils.elementFromUse(memSel);
return field.equals(var);
} else if (tree instanceof IdentifierTree) {
IdentifierTree idTree = (IdentifierTree) tree;
assert isUseOfElement(idTree) : "@AssumeAssertion(nullness): tree kind";
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(
@FullyQualifiedName 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;
}
}
throw new BugInCF("TreeUtils.getField: shouldn't be here");
}
/**
* Determine whether the given tree represents an ExpressionTree.
*
* @param tree the Tree to test
* @return whether the tree is an ExpressionTree
*/
public static boolean isExpressionTree(Tree tree) {
return tree instanceof ExpressionTree;
}
/**
* Returns true if this is a super call to the {@link Enum} constructor.
*
* @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);
assert ex != null : "@AssumeAssertion(nullness): tree kind";
Name name = ElementUtils.getQualifiedClassName(ex);
assert name != null : "@AssumeAssertion(nullness): assumption";
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) {
return isClassTree(node) || node.getKind() == Tree.Kind.TYPE_PARAMETER;
}
/**
* 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 Tree} is an anonymous
* constructor (the constructor for an anonymous class.
*
* @param method the {@link Tree} 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 instanceof Symbol)) {
return false;
}
if ((((@NonNull Symbol) e).flags() & Flags.ANONCONSTR) != 0) {
return true;
}
return false;
}
/**
* Converts the given AnnotationTrees to AnnotationMirrors.
*
* @param annoTrees list of annotation trees to convert to annotation mirrors
* @return list of annotation mirrors that represent the given annotation trees
*/
public static List<AnnotationMirror> annotationsFromTypeAnnotationTrees(
List<? extends AnnotationTree> annoTrees) {
return CollectionsPlume.mapList(TreeUtils::annotationFromAnnotationTree, annoTrees);
}
/**
* Converts the given AnnotationTree to an AnnotationMirror.
*
* @param tree annotation tree to convert to an annotation mirror
* @return annotation mirror that represent the given annotation tree
*/
public static AnnotationMirror annotationFromAnnotationTree(AnnotationTree tree) {
return ((JCAnnotation) tree).attribute;
}
/**
* Converts the given AnnotatedTypeTree to a list of AnnotationMirrors.
*
* @param tree annotated type tree to convert
* @return list of AnnotationMirrors from the tree
*/
public static List<? extends AnnotationMirror> annotationsFromTree(AnnotatedTypeTree tree) {
return annotationsFromTypeAnnotationTrees(((JCAnnotatedType) tree).annotations);
}
/**
* Converts the given TypeParameterTree to a list of AnnotationMirrors.
*
* @param tree type parameter tree to convert
* @return list of AnnotationMirrors from the tree
*/
public static List<? extends AnnotationMirror> annotationsFromTree(TypeParameterTree tree) {
return annotationsFromTypeAnnotationTrees(((JCTypeParameter) tree).annotations);
}
/**
* Converts the given NewArrayTree to a list of AnnotationMirrors.
*
* @param tree new array tree
* @return list of AnnotationMirrors from the tree
*/
public static List<? extends AnnotationMirror> annotationsFromArrayCreation(
NewArrayTree tree, int level) {
assert tree instanceof JCNewArray;
final JCNewArray newArray = ((JCNewArray) tree);
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();
}
/**
* Returns true if the tree is the declaration or use of a local variable.
*
* @return true if the tree is the declaration or use of a local variable
*/
public static boolean isLocalVariable(Tree tree) {
if (tree.getKind() == Kind.VARIABLE) {
return elementFromDeclaration((VariableTree) tree).getKind() == ElementKind.LOCAL_VARIABLE;
} else if (tree.getKind() == Kind.IDENTIFIER) {
ExpressionTree etree = (ExpressionTree) tree;
assert isUseOfElement(etree) : "@AssumeAssertion(nullness): tree kind";
return elementFromUse(etree).getKind() == ElementKind.LOCAL_VARIABLE;
}
return false;
}
/**
* Returns the type as a TypeMirror of {@code tree}. To obtain {@code tree}'s AnnotatedTypeMirror,
* call {@code AnnotatedTypeFactory.getAnnotatedType()}.
*
* @return the type as a TypeMirror of {@code tree}
*/
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());
}
/**
* Returns true if {@code tree} is an implicitly typed lambda.
*
* <p>A lambda expression whose formal type parameters have inferred types is an implicitly typed
* lambda. (See JLS 15.27.1)
*
* @param tree any kind of tree
* @return true iff {@code tree} is an implicitly typed lambda
*/
public static boolean isImplicitlyTypedLambda(Tree tree) {
return tree.getKind() == Kind.LAMBDA_EXPRESSION
&& ((JCLambda) tree).paramKind == ParameterKind.IMPLICIT;
}
/**
* Determine whether an expression {@link ExpressionTree} has the constant value true, according
* to the compiler logic.
*
* @param node the expression to be checked
* @return true if {@code node} has the constant value true
*/
public static boolean isExprConstTrue(final ExpressionTree node) {
assert node instanceof JCExpression;
if (((JCExpression) node).type.isTrue()) {
return true;
}
ExpressionTree tree = TreeUtils.withoutParens(node);
if (tree instanceof JCTree.JCBinary) {
JCBinary binTree = (JCBinary) tree;
JCExpression ltree = binTree.lhs;
JCExpression rtree = binTree.rhs;
switch (binTree.getTag()) {
case AND:
return isExprConstTrue(ltree) && isExprConstTrue(rtree);
case OR:
return isExprConstTrue(ltree) || isExprConstTrue(rtree);
default:
break;
}
}
return false;
}
/**
* Return toString(), but without line separators.
*
* @param tree a tree
* @return a one-line string representation of the tree
*/
public static String toStringOneLine(Tree tree) {
return tree.toString().trim().replaceAll("\\s+", " ");
}
/**
* Return either {@link #toStringOneLine} if it is no more than {@code length} characters, or
* {@link #toStringOneLine} quoted and truncated.
*
* @param tree a tree
* @param length the maximum length for the result; must be at least 6
* @return a one-line string representation of the tree that is no longer than {@code length}
* characters long
*/
public static String toStringTruncated(Tree tree, int length) {
if (length < 6) {
throw new IllegalArgumentException("bad length " + length);
}
String result = toStringOneLine(tree);
if (result.length() > length) {
// The quoting increases the likelihood that all delimiters are balanced in the result.
// That makes it easier to manipulate the result (such as skipping over it) in an
// editor. The quoting also makes clear that the value is truncated.
result = "\"" + result.substring(0, length - 5) + "...\"";
}
return result;
}
/**
* Given a javac ExpressionTree representing a fully qualified name such as "java.lang.Object",
* creates a String containing the name.
*
* @param nameExpr an ExpressionTree representing a fully qualified name
* @return a String representation of the fully qualified name
*/
public static String nameExpressionToString(ExpressionTree nameExpr) {
TreeVisitor<String, Void> visitor =
new SimpleTreeVisitor<String, Void>() {
@Override
public String visitIdentifier(IdentifierTree node, Void p) {
return node.toString();
}
@Override
public String visitMemberSelect(MemberSelectTree node, Void p) {
return node.getExpression().accept(this, null) + "." + node.getIdentifier().toString();
}
};
return nameExpr.accept(visitor, null);
}
/**
* Returns true if the binary operator may do a widening primitive conversion. See <a
* href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-5.html">JLS chapter 5</a>.
*
* @param node a binary tree
* @return true if the tree's operator does numeric promotion on its arguments
*/
public static boolean isWideningBinary(BinaryTree node) {
switch (node.getKind()) {
case LEFT_SHIFT:
case LEFT_SHIFT_ASSIGNMENT:
case RIGHT_SHIFT:
case RIGHT_SHIFT_ASSIGNMENT:
case UNSIGNED_RIGHT_SHIFT:
case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT:
// Strictly speaking, these operators do unary promotion on each argument separately.
return true;
case MULTIPLY:
case MULTIPLY_ASSIGNMENT:
case DIVIDE:
case DIVIDE_ASSIGNMENT:
case REMAINDER:
case REMAINDER_ASSIGNMENT:
case PLUS:
case PLUS_ASSIGNMENT:
case MINUS:
case MINUS_ASSIGNMENT:
case LESS_THAN:
case LESS_THAN_EQUAL:
case GREATER_THAN:
case GREATER_THAN_EQUAL:
case EQUAL_TO:
case NOT_EQUAL_TO:
case AND:
case XOR:
case OR:
// These operators do binary promotion on the two arguments together.
return true;
// TODO: CONDITIONAL_EXPRESSION (?:) sometimes does numeric promotion.
default:
return false;
}
}
/**
* Returns the annotations explicitly written on the given type.
*
* @param annoTrees annotations written before a variable/method declaration; null if this type is
* not from such a location. This might contain type annotations that the Java parser attached
* to the declaration rather than to the type.
* @param typeTree the type whose annotations to return
* @return the annotations explicitly written on the given type
*/
public static List<? extends AnnotationTree> getExplicitAnnotationTrees(
@Nullable List<? extends AnnotationTree> annoTrees, Tree typeTree) {
while (true) {
switch (typeTree.getKind()) {
case IDENTIFIER:
case PRIMITIVE_TYPE:
if (annoTrees == null) {
return Collections.emptyList();
}
return annoTrees;
case ANNOTATED_TYPE:
return ((AnnotatedTypeTree) typeTree).getAnnotations();
case ARRAY_TYPE:
case TYPE_PARAMETER:
case UNBOUNDED_WILDCARD:
case EXTENDS_WILDCARD:
case SUPER_WILDCARD:
return Collections.emptyList();
case MEMBER_SELECT:
if (annoTrees == null) {
return Collections.emptyList();
}
typeTree = ((MemberSelectTree) typeTree).getExpression();
break;
case PARAMETERIZED_TYPE:
typeTree = ((ParameterizedTypeTree) typeTree).getType();
break;
case UNION_TYPE:
List<AnnotationTree> result = new ArrayList<>();
for (Tree alternative : ((UnionTypeTree) typeTree).getTypeAlternatives()) {
result.addAll(getExplicitAnnotationTrees(null, alternative));
}
return result;
default:
throw new BugInCF(
"what typeTree? %s %s %s", typeTree.getKind(), typeTree.getClass(), typeTree);
}
}
}
/**
* Return a tree for the default value of the given type. The default value is 0, false, or null.
*
* @param typeMirror a type
* @param processingEnv the processing environment
* @return a tree for {@code type}'s default value
*/
public static LiteralTree getDefaultValueTree(
TypeMirror typeMirror, ProcessingEnvironment processingEnv) {
switch (typeMirror.getKind()) {
case BYTE:
return TreeUtils.createLiteral(TypeTag.BYTE, (byte) 0, typeMirror, processingEnv);
case CHAR:
return TreeUtils.createLiteral(TypeTag.CHAR, '\u0000', typeMirror, processingEnv);
case SHORT:
return TreeUtils.createLiteral(TypeTag.SHORT, (short) 0, typeMirror, processingEnv);
case LONG:
return TreeUtils.createLiteral(TypeTag.LONG, 0L, typeMirror, processingEnv);
case FLOAT:
return TreeUtils.createLiteral(TypeTag.FLOAT, 0.0f, typeMirror, processingEnv);
case INT:
return TreeUtils.createLiteral(TypeTag.INT, 0, typeMirror, processingEnv);
case DOUBLE:
return TreeUtils.createLiteral(TypeTag.DOUBLE, 0.0d, typeMirror, processingEnv);
case BOOLEAN:
return TreeUtils.createLiteral(TypeTag.BOOLEAN, false, typeMirror, processingEnv);
default:
return TreeUtils.createLiteral(TypeTag.BOT, null, typeMirror, processingEnv);
}
}
/**
* Creates a LiteralTree for the given value.
*
* @param typeTag the literal's type tag
* @param value a wrapped primitive, null, or a String
* @param typeMirror the typeMirror for the literal
* @param processingEnv the processing environment
* @return a LiteralTree for the given type tag and value
*/
public static LiteralTree createLiteral(
TypeTag typeTag,
@Nullable Object value,
TypeMirror typeMirror,
ProcessingEnvironment processingEnv) {
Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
TreeMaker maker = TreeMaker.instance(context);
LiteralTree result = maker.Literal(typeTag, value);
((JCLiteral) result).type = (Type) typeMirror;
return result;
}
/**
* Returns true if the given tree evaluates to {@code null}.
*
* @param t a tree
* @return true if the given tree evaluates to {@code null}
*/
public static boolean isNullExpression(Tree t) {
while (true) {
switch (t.getKind()) {
case PARENTHESIZED:
t = ((ParenthesizedTree) t).getExpression();
break;
case TYPE_CAST:
t = ((TypeCastTree) t).getExpression();
break;
case NULL_LITERAL:
return true;
default:
return false;
}
}
}
/**
* Returns true if two expressions originating from the same scope are identical, i.e. they are
* syntactically represented in the same way (modulo parentheses) and represent the same value.
*
* <p>If the expression includes one or more method calls, assumes the method calls are
* deterministic.
*
* @param expr1 the first expression to compare
* @param expr2 the second expression to compare; expr2 must originate from the same scope as
* expr1
* @return true if the expressions expr1 and expr2 are syntactically identical
*/
public static boolean sameTree(ExpressionTree expr1, ExpressionTree expr2) {
expr1 = TreeUtils.withoutParens(expr1);
expr2 = TreeUtils.withoutParens(expr2);
// Converting to a string in order to compare is somewhat inefficient, and it doesn't handle
// internal parentheses. We could create a visitor instead.
return expr1.getKind() == expr2.getKind() && expr1.toString().equals(expr2.toString());
}
}