All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.checkerframework.javacutil.ElementUtils Maven / Gradle / Ivy

Go to download

The Checker Framework enhances Java's type system to make it more powerful and useful. This lets software developers detect and prevent errors in their Java programs. The Checker Framework includes compiler plug-ins ("checkers") that find bugs or verify their absence. It also permits you to write your own compiler plug-ins.

There is a newer version: 3.42.0-eisop4
Show newest version
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 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 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 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 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 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 getRecordComponents(TypeElement element) { try { return (@NonNull List) 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())); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy