blob: 4c99078ab408fe822c3885b3c33cb2e3ce7506f9 [file] [log] [blame]
package org.checkerframework.javacutil;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.model.JavacTypes;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.util.Context;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Iterator;
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.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.signature.qual.BinaryName;
import org.checkerframework.checker.signature.qual.CanonicalName;
import org.plumelib.util.CollectionsPlume;
/**
* Utility methods for analyzing {@code Element}s. This complements {@link Elements}, providing
* functionality that it does not.
*/
public class ElementUtils {
// Class cannot be instantiated.
private ElementUtils() {
throw new AssertionError("Class ElementUtils cannot be instantiated.");
}
/**
* Returns the innermost type element enclosing the given element. Returns the element itself if
* it is a type element.
*
* @param elem the enclosed element of a class
* @return the innermost type element, or null if no type element encloses {@code elem}
* @deprecated use {@link #enclosingTypeElement}
*/
@Deprecated // use enclosingTypeElement
public static @Nullable TypeElement enclosingClass(final Element elem) {
return enclosingTypeElement(elem);
}
/**
* Returns the innermost type element that is, or encloses, the given element.
*
* <p>Note that in this code:
*
* <pre>{@code
* class Outer {
* static class Inner { }
* }
* }</pre>
*
* {@code Inner} has no enclosing type, but this method returns {@code Outer}.
*
* @param elem the enclosed element of a class
* @return the innermost type element (possibly the argument itself), or null if {@code elem} is
* not, and is not enclosed by, a type element
*/
public static @Nullable TypeElement enclosingTypeElement(final Element elem) {
Element result = elem;
while (result != null && !isTypeElement(result)) {
result = result.getEnclosingElement();
}
return (TypeElement) result;
}
/**
* Returns the innermost type element enclosing the given element, that is different from the
* element itself. By contrast, {@link #enclosingTypeElement} returns its argument if the argument
* is a type element.
*
* @param elem the enclosed element of a class
* @return the innermost type element, or null if no type element encloses {@code elem}
*/
public static @Nullable TypeElement strictEnclosingTypeElement(final Element elem) {
Element enclosingElement = elem.getEnclosingElement();
if (enclosingElement == null) {
return null;
}
return enclosingTypeElement(enclosingElement);
}
/**
* Returns the top-level type element that contains {@code element}.
*
* @param element the element whose enclosing tye element to find
* @return a type element containing {@code element} that isn't contained in another class
*/
public static TypeElement toplevelEnclosingTypeElement(Element element) {
TypeElement result = enclosingTypeElement(element);
if (result == null) {
return (TypeElement) element;
}
TypeElement enclosing = strictEnclosingTypeElement(result);
while (enclosing != null) {
result = enclosing;
enclosing = strictEnclosingTypeElement(enclosing);
}
return result;
}
/**
* Returns the binary name of the class enclosing {@code executableElement}.
*
* @param executableElement the ExecutableElement
* @return the binary name of the class enclosing {@code executableElement}
*/
public static @BinaryName String getEnclosingClassName(ExecutableElement executableElement) {
return getBinaryName(((MethodSymbol) executableElement).enclClass());
}
/**
* Returns the binary name of the class enclosing {@code variableElement}.
*
* @param variableElement the VariableElement
* @return the binary name of the class enclosing {@code variableElement}
*/
public static @BinaryName String getEnclosingClassName(VariableElement variableElement) {
TypeElement enclosingType = enclosingTypeElement(variableElement);
if (enclosingType == null) {
throw new BugInCF("enclosingTypeElement(%s) is null", variableElement);
}
return getBinaryName(enclosingType);
}
/**
* Returns the innermost package element enclosing the given element. The same effect as {@link
* javax.lang.model.util.Elements#getPackageOf(Element)}. Returns the element itself if it is a
* package.
*
* @param elem the enclosed element of a package
* @return the innermost package element
*/
public static PackageElement enclosingPackage(final Element elem) {
Element result = elem;
while (result != null && result.getKind() != ElementKind.PACKAGE) {
@Nullable Element encl = result.getEnclosingElement();
result = encl;
}
return (PackageElement) result;
}
/**
* Returns the "parent" package element for the given package element. For package "A.B" it gives
* "A". For package "A" it gives the default package. For the default package it returns null.
*
* <p>Note that packages are not enclosed within each other, we have to manually climb the
* namespaces. Calling "enclosingPackage" on a package element returns the package element itself
* again.
*
* @param elem the package to start from
* @return the parent package element or {@code null}
*/
public static @Nullable PackageElement parentPackage(
final PackageElement elem, final Elements e) {
// The following might do the same thing:
// ((Symbol) elt).owner;
// TODO: verify and see whether the change is worth it.
String fqnstart = elem.getQualifiedName().toString();
String fqn = fqnstart;
if (fqn != null && !fqn.isEmpty()) {
int dotPos = fqn.lastIndexOf('.');
if (dotPos != -1) {
return e.getPackageElement(fqn.substring(0, dotPos));
}
}
return null;
}
/**
* Returns true if the element is a static element: whether it is a static field, static method,
* or static class.
*
* @return true if element is static
*/
public static boolean isStatic(Element element) {
return element.getModifiers().contains(Modifier.STATIC);
}
/**
* Returns true if the element is a final element: a final field, final method, or final class.
*
* @return true if the element is final
*/
public static boolean isFinal(Element element) {
return element.getModifiers().contains(Modifier.FINAL);
}
/**
* Returns true if the element is a effectively final element.
*
* @return true if the element is effectively final
*/
public static boolean isEffectivelyFinal(Element element) {
Symbol sym = (Symbol) element;
if (sym.getEnclosingElement().getKind() == ElementKind.METHOD
&& (sym.getEnclosingElement().flags() & Flags.ABSTRACT) != 0) {
return true;
}
return (sym.flags() & (Flags.FINAL | Flags.EFFECTIVELY_FINAL)) != 0;
}
/**
* Returns the {@code TypeMirror} for usage of Element as a value. It returns the return type of a
* method element, the class type of a constructor, or simply the type mirror of the element
* itself.
*
* @param element the element whose type to obtain
* @return the type for the element used as a value
*/
@SuppressWarnings("nullness:dereference.of.nullable") // a constructor has an enclosing class
public static TypeMirror getType(Element element) {
if (element.getKind() == ElementKind.METHOD) {
return ((ExecutableElement) element).getReturnType();
} else if (element.getKind() == ElementKind.CONSTRUCTOR) {
return enclosingClass(element).asType();
} else {
return element.asType();
}
}
/**
* Returns the qualified name of the innermost class enclosing the provided {@code Element}.
*
* @param element an element enclosed by a class, or a {@code TypeElement}
* @return the qualified {@code Name} of the innermost class enclosing the element
*/
public static @Nullable Name getQualifiedClassName(Element element) {
if (element.getKind() == ElementKind.PACKAGE) {
PackageElement elem = (PackageElement) element;
return elem.getQualifiedName();
}
TypeElement elem = enclosingClass(element);
if (elem == null) {
return null;
}
return elem.getQualifiedName();
}
/**
* Returns a verbose name that identifies the element.
*
* @param elt the element whose name to obtain
* @return the qualified name of the given element
*/
public static String getQualifiedName(Element elt) {
if (elt.getKind() == ElementKind.PACKAGE || isTypeElement(elt)) {
Name n = getQualifiedClassName(elt);
if (n == null) {
return "Unexpected element: " + elt;
}
return n.toString();
} else {
return getQualifiedName(elt.getEnclosingElement()) + "." + elt;
}
}
/**
* Returns the binary name of the given type.
*
* @param te a type
* @return the binary name of the type
*/
@SuppressWarnings("signature:return") // string manipulation
public static @BinaryName String getBinaryName(TypeElement te) {
Element enclosing = te.getEnclosingElement();
String simpleName = te.getSimpleName().toString();
if (enclosing == null) { // is this possible?
return simpleName;
}
if (ElementUtils.isTypeElement(enclosing)) {
return getBinaryName((TypeElement) enclosing) + "$" + simpleName;
} else if (enclosing.getKind() == ElementKind.PACKAGE) {
PackageElement pe = (PackageElement) enclosing;
if (pe.isUnnamed()) {
return simpleName;
} else {
return pe.getQualifiedName() + "." + simpleName;
}
} else {
// This case occurs for anonymous inner classes. Fall back to the flatname method.
return ((ClassSymbol) te).flatName().toString();
}
}
/**
* Returns the canonical representation of the method declaration, which contains simple names of
* the types only.
*
* @param element a method declaration
* @return the simple name of the method, followed by the simple names of the formal parameter
* types
*/
public static String getSimpleSignature(ExecutableElement element) {
// note: constructor simple name is <init>
StringJoiner sj = new StringJoiner(",", element.getSimpleName() + "(", ")");
for (Iterator<? extends VariableElement> i = element.getParameters().iterator();
i.hasNext(); ) {
sj.add(TypesUtils.simpleTypeName(i.next().asType()));
}
return sj.toString();
}
/**
* Returns a user-friendly name for the given method. Does not return {@code "<init>"} or {@code
* "<clinit>"} as ExecutableElement.getSimpleName() does.
*
* @param element a method declaration
* @return a user-friendly name for the method
*/
public static CharSequence getSimpleNameOrDescription(ExecutableElement element) {
Name result = element.getSimpleName();
switch (result.toString()) {
case "<init>":
return element.getEnclosingElement().getSimpleName();
case "<clinit>":
return "class initializer";
default:
return result;
}
}
/**
* Check if the element is an element for 'java.lang.Object'
*
* @param element the type element
* @return true iff the element is java.lang.Object element
*/
public static boolean isObject(TypeElement element) {
return element.getQualifiedName().contentEquals("java.lang.Object");
}
/**
* Check if the element is an element for 'java.lang.String'
*
* @param element the type element
* @return true iff the element is java.lang.String element
*/
public static boolean isString(TypeElement element) {
return element.getQualifiedName().contentEquals("java.lang.String");
}
/**
* Returns true if the element is a reference to a compile-time constant.
*
* @param elt an element
* @return true if the element is a reference to a compile-time constant
*/
public static boolean isCompileTimeConstant(Element elt) {
return elt != null
&& (elt.getKind() == ElementKind.FIELD || elt.getKind() == ElementKind.LOCAL_VARIABLE)
&& ((VariableElement) elt).getConstantValue() != null;
}
/**
* Checks whether a given element came from a source file.
*
* <p>By contrast, {@link ElementUtils#isElementFromByteCode(Element)} returns true if there is a
* classfile for the given element, even if there is also a source file.
*
* @param element the element to check, or null
* @return true if a source file containing the element is being compiled
*/
public static boolean isElementFromSourceCode(@Nullable Element element) {
if (element == null) {
return false;
}
TypeElement enclosingClass = enclosingClass(element);
if (enclosingClass == null) {
throw new BugInCF("enclosingClass(%s) is null", element);
}
return isElementFromSourceCodeImpl((Symbol.ClassSymbol) enclosingClass);
}
/**
* Checks whether a given ClassSymbol came from a source file.
*
* <p>By contrast, {@link ElementUtils#isElementFromByteCode(Element)} returns true if there is a
* classfile for the given element, even if there is also a source file.
*
* @param symbol the class to check
* @return true if a source file containing the class is being compiled
*/
private static boolean isElementFromSourceCodeImpl(Symbol.ClassSymbol symbol) {
// This is a bit of a hack to avoid treating JDK as source files. JDK files' toUri() method
// returns just the name of the file (e.g. "Object.java"), but any file actually being
// compiled returns a file URI to the source file.
return symbol.sourcefile != null
&& symbol.sourcefile.getKind() == JavaFileObject.Kind.SOURCE
&& symbol.sourcefile.toUri().toString().startsWith("file:");
}
/**
* Returns true if the element is declared in ByteCode. Always return false if elt is a package.
*
* @param elt some element
* @return true if the element is declared in ByteCode
*/
public static boolean isElementFromByteCode(@Nullable Element elt) {
if (elt == null) {
return false;
}
if (elt instanceof Symbol.ClassSymbol) {
Symbol.ClassSymbol clss = (Symbol.ClassSymbol) elt;
if (null != clss.classfile) {
// The class file could be a .java file
return clss.classfile.getKind() == Kind.CLASS;
} else {
return elt.asType().getKind().isPrimitive();
}
}
return isElementFromByteCode(elt.getEnclosingElement());
}
/**
* Returns the path to the source file containing {@code element}, which must be from source code.
*
* @param element the type element to look at
* @return path to the source file containing {@code element}
*/
public static String getSourceFilePath(TypeElement element) {
return ((ClassSymbol) element).sourcefile.toUri().getPath();
}
/**
* Returns the field of the class or {@code null} if not found.
*
* @param type TypeElement to search
* @param name name of a field
* @return The VariableElement for the field if it was found, null otherwise
*/
public static @Nullable VariableElement findFieldInType(TypeElement type, String name) {
for (VariableElement field : ElementFilter.fieldsIn(type.getEnclosedElements())) {
if (field.getSimpleName().contentEquals(name)) {
return field;
}
}
return null;
}
/**
* Returns the elements of the fields whose simple names are {@code names} and are declared in
* {@code type}.
*
* <p>If a field isn't declared in {@code type}, its element isn't included in the returned set.
* If none of the fields is declared in {@code type}, the empty set is returned.
*
* @param type where to look for fields
* @param names simple names of fields that might be declared in {@code type}
* @return the elements of the fields whose simple names are {@code names} and are declared in
* {@code type}
*/
public static Set<VariableElement> findFieldsInType(TypeElement type, Collection<String> names) {
Set<VariableElement> results = new HashSet<>();
for (VariableElement field : ElementFilter.fieldsIn(type.getEnclosedElements())) {
if (names.contains(field.getSimpleName().toString())) {
results.add(field);
}
}
return results;
}
/**
* Returns non-private field elements, and side-effects {@code names} to remove them. For every
* field name in {@code names} that is declared in {@code type} or a supertype, add its element to
* the returned set and remove it from {@code names}.
*
* <p>When this routine returns, the combination of the return value and {@code names} has the
* same cardinality, and represents the same fields, as {@code names} did when the method was
* called.
*
* @param type where to look for fields
* @param names simple names of fields that might be declared in {@code type} or a supertype
* (Names that are found are removed from this list.)
* @return the {@code VariableElement}s for non-private fields that are declared in {@code type}
* whose simple names were in {@code names} when the method was called.
*/
public static Set<VariableElement> findFieldsInTypeOrSuperType(
TypeMirror type, Collection<String> names) {
int origCardinality = names.size();
Set<VariableElement> elements = new HashSet<>();
findFieldsInTypeOrSuperType(type, names, elements);
// Since names may contain duplicates, I don't trust the claim in the documentation about
// cardinality. (Does any code depend on the invariant, though?)
if (origCardinality != names.size() + elements.size()) {
throw new BugInCF("Bad sizes: %d != %d + %d", origCardinality, names.size(), elements.size());
}
return elements;
}
/**
* Side-effects both {@code foundFields} (which starts empty) and {@code notFound}, conceptually
* moving elements from {@code notFound} to {@code foundFields}.
*/
private static void findFieldsInTypeOrSuperType(
TypeMirror type, Collection<String> notFound, Set<VariableElement> foundFields) {
if (TypesUtils.isObject(type)) {
return;
}
TypeElement elt = TypesUtils.getTypeElement(type);
assert elt != null : "@AssumeAssertion(nullness): assumption";
Set<VariableElement> fieldElts = findFieldsInType(elt, notFound);
for (VariableElement field : new HashSet<VariableElement>(fieldElts)) {
if (!field.getModifiers().contains(Modifier.PRIVATE)) {
notFound.remove(field.getSimpleName().toString());
} else {
fieldElts.remove(field);
}
}
foundFields.addAll(fieldElts);
if (!notFound.isEmpty()) {
findFieldsInTypeOrSuperType(elt.getSuperclass(), notFound, foundFields);
}
}
/**
* Returns true if {@code element} is "com.sun.tools.javac.comp.Resolve$SymbolNotFoundError".
*
* @param element the element to test
* @return true if {@code element} is "com.sun.tools.javac.comp.Resolve$SymbolNotFoundError"
*/
public static boolean isError(Element element) {
return element.getClass().getName()
== "com.sun.tools.javac.comp.Resolve$SymbolNotFoundError"; // interned
}
/**
* Does the given element need a receiver for accesses? For example, an access to a local variable
* does not require a receiver.
*
* @param element the element to test
* @return whether the element requires a receiver for accesses
*/
public static boolean hasReceiver(Element element) {
if (element.getKind() == ElementKind.CONSTRUCTOR) {
// The enclosing element of a constructor is the class it creates.
// A constructor can only have a receiver if the class it creates has an outer type.
TypeMirror t = element.getEnclosingElement().asType();
return TypesUtils.hasEnclosingType(t);
} else if (element.getKind() == ElementKind.FIELD) {
if (ElementUtils.isStatic(element)
// Artificial fields in interfaces are not marked as static, so check that
// the field is not declared in an interface.
|| element.getEnclosingElement().getKind().isInterface()) {
return false;
} else {
// In constructors, the element for "this" is a non-static field, but that field
// does not have a receiver.
return !element.getSimpleName().contentEquals("this");
}
}
return element.getKind() == ElementKind.METHOD && !ElementUtils.isStatic(element);
}
/**
* Returns a type's superclass, or null if it does not have a superclass (it is object or an
* interface, or the superclass is not on the classpath).
*
* @param typeElt a type element
* @return the superclass of {@code typeElt}
*/
public static @Nullable TypeElement getSuperClass(TypeElement typeElt) {
TypeMirror superTypeMirror;
try {
superTypeMirror = typeElt.getSuperclass();
} catch (com.sun.tools.javac.code.Symbol.CompletionFailure cf) {
// Looking up a supertype failed. This sometimes happens
// when transitive dependencies are not on the classpath.
// As javac didn't complain, let's also not complain.
return null;
}
if (superTypeMirror == null || superTypeMirror.getKind() == TypeKind.NONE) {
return null;
} else {
return (TypeElement) ((DeclaredType) superTypeMirror).asElement();
}
}
/**
* Determine all type elements for the supertypes of the given type element. This is the
* transitive closure of the extends and implements clauses.
*
* <p>TODO: can we learn from the implementation of
* com.sun.tools.javac.model.JavacElements.getAllMembers(TypeElement)?
*
* @param type the type whose supertypes to return
* @param elements the Element utilities
* @return supertypes of {@code type}
*/
public static List<TypeElement> getSuperTypes(TypeElement type, Elements elements) {
if (type == null) {
return Collections.emptyList();
}
List<TypeElement> superelems = new ArrayList<>();
// Set up a stack containing type, which is our starting point.
Deque<TypeElement> stack = new ArrayDeque<>();
stack.push(type);
while (!stack.isEmpty()) {
TypeElement current = stack.pop();
// For each direct supertype of the current type element, if it
// hasn't already been visited, push it onto the stack and
// add it to our superelems set.
TypeElement supercls = ElementUtils.getSuperClass(current);
if (supercls != null) {
if (!superelems.contains(supercls)) {
stack.push(supercls);
superelems.add(supercls);
}
}
for (TypeMirror supertypeitf : current.getInterfaces()) {
TypeElement superitf = (TypeElement) ((DeclaredType) supertypeitf).asElement();
if (!superelems.contains(superitf)) {
stack.push(superitf);
superelems.add(superitf);
}
}
}
// Include java.lang.Object as implicit superclass for all classes and interfaces.
TypeElement jlobject = elements.getTypeElement("java.lang.Object");
if (!superelems.contains(jlobject)) {
superelems.add(jlobject);
}
return Collections.unmodifiableList(superelems);
}
/**
* Return all fields declared in the given type or any superclass/interface.
*
* <p>TODO: should this use javax.lang.model.util.Elements.getAllMembers(TypeElement) instead of
* our own getSuperTypes?
*
* @param type the type whose fields to return
* @param elements the Element utilities
* @return fields of {@code type}
*/
public static List<VariableElement> getAllFieldsIn(TypeElement type, Elements elements) {
List<VariableElement> fields =
new ArrayList<>(ElementFilter.fieldsIn(type.getEnclosedElements()));
List<TypeElement> alltypes = getSuperTypes(type, elements);
for (TypeElement atype : alltypes) {
fields.addAll(ElementFilter.fieldsIn(atype.getEnclosedElements()));
}
return Collections.unmodifiableList(fields);
}
/**
* Return all methods declared in the given type or any superclass/interface. Note that no
* constructors will be returned.
*
* <p>TODO: should this use javax.lang.model.util.Elements.getAllMembers(TypeElement) instead of
* our own getSuperTypes?
*
* @param type the type whose methods to return
* @param elements the Element utilities
* @return methods of {@code type}
*/
public static List<ExecutableElement> getAllMethodsIn(TypeElement type, Elements elements) {
List<ExecutableElement> meths =
new ArrayList<>(ElementFilter.methodsIn(type.getEnclosedElements()));
List<TypeElement> alltypes = getSuperTypes(type, elements);
for (TypeElement atype : alltypes) {
meths.addAll(ElementFilter.methodsIn(atype.getEnclosedElements()));
}
return Collections.unmodifiableList(meths);
}
/**
* Return all nested/inner classes/interfaces declared in the given type.
*
* @param type a type
* @return all nested/inner classes/interfaces declared in {@code type}
*/
public static List<TypeElement> getAllTypeElementsIn(TypeElement type) {
return ElementFilter.typesIn(type.getEnclosedElements());
}
/** The set of kinds that represent types. */
private static final Set<ElementKind> typeElementKinds;
static {
typeElementKinds = EnumSet.noneOf(ElementKind.class);
for (ElementKind kind : ElementKind.values()) {
if (kind.isClass() || kind.isInterface()) {
typeElementKinds.add(kind);
}
}
}
/**
* Return the set of kinds that represent classes.
*
* @return the set of kinds that represent classes
* @deprecated use {@link #typeElementKinds()}
*/
@Deprecated // use typeElementKinds
public static Set<ElementKind> classElementKinds() {
return typeElementKinds();
}
/**
* Return the set of kinds that represent classes.
*
* @return the set of kinds that represent classes
*/
public static Set<ElementKind> typeElementKinds() {
return typeElementKinds;
}
/**
* Is the given element kind a type, i.e., a class, enum, interface, or annotation type.
*
* @param element the element to test
* @return true, iff the given kind is a class kind
* @deprecated use {@link #isTypeElement}
*/
@Deprecated // use isTypeElement
public static boolean isClassElement(Element element) {
return isTypeElement(element);
}
/**
* Is the given element kind a type, i.e., a class, enum, interface, or annotation type.
*
* @param element the element to test
* @return true, iff the given kind is a class kind
*/
public static boolean isTypeElement(Element element) {
return typeElementKinds().contains(element.getKind());
}
/**
* Return true if the element is a type declaration.
*
* @param elt the element to test
* @return true if the argument is a type declaration
*/
public static boolean isTypeDeclaration(Element elt) {
return isClassElement(elt) || elt.getKind() == ElementKind.TYPE_PARAMETER;
}
/**
* Return true if the element is a binding variable.
*
* <p>Note: This is to conditionally support Java 15 instanceof pattern matching. When available,
* this should use {@code ElementKind.BINDING_VARIABLE} directly.
*
* @param element the element to test
* @return true if the element is a binding variable
*/
public static boolean isBindingVariable(Element element) {
return "BINDING_VARIABLE".equals(element.getKind().name());
}
/**
* Check that a method Element matches a signature.
*
* <p>Note: Matching the receiver type must be done elsewhere as the Element receiver type is only
* populated when annotated.
*
* @param method the method Element to be tested
* @param methodName the goal method name
* @param parameters the goal formal parameter Classes
* @return true if the method matches the methodName and parameters
*/
public static boolean matchesElement(
ExecutableElement method, String methodName, Class<?>... parameters) {
if (!method.getSimpleName().contentEquals(methodName)) {
return false;
}
if (method.getParameters().size() != parameters.length) {
return false;
} else {
for (int i = 0; i < method.getParameters().size(); i++) {
if (!method.getParameters().get(i).asType().toString().equals(parameters[i].getName())) {
return false;
}
}
}
return true;
}
/** Returns true if the given element is, or overrides, method. */
public static boolean isMethod(
ExecutableElement questioned, ExecutableElement method, ProcessingEnvironment env) {
TypeElement enclosing = (TypeElement) questioned.getEnclosingElement();
return questioned.equals(method)
|| env.getElementUtils().overrides(questioned, method, enclosing);
}
/**
* Given an annotation name, return true if the element has the annotation of that name.
*
* @param element the element
* @param annotName name of the annotation
* @return true if the element has the annotation of that name
*/
public static boolean hasAnnotation(Element element, String annotName) {
for (AnnotationMirror anm : element.getAnnotationMirrors()) {
if (AnnotationUtils.areSameByName(anm, annotName)) {
return true;
}
}
return false;
}
/**
* Returns the TypeElement for the given class.
*
* @param processingEnv the processing environment
* @param clazz a class
* @return the TypeElement for the class
*/
public static TypeElement getTypeElement(ProcessingEnvironment processingEnv, Class<?> clazz) {
@CanonicalName String className = clazz.getCanonicalName();
if (className == null) {
throw new Error("Anonymous class " + clazz + " has no canonical name");
}
return processingEnv.getElementUtils().getTypeElement(className);
}
/**
* Get all the supertypes of a given type, including the type itself. The result includes both
* superclasses and implemented interfaces.
*
* @param type a type
* @param env the processing environment
* @return list including the type and all its supertypes, with a guarantee that direct supertypes
* (i.e. those that appear in extends or implements clauses) appear before indirect supertypes
*/
public static List<TypeElement> getAllSupertypes(TypeElement type, ProcessingEnvironment env) {
Context ctx = ((JavacProcessingEnvironment) env).getContext();
com.sun.tools.javac.code.Types javacTypes = com.sun.tools.javac.code.Types.instance(ctx);
return CollectionsPlume.<Type, TypeElement>mapList(
t -> (TypeElement) t.tsym, javacTypes.closure(((Symbol) type).type));
}
/**
* Returns the methods that are overriden or implemented by a given method.
*
* @param m a method
* @param types the type utilities
* @return the methods that {@code m} overrides or implements
*/
public static Set<? extends ExecutableElement> getOverriddenMethods(
ExecutableElement m, Types types) {
JavacTypes t = (JavacTypes) types;
return t.getOverriddenMethods(m);
}
/**
* Returns true if the two elements are in the same class. The two elements should be class
* members, such as methods or fields.
*
* @param e1 an element
* @param e2 an element
* @return true if the two elements are in the same class
*/
public static boolean inSameClass(Element e1, Element e2) {
return e1.getEnclosingElement().equals(e2.getEnclosingElement());
}
}