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

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

There is a newer version: 3.48.2
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 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;
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;

/**
 * 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, 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") // 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 enclosingClass = enclosingTypeElement(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. * *

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) { return ((ClassSymbol) element).sourcefile.toUri().getPath(); } /** * 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 * @deprecated use {@link #isTypeElement} */ @Deprecated // 2020-12-11 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; } /** 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 (enclosing.getKind().toString().equals("RECORD")) { String methodName = methodElement.getSimpleName().toString(); List encloseds = enclosing.getEnclosedElements(); for (Element enclosed : encloseds) { if (enclosed.getKind().toString().equals("RECORD_COMPONENT") && 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, @Nullable ExecutableElement method, ProcessingEnvironment env) { if (method == null) { return false; } 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()); } /** * 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) { ElementKind kind = elt.getKind(); if (kind.name().equals("RECORD")) { kind = ElementKind.CLASS; } return kind; } /** The {@code TypeElement.getRecordComponents()} method. */ private static final @Nullable Method getRecordComponentsMethod; static { if (SystemUtil.jreVersion >= 16) { try { getRecordComponentsMethod = TypeElement.class.getMethod("getRecordComponents"); } catch (NoSuchMethodException e) { throw new BugInCF("Cannot access TypeElement.getRecordComponents()", e); } } else { getRecordComponentsMethod = 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) getRecordComponentsMethod.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