org.checkerframework.javacutil.ElementUtils Maven / Gradle / Ivy
Show all versions of checker Show documentation
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 org.checkerframework.checker.nullness.qual.NonNull;
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.ArraySet;
import org.plumelib.util.CollectionsPlume;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
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.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;
/**
* 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.");
}
/** The value of Flags.COMPACT_RECORD_CONSTRUCTOR which does not exist in Java 9 or 11. */
private static final long Flags_COMPACT_RECORD_CONSTRUCTOR = 1L << 51;
/** The value of Flags.GENERATED_MEMBER which does not exist in Java 9 or 11. */
private static final long Flags_GENERATED_MEMBER = 16777216;
/**
* Returns the innermost type element that is, or encloses, the given element.
*
* Note that in this code:
*
*
{@code
* class Outer {
* static class Inner { }
* }
* }
*
* {@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(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(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(Element elem) {
Element result = elem;
while (result != null && result.getKind() != ElementKind.PACKAGE) {
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.
*
* 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
* @param elements the element
* @return the parent package element or {@code null}
*/
public static @Nullable PackageElement parentPackage(PackageElement elem, Elements elements) {
// 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 elements.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 enclosingTypeElement(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 = enclosingTypeElement(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.type.incompatible") // 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
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 ""} or {@code
* ""} as ExecutableElement.getSimpleName() does.
*
* @param element a method declaration
* @return a user-friendly name for the method
* @deprecated use {@link #getSimpleDescription}
*/
@Deprecated // 2023-06-01
public static CharSequence getSimpleNameOrDescription(ExecutableElement element) {
Name result = element.getSimpleName();
switch (result.toString()) {
case "":
return element.getEnclosingElement().getSimpleName();
case "":
return "class initializer";
default:
return result;
}
}
/**
* Returns a user-friendly name for the given method, which includes the name of the enclosing
* type. Does not return {@code ""} or {@code ""} as
* ExecutableElement.getSimpleName() does.
*
* @param element a method declaration
* @return a user-friendly name for the method
*/
public static CharSequence getSimpleDescription(ExecutableElement element) {
String enclosingTypeName =
((TypeElement) element.getEnclosingElement()).getSimpleName().toString();
Name methodName = element.getSimpleName();
switch (methodName.toString()) {
case "":
return enclosingTypeName + " constructor";
case "":
return "class initializer for " + enclosingTypeName;
default:
return enclosingTypeName + "." + methodName;
}
}
/**
* 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(@Nullable 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.
*
* 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 enclosingTypeElement = enclosingTypeElement(element);
if (enclosingTypeElement == null) {
throw new BugInCF("enclosingTypeElement(%s) is null", element);
}
return isElementFromSourceCodeImpl((Symbol.ClassSymbol) enclosingTypeElement);
}
/**
* Checks whether a given ClassSymbol came from a source file.
*
*
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() == JavaFileObject.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) {
String path = ((ClassSymbol) element).sourcefile.toUri().getPath();
if (path == null) {
throw new BugInCF("Unexpected null path for TypeElement: " + element);
}
return path;
}
/**
* Returns the field of the class or {@code null} if not found.
*
* @param type the 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 in {@code names} and are declared
* in {@code type}.
*
*
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 findFieldsInType(
TypeElement type, Collection names) {
Set results = ArraySet.newArraySetOrHashSet(names.size());
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}.
*
* 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 findFieldsInTypeOrSuperType(
TypeMirror type, Collection names) {
int origCardinality = names.size();
Set elements = ArraySet.newArraySetOrHashSet(origCardinality);
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 ; names=%s elements=%s",
origCardinality, names.size(), elements.size(), names, elements);
}
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 notFound, Set foundFields) {
if (TypesUtils.isObject(type)) {
return;
}
TypeElement elt = TypesUtils.getTypeElement(type);
assert elt != null : "@AssumeAssertion(nullness): assumption";
Set fieldElts = findFieldsInType(elt, notFound);
// Use a new list to avoid a ConcurrentModificationException.
for (VariableElement field : new ArrayList<>(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.
*
* 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 getSuperTypes(TypeElement type, Elements elements) {
if (type == null) {
return Collections.emptyList();
}
List superElems = new ArrayList<>();
// Set up a stack containing `type`, which is our starting point.
Deque 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);
}
/**
* Determine all type elements for the direct supertypes of the given type element. This is the
* union of the extends and implements clauses.
*
* @param type the type whose supertypes to return
* @param elements the Element utilities
* @return direct supertypes of {@code type}
*/
public static List getDirectSuperTypeElements(
TypeElement type, Elements elements) {
TypeMirror superclass = type.getSuperclass();
List extends TypeMirror> interfaces = type.getInterfaces();
List result = new ArrayList(interfaces.size() + 1);
if (superclass.getKind() != TypeKind.NONE) {
@SuppressWarnings("nullness:assignment") // Not null because the TypeKind is not NONE.
@NonNull TypeElement superclassElement = TypesUtils.getTypeElement(superclass);
result.add(superclassElement);
}
for (TypeMirror interfac : interfaces) {
@SuppressWarnings("nullness:assignment") // every interface is a type
@NonNull TypeElement interfaceElt = TypesUtils.getTypeElement(interfac);
result.add(interfaceElt);
}
return result;
}
/**
* Return all fields declared in the given type or any superclass/interface.
*
* 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 getAllFieldsIn(TypeElement type, Elements elements) {
// ElementFilter.fieldsIn returns a new list
List fields = ElementFilter.fieldsIn(type.getEnclosedElements());
List alltypes = getSuperTypes(type, elements);
for (TypeElement atype : alltypes) {
fields.addAll(ElementFilter.fieldsIn(atype.getEnclosedElements()));
}
return Collections.unmodifiableList(fields);
}
/**
* Returns all enum constants declared in the given enumeration.
*
* @param type an Enum type
* @return all enum constants declared in the given enumeration
*/
public static List getEnumConstants(TypeElement type) {
List extends Element> enclosedElements = type.getEnclosedElements();
List enumConstants = new ArrayList<>(enclosedElements.size());
for (Element e : enclosedElements) {
if (e.getKind() == ElementKind.ENUM_CONSTANT) {
enumConstants.add((VariableElement) e);
}
}
return enumConstants;
}
/**
* Return all methods declared in the given type or any superclass/interface. Note that no
* constructors will be returned.
*
* 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 getAllMethodsIn(TypeElement type, Elements elements) {
// ElementFilter.fieldsIn returns a new list
List meths = ElementFilter.methodsIn(type.getEnclosedElements());
List 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 getAllTypeElementsIn(TypeElement type) {
return ElementFilter.typesIn(type.getEnclosedElements());
}
/** The set of kinds that represent types. */
private static final Set 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
*/
public static Set 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
*/
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 isTypeElement(elt) || elt.getKind() == ElementKind.TYPE_PARAMETER;
}
/** The set of kinds that represent local variables. */
private static final Set localVariableElementKinds =
EnumSet.of(
ElementKind.LOCAL_VARIABLE,
ElementKind.RESOURCE_VARIABLE,
ElementKind.EXCEPTION_PARAMETER);
/**
* Return true if the element is a local variable.
*
* @param elt the element to test
* @return true if the argument is a local variable
*/
public static boolean isLocalVariable(Element elt) {
return localVariableElementKinds.contains(elt.getKind());
}
/**
* Return true if the element is a binding variable.
*
* This implementation compiles and runs under JDK 8 and 11 as well as versions that contain
* {@code ElementKind.BINDING_VARIABLE}.
*
* @param element the element to test
* @return true if the element is a binding variable
*/
public static boolean isBindingVariable(Element element) {
return SystemUtil.jreVersion >= 16 && "BINDING_VARIABLE".equals(element.getKind().name());
}
/**
* Returns true if the element is a record accessor method.
*
* @param methodElement a method element
* @return true if the element is a record accessor method
*/
public static boolean isRecordAccessor(ExecutableElement methodElement) {
// Can only be a record accessor if it has no parameters.
if (!methodElement.getParameters().isEmpty()) {
return false;
}
TypeElement enclosing = (TypeElement) methodElement.getEnclosingElement();
if (isRecordElement(enclosing)) {
String methodName = methodElement.getSimpleName().toString();
List extends Element> encloseds = enclosing.getEnclosedElements();
for (Element enclosed : encloseds) {
if (isRecordComponentElement(enclosed)
&& enclosed.getSimpleName().toString().equals(methodName)) {
return true;
}
}
}
return false;
}
/**
* Returns true if the given {@link Element} is part of a record that has been automatically
* generated by the compiler. This can be a field that is derived from the record's header field
* list, or an automatically generated canonical constructor.
*
* @param e the {@link Element} for a member of a record
* @return true if the given element is generated by the compiler
*/
public static boolean isAutoGeneratedRecordMember(Element e) {
if (!(e instanceof Symbol)) {
return false;
}
// Generated constructors seem to get GENERATEDCONSTR even though the documentation
// seems to imply they would get GENERATED_MEMBER like the fields do.
return (((Symbol) e).flags() & (Flags_GENERATED_MEMBER | Flags.GENERATEDCONSTR)) != 0;
}
/**
* Check that a method Element matches a signature.
*
*
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, {@code method}.
*
* @param questioned an element that might override {@code method}
* @param method a method that might be overridden
* @param env the processing environment
* @return true if {@code questioned} is, or overrides, {@code method}
*/
public static boolean isMethod(
ExecutableElement questioned, ExecutableElement method, ProcessingEnvironment env) {
return questioned.equals(method)
|| env.getElementUtils()
.overrides(
questioned, method, (TypeElement) questioned.getEnclosingElement());
}
/**
* Given an annotation name, return true if the element has the annotation of that name.
*
*
It is more efficient to use {@code Element#getAnnotation(Class)}, but note that both
* methods ignore types from annotation files, such as stub or ajava files.
*
*
To include types from annotation files, use {@code AnnotatedTypeFactory#fromElement} or
* {@code AnnotatedTypeFactory#getDeclAnnotations}.
*
* @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 BugInCF("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 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.mapList(
t -> (TypeElement) t.tsym, javacTypes.closure(((Symbol) type).type));
}
/**
* Returns the methods that are overridden 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());
}
/**
* Determine whether the given element is of Kind RECORD, in a way that works on all versions of
* Java.
*
* @param elt the element to test
* @return whether the element is of the kind RECORD
*/
public static boolean isRecordElement(Element elt) {
ElementKind kind = elt.getKind();
return kind.name().equals("RECORD");
}
/**
* Determine whether the given element is of Kind RECORD_COMPONENT, in a way that works on all
* versions of Java.
*
* @param elt the element to test
* @return whether the element is of the kind RECORD_COMPONENT
*/
public static boolean isRecordComponentElement(Element elt) {
ElementKind kind = elt.getKind();
return kind.name().equals("RECORD_COMPONENT");
}
/**
* Calls getKind() on the given Element, but returns CLASS if the ElementKind is RECORD. This is
* needed because the Checker Framework runs on JDKs before the RECORD item was added, so RECORD
* can't be used in case statements, and usually we want to treat them the same as classes.
*
* @param elt the element to get the kind for
* @return the kind of the element, but CLASS if the kind was RECORD
*/
public static ElementKind getKindRecordAsClass(Element elt) {
if (isRecordElement(elt)) {
return ElementKind.CLASS;
}
return elt.getKind();
}
/** The {@code TypeElement.getRecordComponents()} method. */
private static final @Nullable Method TYPEELEMENT_GETRECORDCOMPONENTS;
static {
if (SystemUtil.jreVersion >= 16) {
try {
TYPEELEMENT_GETRECORDCOMPONENTS =
TypeElement.class.getMethod("getRecordComponents");
} catch (NoSuchMethodException e) {
throw new BugInCF("Cannot access TypeElement.getRecordComponents()", e);
}
} else {
TYPEELEMENT_GETRECORDCOMPONENTS = null;
}
}
/**
* Calls getRecordComponents on the given TypeElement. Uses reflection because this method is
* not available before JDK 16. On earlier JDKs, which don't support records anyway, an
* exception is thrown.
*
* @param element the type element to call getRecordComponents on
* @return the return value of calling getRecordComponents, or empty list if the method is not
* available
*/
@SuppressWarnings({"unchecked", "nullness"}) // because of cast from reflection
public static List extends Element> getRecordComponents(TypeElement element) {
try {
return (@NonNull List extends Element>)
TYPEELEMENT_GETRECORDCOMPONENTS.invoke(element);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new Error("Cannot call TypeElement.getRecordComponents()", e);
}
}
/**
* Check if the given element is a compact canonical record constructor.
*
* @param elt the element to check
* @return true if the element is a compact canonical constructor of a record
*/
public static boolean isCompactCanonicalRecordConstructor(Element elt) {
return elt.getKind() == ElementKind.CONSTRUCTOR
&& (((Symbol) elt).flags() & Flags_COMPACT_RECORD_CONSTRUCTOR) != 0;
}
/**
* Returns true iff the given element is a resource variable.
*
* @param elt an element; may be null, in which case this method always returns false
* @return true iff the given element represents a resource variable
*/
public static boolean isResourceVariable(@Nullable Element elt) {
return elt != null && elt.getKind() == ElementKind.RESOURCE_VARIABLE;
}
/**
* Returns true if the given element is a getter method. A getter method is an instance method
* with no formal parameters, whose name starts with "get", "is", "not", or "has" followed by an
* upper-case letter.
*
* @param methodElt a method
* @return true if the given element is a getter method
*/
public static boolean isGetter(@Nullable ExecutableElement methodElt) {
if (methodElt == null) {
return false;
}
if (isStatic(methodElt)) {
return false;
}
if (!methodElt.getParameters().isEmpty()) {
return false;
}
// I could check that the method has a non-void return type,
// and that methods with prefix "is", "has", and "not" return boolean.
// Constructors and initializers don't have a name starting with a character.
String name = methodElt.getSimpleName().toString();
// I expect this code is more efficient than use of a regular expression.
boolean nameOk =
nameStartsWith(name, "get")
|| nameStartsWith(name, "is")
|| nameStartsWith(name, "not")
|| nameStartsWith(name, "has");
if (!nameOk) {
return false;
}
return true;
}
/**
* Returns true if the name starts with the given prefix, followed by an upper-case letter.
*
* @param name a name
* @param prefix a prefix
* @return true if the name starts with the given prefix, followed by an upper-case letter
*/
private static boolean nameStartsWith(String name, String prefix) {
return name.startsWith(prefix)
&& name.length() > prefix.length()
&& Character.isUpperCase(name.charAt(prefix.length()));
}
}