/*
* Copyright 2012 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.errorprone.util;
import static com.google.errorprone.matchers.JUnitMatchers.JUNIT4_RUN_WITH_ANNOTATION;
import static com.sun.tools.javac.code.Scope.LookupKind.NON_RECURSIVE;
import com.google.common.base.CharMatcher;
import com.google.common.base.Predicate;
import com.google.errorprone.VisitorState;
import com.google.errorprone.dataflow.nullnesspropagation.Nullness;
import com.google.errorprone.dataflow.nullnesspropagation.NullnessAnalysis;
import com.google.errorprone.matchers.JUnitMatchers;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.suppliers.Suppliers;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.MemberReferenceTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ModifiersTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.PackageTree;
import com.sun.source.tree.ParameterizedTypeTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
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.Scope;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.code.Symbol.CompletionFailure;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import com.sun.tools.javac.code.Symbol.PackageSymbol;
import com.sun.tools.javac.code.Symbol.TypeSymbol;
import com.sun.tools.javac.code.Symbol.VarSymbol;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Type.ClassType;
import com.sun.tools.javac.code.Type.TypeVar;
import com.sun.tools.javac.code.TypeTag;
import com.sun.tools.javac.code.Types;
import com.sun.tools.javac.comp.Enter;
import com.sun.tools.javac.comp.Resolve;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
import com.sun.tools.javac.tree.JCTree.JCIdent;
import com.sun.tools.javac.tree.JCTree.JCLiteral;
import com.sun.tools.javac.tree.JCTree.JCMemberReference;
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
import com.sun.tools.javac.tree.JCTree.JCNewClass;
import com.sun.tools.javac.tree.JCTree.JCPackageDecl;
import com.sun.tools.javac.tree.JCTree.JCTypeParameter;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
import com.sun.tools.javac.tree.TreeInfo;
import com.sun.tools.javac.util.Filter;
import com.sun.tools.javac.util.Log;
import com.sun.tools.javac.util.Log.DeferredDiagnosticHandler;
import com.sun.tools.javac.util.Name;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.net.JarURLConnection;
import java.net.URI;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ElementKind;
import javax.lang.model.type.TypeKind;
/**
* This class contains utility methods to work with the javac AST.
*/
public class ASTHelpers {
/**
* Determines whether two expressions refer to the same variable. Note that returning false
* doesn't necessarily mean the expressions do *not* refer to the same field. We don't attempt
* to do any complex analysis here, just catch the obvious cases.
*/
public static boolean sameVariable(ExpressionTree expr1, ExpressionTree expr2) {
// Throw up our hands if we're not comparing identifiers and/or field accesses.
if ((expr1.getKind() != Kind.IDENTIFIER && expr1.getKind() != Kind.MEMBER_SELECT)
|| (expr2.getKind() != Kind.IDENTIFIER && expr2.getKind() != Kind.MEMBER_SELECT)) {
return false;
}
Symbol sym1 = getSymbol(expr1);
Symbol sym2 = getSymbol(expr2);
if (sym1 == null) {
throw new IllegalStateException("Couldn't get symbol for " + expr1);
} else if (sym2 == null) {
throw new IllegalStateException("Couldn't get symbol for " + expr2);
}
if (expr1.getKind() == Kind.IDENTIFIER && expr2.getKind() == Kind.IDENTIFIER) {
// foo == foo?
return sym1.equals(sym2);
} else if (expr1.getKind() == Kind.MEMBER_SELECT && expr2.getKind() == Kind.MEMBER_SELECT) {
// foo.baz.bar == foo.baz.bar?
return sym1.equals(sym2)
&& sameVariable(((JCFieldAccess) expr1).selected, ((JCFieldAccess) expr2).selected);
} else {
// this.foo == foo?
ExpressionTree selected = null;
if (expr1.getKind() == Kind.IDENTIFIER) {
selected = ((JCFieldAccess) expr2).selected;
} else {
selected = ((JCFieldAccess) expr1).selected;
}
// TODO(eaftan): really shouldn't be relying on .toString()
return selected.toString().equals("this") && sym1.equals(sym2);
}
}
/**
* Gets the symbol for a tree. Returns null if this tree does not have a symbol because it is of
* the wrong type, if {@code tree} is null, or if the symbol cannot be found due to a compilation
* error.
*/
// TODO(eaftan): refactor other code that accesses symbols to use this method
public static Symbol getSymbol(Tree tree) {
if (tree instanceof ClassTree) {
return getSymbol((ClassTree) tree);
}
if (tree instanceof MethodTree) {
return getSymbol((MethodTree) tree);
}
if (tree instanceof VariableTree) {
return getSymbol((VariableTree) tree);
}
if (tree instanceof JCFieldAccess) {
return ((JCFieldAccess) tree).sym;
}
if (tree instanceof JCIdent) {
return ((JCIdent) tree).sym;
}
if (tree instanceof JCMethodInvocation) {
return ASTHelpers.getSymbol((MethodInvocationTree) tree);
}
if (tree instanceof JCNewClass) {
return ASTHelpers.getSymbol((NewClassTree) tree);
}
if (tree instanceof AnnotationTree) {
return getSymbol(((AnnotationTree) tree).getAnnotationType());
}
if (tree instanceof PackageTree) {
return getSymbol((PackageTree) tree);
}
if (tree instanceof ParameterizedTypeTree) {
return getSymbol(((ParameterizedTypeTree) tree).getType());
}
if (tree instanceof TypeParameterTree) {
Type type = ((JCTypeParameter) tree).type;
return type == null ? null : type.tsym;
}
if (tree instanceof MemberReferenceTree) {
return ((JCMemberReference) tree).sym;
}
return null;
}
/** Gets the symbol for a class. */
public static ClassSymbol getSymbol(ClassTree tree) {
return ((JCClassDecl) tree).sym;
}
/** Gets the symbol for a package. */
public static PackageSymbol getSymbol(PackageTree tree) {
return ((JCPackageDecl) tree).packge;
}
/** Gets the symbol for a method. */
public static MethodSymbol getSymbol(MethodTree tree) {
return ((JCMethodDecl) tree).sym;
}
/** Gets the method symbol for a new class. */
public static MethodSymbol getSymbol(NewClassTree tree) {
Symbol sym = ((JCNewClass) tree).constructor;
return sym instanceof MethodSymbol ? (MethodSymbol) sym : null;
}
/** Gets the symbol for a variable. */
public static VarSymbol getSymbol(VariableTree tree) {
return ((JCVariableDecl) tree).sym;
}
/** Gets the symbol for a method invocation. */
public static MethodSymbol getSymbol(MethodInvocationTree tree) {
Symbol sym = ASTHelpers.getSymbol(tree.getMethodSelect());
if (!(sym instanceof MethodSymbol)) {
// Defensive. Would only occur if there are errors in the AST.
return null;
}
return (MethodSymbol) sym;
}
/**
* Given a TreePath, finds the first enclosing node of the given type and returns the path from
* the enclosing node to the top-level {@code CompilationUnitTree}.
*/
public static <T> TreePath findPathFromEnclosingNodeToTopLevel(TreePath path, Class<T> klass) {
if (path != null) {
do {
path = path.getParentPath();
} while (path != null && !(klass.isInstance(path.getLeaf())));
}
return path;
}
/**
* Given a TreePath, walks up the tree until it finds a node of the given type. Returns null
* if no such node is found.
*/
@Nullable
public static <T> T findEnclosingNode(TreePath path, Class<T> klass) {
path = findPathFromEnclosingNodeToTopLevel(path, klass);
return (path == null) ? null : klass.cast(path.getLeaf());
}
/**
* Find the root assignable expression of a chain of field accesses. If there is no root
* (i.e, a bare method call or a static method call), return null.
*
* <p>Examples:
* <pre>
* {@code
* a.trim().intern() ==> a
* a.b.trim().intern() ==> a.b
* this.intValue.foo() ==> this.intValue
* this.foo() ==> this
* intern() ==> null
* String.format() ==> null
* java.lang.String.format() ==> null
* }
* </pre>
*/
public static ExpressionTree getRootAssignable(MethodInvocationTree methodInvocationTree) {
if (!(methodInvocationTree instanceof JCMethodInvocation)) {
throw new IllegalArgumentException(
"Expected type to be JCMethodInvocation, but was " + methodInvocationTree.getClass());
}
// Check for bare method call, e.g. intern().
if (((JCMethodInvocation) methodInvocationTree).getMethodSelect() instanceof JCIdent) {
return null;
}
// Unwrap the field accesses until you get to an identifier.
ExpressionTree expr = methodInvocationTree;
while (expr instanceof JCMethodInvocation) {
expr = ((JCMethodInvocation) expr).getMethodSelect();
if (expr instanceof JCFieldAccess) {
expr = ((JCFieldAccess) expr).getExpression();
}
}
// We only want assignable identifiers.
Symbol sym = getSymbol(expr);
if (sym instanceof VarSymbol) {
return expr;
}
return null;
}
/**
* Gives the return type of an ExpressionTree that represents a method select.
*
* TODO(eaftan): Are there other places this could be used?
*/
public static Type getReturnType(ExpressionTree expressionTree) {
if (expressionTree instanceof JCFieldAccess) {
JCFieldAccess methodCall = (JCFieldAccess) expressionTree;
return methodCall.type.getReturnType();
} else if (expressionTree instanceof JCIdent) {
JCIdent methodCall = (JCIdent) expressionTree;
return methodCall.type.getReturnType();
} else if (expressionTree instanceof JCMethodInvocation) {
return getReturnType(((JCMethodInvocation) expressionTree).getMethodSelect());
}
throw new IllegalArgumentException("Expected a JCFieldAccess or JCIdent");
}
/**
* Returns the type that this expression tree will evaluate to. If its a literal, an identifier,
* or a member select this is the actual type, if its a method invocation then its the return type
* of the method (after instantiating generic types), if its a constructor then its the type of
* the returned class.
*
* <p>TODO(andrewrice) consider replacing {@code getReturnType} with this method
*
* @param expressionTree the tree to evaluate
* @return the result type of this tree or null if unable to resolve it
*/
public static Type getResultType(ExpressionTree expressionTree) {
Type type = ASTHelpers.getType(expressionTree);
return type == null ? null : Optional.ofNullable(type.getReturnType()).orElse(type);
}
/**
* Returns the type of a receiver of a method call expression. Precondition: the expressionTree
* corresponds to a method call.
*
* <p>Examples:
*
* <pre>{@code
* a.b.foo() ==> type of a.b
* a.bar().foo() ==> type of a.bar()
* this.foo() ==> type of this
* foo() ==> type of this
* TheClass.aStaticMethod() ==> TheClass
* aStaticMethod() ==> type of class in which method is defined
* }</pre>
*/
public static Type getReceiverType(ExpressionTree expressionTree) {
if (expressionTree instanceof JCFieldAccess) {
JCFieldAccess methodSelectFieldAccess = (JCFieldAccess) expressionTree;
return methodSelectFieldAccess.selected.type;
} else if (expressionTree instanceof JCIdent) {
JCIdent methodCall = (JCIdent) expressionTree;
return methodCall.sym.owner.type;
} else if (expressionTree instanceof JCMethodInvocation) {
return getReceiverType(((JCMethodInvocation) expressionTree).getMethodSelect());
} else if (expressionTree instanceof JCMemberReference) {
return ((JCMemberReference) expressionTree).getQualifierExpression().type;
}
throw new IllegalArgumentException(
"Expected a JCFieldAccess or JCIdent from expression " + expressionTree);
}
/**
* Returns the receiver of an expression.
*
* <p>Examples:
* <pre>
* {@code
* a.foo() ==> a
* a.b.foo() ==> a.b
* a.bar().foo() ==> a.bar()
* a.b.c ==> a.b
* a.b().c ==> a.b()
* this.foo() ==> this
* foo() ==> null
* TheClass.aStaticMethod() ==> TheClass
* aStaticMethod() ==> null
* aStaticallyImportedMethod() ==> null
* }
* </pre>
*/
@Nullable
public static ExpressionTree getReceiver(ExpressionTree expressionTree) {
if (expressionTree instanceof MethodInvocationTree) {
ExpressionTree methodSelect = ((MethodInvocationTree) expressionTree).getMethodSelect();
if (methodSelect instanceof IdentifierTree) {
return null;
}
return getReceiver(methodSelect);
} else if (expressionTree instanceof MemberSelectTree) {
return ((MemberSelectTree) expressionTree).getExpression();
} else if (expressionTree instanceof MemberReferenceTree) {
return ((MemberReferenceTree) expressionTree).getQualifierExpression();
} else {
throw new IllegalStateException(String.format(
"Expected expression '%s' to be a method invocation or field access, but was %s",
expressionTree, expressionTree.getKind()));
}
}
/**
* Given a BinaryTree to match against and a list of two matchers, applies the matchers to the
* operands in both orders. If both matchers match, returns a list with the operand that
* matched each matcher in the corresponding position.
*
* @param tree a BinaryTree AST node
* @param matchers a list of matchers
* @param state the VisitorState
* @return a list of matched operands, or null if at least one did not match
*/
public static List<ExpressionTree> matchBinaryTree(
BinaryTree tree, List<Matcher<ExpressionTree>> matchers, VisitorState state) {
ExpressionTree leftOperand = tree.getLeftOperand();
ExpressionTree rightOperand = tree.getRightOperand();
if (matchers.get(0).matches(leftOperand, state)
&& matchers.get(1).matches(rightOperand, state)) {
return Arrays.asList(leftOperand, rightOperand);
} else if (matchers.get(0).matches(rightOperand, state)
&& matchers.get(1).matches(leftOperand, state)) {
return Arrays.asList(rightOperand, leftOperand);
}
return null;
}
@Nullable
public static MethodSymbol findSuperMethodInType(
MethodSymbol methodSymbol, Type superType, Types types) {
if (methodSymbol.isStatic() || superType.equals(methodSymbol.owner.type)) {
return null;
}
Scope scope = superType.tsym.members();
for (Symbol sym : scope.getSymbolsByName(methodSymbol.name)) {
if (sym != null
&& !sym.isStatic()
&& ((sym.flags() & Flags.SYNTHETIC) == 0)
&& sym.name.contentEquals(methodSymbol.name)
&& methodSymbol.overrides(sym, (TypeSymbol) methodSymbol.owner, types, true)) {
return (MethodSymbol) sym;
}
}
return null;
}
public static Set<MethodSymbol> findSuperMethods(MethodSymbol methodSymbol, Types types) {
TypeSymbol owner = (TypeSymbol) methodSymbol.owner;
return types
.closure(owner.type)
.stream()
.map(type -> findSuperMethodInType(methodSymbol, type, types))
.filter(x -> x != null)
.collect(Collectors.toCollection(LinkedHashSet::new));
}
/**
* Finds all methods in any superclass of {@code startClass} with a certain {@code name} that
* match the given {@code predicate}.
*
* @return The (possibly empty) set of methods in any superclass that match {@code predicate} and
* have the given {@code name}.
*/
public static Set<MethodSymbol> findMatchingMethods(
Name name, final Predicate<MethodSymbol> predicate, Type startClass, Types types) {
Filter<Symbol> matchesMethodPredicate =
sym -> sym instanceof MethodSymbol && predicate.apply((MethodSymbol) sym);
Set<MethodSymbol> matchingMethods = new HashSet<>();
// Iterate over all classes and interfaces that startClass inherits from.
for (Type superClass : types.closure(startClass)) {
// Iterate over all the methods declared in superClass.
TypeSymbol superClassSymbol = superClass.tsym;
Scope superClassSymbols = superClassSymbol.members();
if (superClassSymbols != null) { // Can be null if superClass is a type variable
for (Symbol symbol :
superClassSymbols.getSymbolsByName(name, matchesMethodPredicate, NON_RECURSIVE)) {
// By definition of the filter, we know that the symbol is a MethodSymbol.
matchingMethods.add((MethodSymbol) symbol);
}
}
}
return matchingMethods;
}
/**
* Determines whether a symbol has an annotation of the given type. This includes annotations
* inherited from superclasses due to {@code @Inherited}.
*
* @param annotationClass the binary class name of the annotation (e.g.
* "javax.annotation.Nullable", or "some.package.OuterClassName$InnerClassName")
*/
public static boolean hasAnnotation(Symbol sym, String annotationClass, VisitorState state) {
Name annotationName = state.getName(annotationClass);
Symbol annotationSym;
synchronized (state.context) {
annotationSym = state.getSymtab().enterClass(state.getSymtab().java_base, annotationName);
}
try {
annotationSym.complete();
} catch (CompletionFailure e) {
// @Inherited won't work if the annotation isn't on the classpath, but we can still check
// if it's present directly
}
Symbol inheritedSym = state.getSymtab().inheritedType.tsym;
if ((sym == null) || (annotationSym == null)) {
return false;
}
if ((sym instanceof ClassSymbol) && (annotationSym.attribute(inheritedSym) != null)) {
while (sym != null) {
if (sym.attribute(annotationSym) != null) {
return true;
}
sym = ((ClassSymbol) sym).getSuperclass().tsym;
}
return false;
} else {
return sym.attribute(annotationSym) != null;
}
}
/**
* Check for the presence of an annotation, considering annotation inheritance.
*
* @return true if the symbol is annotated with given type.
*/
public static boolean hasAnnotation(
Symbol sym, Class<? extends Annotation> annotationClass, VisitorState state) {
return hasAnnotation(sym, annotationClass.getName(), state);
}
/**
* Check for the presence of an annotation, considering annotation inheritance.
*
* @return the annotation of given type on the tree's symbol, or null.
*/
public static boolean hasAnnotation(
Tree tree, Class<? extends Annotation> annotationClass, VisitorState state) {
Symbol sym = getSymbol(tree);
return hasAnnotation(sym, annotationClass.getName(), state);
}
/**
* Check for the presence of an annotation with a specific simple name directly on this symbol.
* Does *not* consider annotation inheritance.
*
* @param sym the symbol to check for the presence of the annotation
* @param simpleName the simple name of the annotation to look for, e.g. "Nullable" or
* "CheckReturnValue"
*/
public static boolean hasDirectAnnotationWithSimpleName(Symbol sym, String simpleName) {
for (AnnotationMirror annotation : sym.getAnnotationMirrors()) {
if (annotation.getAnnotationType().asElement().getSimpleName().contentEquals(simpleName)) {
return true;
}
}
return false;
}
/**
* Retrieve an annotation, considering annotation inheritance.
*
* @return the annotation of given type on the tree's symbol, or null.
*/
public static <T extends Annotation> T getAnnotation(Tree tree, Class<T> annotationClass) {
Symbol sym = getSymbol(tree);
return sym == null ? null : getAnnotation(sym, annotationClass);
}
/**
* Retrieve an annotation, considering annotation inheritance.
*
* @return the annotation of given type on the symbol, or null.
*/
// Symbol#getAnnotation is not intended for internal javac use, but because error-prone is run
// after attribution it's safe to use here.
@SuppressWarnings("deprecation")
public static <T extends Annotation> T getAnnotation(Symbol sym, Class<T> annotationClass) {
return sym == null ? null : sym.getAnnotation(annotationClass);
}
/** @return all values of the given enum type, in declaration order. */
public static LinkedHashSet<String> enumValues(TypeSymbol enumType) {
if (enumType.getKind() != ElementKind.ENUM) {
throw new IllegalStateException();
}
Scope scope = enumType.members();
Deque<String> values = new ArrayDeque<>();
for (Symbol sym : scope.getSymbols()) {
if (sym instanceof VarSymbol) {
VarSymbol var = (VarSymbol) sym;
if ((var.flags() & Flags.ENUM) != 0) {
/**
* Javac gives us the members backwards, apparently. It's worth making an effort to
* preserve declaration order because it's useful for diagnostics (e.g. in
* {@link MissingCasesInEnumSwitch}).
*/
values.push(sym.name.toString());
}
}
}
return new LinkedHashSet<>(values);
}
/** Returns true if the given tree is a generated constructor. **/
public static boolean isGeneratedConstructor(MethodTree tree) {
if (!(tree instanceof JCMethodDecl)) {
return false;
}
return (((JCMethodDecl) tree).mods.flags & Flags.GENERATEDCONSTR) == Flags.GENERATEDCONSTR;
}
/** Returns the list of all constructors defined in the class (including generated ones). */
public static List<MethodTree> getConstructors(ClassTree classTree) {
List<MethodTree> constructors = new ArrayList<>();
for (Tree member : classTree.getMembers()) {
if (member instanceof MethodTree) {
MethodTree methodTree = (MethodTree) member;
if (getSymbol(methodTree).isConstructor()) {
constructors.add(methodTree);
}
}
}
return constructors;
}
/**
* Returns the {@code Type} of the given tree, or {@code null} if the type could not be
* determined.
*/
@Nullable
public static Type getType(Tree tree) {
return tree instanceof JCTree ? ((JCTree) tree).type : null;
}
/**
* Returns the {@code ClassType} for the given type {@code ClassTree} or {@code null} if the
* type could not be determined.
*/
@Nullable
public static ClassType getType(ClassTree tree) {
Type type = getType((Tree) tree);
return type instanceof ClassType ? (ClassType) type : null;
}
public static String getAnnotationName(AnnotationTree tree) {
Symbol sym = getSymbol(tree);
return sym == null ? null : sym.name.toString();
}
/** Return the enclosing {@code ClassSymbol} of the given symbol, or {@code null}. */
public static ClassSymbol enclosingClass(Symbol sym) {
return sym.owner.enclClass();
}
/** Return the enclosing {@code PackageSymbol} of the given symbol, or {@code null}. */
public static PackageSymbol enclosingPackage(Symbol sym) {
return sym.packge();
}
/**
* Returns the {@link Nullness} for an expression as determined by the nullness dataflow
* analysis.
*/
public static Nullness getNullnessValue(
ExpressionTree expr, VisitorState state, NullnessAnalysis nullnessAnalysis) {
TreePath pathToExpr = new TreePath(state.getPath(), expr);
return nullnessAnalysis.getNullness(pathToExpr, state.context);
}
/** Returns the compile-time constant value of a tree if it has one, or {@code null}. */
@Nullable
public static Object constValue(Tree tree) {
if (tree == null) {
return null;
}
tree = TreeInfo.skipParens((JCTree) tree);
Type type = ASTHelpers.getType(tree);
Object value;
if (tree instanceof JCLiteral) {
value = ((JCLiteral) tree).value;
} else if (type != null) {
value = type.constValue();
} else {
return null;
}
if (type.hasTag(TypeTag.BOOLEAN) && value instanceof Integer) {
return ((Integer) value) == 1;
}
return value;
}
/** Returns the compile-time constant value of a tree if it is of type clazz, or {@code null}. */
@Nullable
public static <T> T constValue(Tree tree, Class<? extends T> clazz) {
Object value = constValue(tree);
return clazz.isInstance(value) ? clazz.cast(value) : null;
}
/**
* Return true if the given type is 'void' or 'Void'.
*/
public static boolean isVoidType(Type type, VisitorState state) {
if (type == null) {
return false;
}
return type.getKind() == TypeKind.VOID
|| state.getTypes().isSameType(Suppliers.JAVA_LANG_VOID_TYPE.get(state), type);
}
/**
* Returns true if {@code erasure(s) <: erasure(t)}.
*/
public static boolean isSubtype(Type s, Type t, VisitorState state) {
if (s == null || t == null) {
return false;
}
Types types = state.getTypes();
return types.isSubtype(types.erasure(s), types.erasure(t));
}
/**
* Returns true if {@code erasure(s)} is castable to {@code erasure(t)}.
*/
public static boolean isCastable(Type s, Type t, VisitorState state) {
if (s == null || t == null) {
return false;
}
Types types = state.getTypes();
return types.isCastable(types.erasure(s), types.erasure(t));
}
/** Returns true if {@code erasure(s) == erasure(t)}. */
public static boolean isSameType(Type s, Type t, VisitorState state) {
if (s == null || t == null) {
return false;
}
Types types = state.getTypes();
return types.isSameType(types.erasure(s), types.erasure(t));
}
/** Returns the modifiers tree of the given class, method, or variable declaration. */
@Nullable
public static ModifiersTree getModifiers(Tree tree) {
if (tree instanceof ClassTree) {
return ((ClassTree) tree).getModifiers();
} else if (tree instanceof MethodTree) {
return ((MethodTree) tree).getModifiers();
} else if (tree instanceof VariableTree) {
return ((VariableTree) tree).getModifiers();
} else {
return null;
}
}
/**
* Returns the upper bound of a type if it has one, or the type itself if not. Correctly
* handles wildcards and capture variables.
*/
public static Type getUpperBound(Type type, Types types) {
if (type.hasTag(TypeTag.WILDCARD)) {
return types.wildUpperBound(type);
}
if (type.hasTag(TypeTag.TYPEVAR) && ((TypeVar) type).isCaptured()) {
return types.cvarUpperBound(type);
}
if (type.getUpperBound() != null) {
return type.getUpperBound();
}
// concrete type, e.g. java.lang.String, or a case we haven't considered
return type;
}
/**
* Returns true if the leaf node in the {@link TreePath} from {@code state} sits somewhere
* underneath a class or method that is marked as JUnit 3 or 4 test code.
*/
public static boolean isJUnitTestCode(VisitorState state) {
for (Tree ancestor : state.getPath()) {
if (ancestor instanceof MethodTree
&& JUnitMatchers.hasJUnitAnnotation.matches((MethodTree) ancestor, state)) {
return true;
}
if (ancestor instanceof ClassTree
&& (JUnitMatchers.isTestCaseDescendant.matches((ClassTree) ancestor, state)
|| hasAnnotation(getSymbol(ancestor), JUNIT4_RUN_WITH_ANNOTATION, state))) {
return true;
}
}
return false;
}
/**
* Finds a declaration with the given name that is in scope at the current location.
*
* @deprecated Use {@link FindIdentifiers#findIdent} instead.
*/
// TODO(eaftan): migrate plugin callers and delete this
@Deprecated
public static Symbol findIdent(String name, VisitorState state) {
return FindIdentifiers.findIdent(name, state);
}
/** Returns an {@link AnnotationTree} with the given simple name, or {@code null}. */
public static AnnotationTree getAnnotationWithSimpleName(
List<? extends AnnotationTree> annotations, String name) {
for (AnnotationTree annotation : annotations) {
if (hasSimpleName(annotation, name)) {
return annotation;
}
}
return null;
}
private static boolean hasSimpleName(AnnotationTree annotation, String name) {
Tree annotationType = annotation.getAnnotationType();
javax.lang.model.element.Name simpleName;
if (annotationType instanceof IdentifierTree) {
simpleName = ((IdentifierTree) annotationType).getName();
} else if (annotationType instanceof MemberSelectTree) {
simpleName = ((MemberSelectTree) annotationType).getIdentifier();
} else {
return false;
}
return simpleName.contentEquals(name);
}
private static final CharMatcher BACKSLASH_MATCHER = CharMatcher.is('\\');
/**
* Extract the filename from the URI, with special handling for jar files. The return value is
* normalized to always use '/' to separate elements of the path and to always have a leading '/'.
*/
@Nullable
public static String getFileNameFromUri(URI uri) {
if (!uri.getScheme().equals("jar")) {
return uri.getPath();
}
try {
String jarEntryFileName = ((JarURLConnection) uri.toURL().openConnection()).getEntryName();
// It's possible (though it violates the zip file spec) for paths to zip file entries to use
// '\' as the separator. Normalize to use '/'.
jarEntryFileName = BACKSLASH_MATCHER.replaceFrom(jarEntryFileName, '/');
if (!jarEntryFileName.startsWith("/")) {
jarEntryFileName = "/" + jarEntryFileName;
}
return jarEntryFileName;
} catch (IOException e) {
return null;
}
}
/**
* Given a Type ({@code base}), find the method named {@code name}, with the appropriate {@code
* argTypes} and {@code tyargTypes} and return its MethodSymbol.
*
* <p>Ex:
*
* <pre>{@code
* .....
* class A {}
* class B {
* public int hashCode() { return 42; }
* }
* .....
*
* MethodSymbol meth = ASTHelpers.resolveExistingMethod(
* state,
* symbol,
* state.getName("hashCode"),
* ImmutableList.<Type>of(),
* ImmutableList.<Type>of());
* }</pre>
*
* {@code meth} could be different MethodSymbol's depending on whether {@code symbol} represented
* {@code B} or {@code A}. (B's hashCode method or Object#hashCode).
*
* <p>Do NOT call this method unless the method you're looking for is guaranteed to exist. A fatal
* error will result otherwise. Note: a method can fail to exist if it was added in a newer
* version of a library (you may be depending on version N of a library which added a method to a
* class, but someone else could depend on version N-1 which didn't have that method).
*
* @return a MethodSymbol representing the method symbol resolved from the context of this type
*/
public static MethodSymbol resolveExistingMethod(
VisitorState state,
TypeSymbol base,
Name name,
Iterable<Type> argTypes,
Iterable<Type> tyargTypes) {
Resolve resolve = Resolve.instance(state.context);
Enter enter = Enter.instance(state.context);
Log log = Log.instance(state.context);
DeferredDiagnosticHandler handler = new DeferredDiagnosticHandler(log);
try {
return resolve.resolveInternalMethod(
/*pos*/ null,
enter.getEnv(base),
base.type,
name,
com.sun.tools.javac.util.List.from(argTypes),
com.sun.tools.javac.util.List.from(tyargTypes));
} finally {
log.popDiagnosticHandler(handler);
}
}
}