| package org.checkerframework.javacutil; |
| |
| /*>>> |
| import org.checkerframework.checker.nullness.qual.Nullable; |
| */ |
| |
| import static com.sun.tools.javac.code.Flags.ABSTRACT; |
| import static com.sun.tools.javac.code.Flags.EFFECTIVELY_FINAL; |
| import static com.sun.tools.javac.code.Flags.FINAL; |
| |
| import com.sun.tools.javac.code.Symbol; |
| import java.util.ArrayDeque; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Deque; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| import javax.annotation.processing.ProcessingEnvironment; |
| 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; |
| |
| /** A Utility class for analyzing {@code Element}s. */ |
| public class ElementUtils { |
| |
| // Class cannot be instantiated. |
| private ElementUtils() { |
| throw new AssertionError("Class ElementUtils cannot be instantiated."); |
| } |
| |
| /** |
| * Returns the innermost type element enclosing the given element |
| * |
| * @param elem the enclosed element of a class |
| * @return the innermost type element |
| */ |
| public static TypeElement enclosingClass(final Element elem) { |
| Element result = elem; |
| while (result != null && !result.getKind().isClass() && !result.getKind().isInterface()) { |
| /*@Nullable*/ Element encl = result.getEnclosingElement(); |
| result = encl; |
| } |
| return (TypeElement) result; |
| } |
| |
| /** |
| * Returns the innermost package element enclosing the given element. The same effect as {@link |
| * javax.lang.model.util.Elements#getPackageOf(Element)}. Returns the element itself if it is a |
| * package. |
| * |
| * @param elem the enclosed element of a package |
| * @return the innermost package element |
| */ |
| public static PackageElement enclosingPackage(final Element elem) { |
| Element result = elem; |
| while (result != null && result.getKind() != ElementKind.PACKAGE) { |
| /*@Nullable*/ Element encl = result.getEnclosingElement(); |
| result = encl; |
| } |
| return (PackageElement) result; |
| } |
| |
| /** |
| * Returns the "parent" package element for the given package element. For package "A.B" it |
| * gives "A". For package "A" it gives the default package. For the default package it returns |
| * null; |
| * |
| * <p>Note that packages are not enclosed within each other, we have to manually climb the |
| * namespaces. Calling "enclosingPackage" on a package element returns the package element |
| * itself again. |
| * |
| * @param elem the package to start from |
| * @return the parent package element |
| */ |
| public static PackageElement parentPackage(final Elements e, final PackageElement elem) { |
| // 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() && fqn.contains(".")) { |
| fqn = fqn.substring(0, fqn.lastIndexOf('.')); |
| return e.getPackageElement(fqn); |
| } |
| 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() & ABSTRACT) != 0) { |
| return true; |
| } |
| return (sym.flags() & (FINAL | 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. |
| * |
| * @return the type for the element used as a value |
| */ |
| public static TypeMirror getType(Element element) { |
| if (element.getKind() == ElementKind.METHOD) { |
| return ((ExecutableElement) element).getReturnType(); |
| } else if (element.getKind() == ElementKind.CONSTRUCTOR) { |
| return enclosingClass(element).asType(); |
| } else { |
| return element.asType(); |
| } |
| } |
| |
| /** |
| * Returns the qualified name of the inner most class enclosing the provided {@code Element} |
| * |
| * @param element an element enclosed by a class, or a {@code TypeElement} |
| * @return the qualified {@code Name} of the innermost class enclosing the element |
| */ |
| public static /*@Nullable*/ Name getQualifiedClassName(Element element) { |
| if (element.getKind() == ElementKind.PACKAGE) { |
| PackageElement elem = (PackageElement) element; |
| return elem.getQualifiedName(); |
| } |
| |
| TypeElement elem = enclosingClass(element); |
| if (elem == null) { |
| return null; |
| } |
| |
| return elem.getQualifiedName(); |
| } |
| |
| /** Returns a verbose name that identifies the element. */ |
| public static String getVerboseName(Element elt) { |
| if (elt.getKind() == ElementKind.PACKAGE |
| || elt.getKind().isClass() |
| || elt.getKind().isInterface()) { |
| return getQualifiedClassName(elt).toString(); |
| } else { |
| return getQualifiedClassName(elt) + "." + elt.toString(); |
| } |
| } |
| |
| /** |
| * 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"); |
| } |
| |
| /** Returns true if the element is a constant time reference */ |
| public static boolean isCompileTimeConstant(Element elt) { |
| return elt != null |
| && (elt.getKind() == ElementKind.FIELD |
| || elt.getKind() == ElementKind.LOCAL_VARIABLE) |
| && ((VariableElement) elt).getConstantValue() != null; |
| } |
| |
| /** |
| * Returns true if the element is declared in ByteCode. Always return false if elt is a package. |
| */ |
| public static boolean isElementFromByteCode(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.getName().endsWith(".class"); |
| } else { |
| return false; |
| } |
| } |
| return isElementFromByteCode(elt.getEnclosingElement(), elt); |
| } |
| |
| /** |
| * Returns true if the element is declared in ByteCode. Always return false if elt is a package. |
| */ |
| private static boolean isElementFromByteCode(Element elt, Element orig) { |
| 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.getName().endsWith(".class") |
| || clss.classfile.getName().endsWith(".class)") |
| || clss.classfile.getName().endsWith(".class)]")); |
| } else { |
| return false; |
| } |
| } |
| return isElementFromByteCode(elt.getEnclosingElement(), elt); |
| } |
| |
| /** Returns the field of the class */ |
| public static VariableElement findFieldInType(TypeElement type, String name) { |
| for (VariableElement field : ElementFilter.fieldsIn(type.getEnclosedElements())) { |
| if (field.getSimpleName().toString().equals(name)) { |
| return field; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the elements of the fields whose simple names are {@code names} and are declared in |
| * {@code type}. |
| * |
| * <p>If a field isn't declared in {@code type}, its element isn't included in the returned set. |
| * If none of the fields is declared in {@code type}, the empty set is returned. |
| * |
| * @param type where to look for fields |
| * @param names simple names of fields that might be declared in {@code type} |
| * @return the elements of the fields whose simple names are {@code names} and are declared in |
| * {@code type} |
| */ |
| public static Set<VariableElement> findFieldsInType( |
| TypeElement type, Collection<String> names) { |
| Set<VariableElement> results = new HashSet<VariableElement>(); |
| for (VariableElement field : ElementFilter.fieldsIn(type.getEnclosedElements())) { |
| if (names.contains(field.getSimpleName().toString())) { |
| results.add(field); |
| } |
| } |
| return results; |
| } |
| |
| /** |
| * Returns non-private field elements, and side-effects {@code names} to remove them. For every |
| * field name in {@code names} that is declared in {@code type} or a supertype, add its element |
| * to the returned set and remove it from {@code names}. |
| * |
| * <p>When this routine returns, the combination of the return value and {@code names} has the |
| * same cardinality, and represents the same fields, as {@code names} did when the method was |
| * called. |
| * |
| * @param type where to look for fields |
| * @param names simple names of fields that might be declared in {@code type} or a supertype. |
| * (Names that are found are removed from this list.) |
| * @return the {@code VariableElement}s for non-private fields that are declared in {@code type} |
| * whose simple names were in {@code names} when the method was called. |
| */ |
| public static Set<VariableElement> findFieldsInTypeOrSuperType( |
| TypeMirror type, Collection<String> names) { |
| Set<VariableElement> elements = new HashSet<>(); |
| findFieldsInTypeOrSuperType(type, 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<String> notFound, Set<VariableElement> foundFields) { |
| if (TypesUtils.isObject(type)) { |
| return; |
| } |
| TypeElement elt = InternalUtils.getTypeElement(type); |
| |
| Set<VariableElement> fieldElts = findFieldsInType(elt, notFound); |
| for (VariableElement field : new HashSet<>(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); |
| } |
| } |
| |
| public static boolean isError(Element element) { |
| return element.getClass() |
| .getName() |
| .equals("com.sun.tools.javac.comp.Resolve$SymbolNotFoundError"); |
| } |
| |
| /** |
| * 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) { |
| return (element.getKind().isField() |
| || element.getKind() == ElementKind.METHOD |
| || element.getKind() == ElementKind.CONSTRUCTOR) |
| && !ElementUtils.isStatic(element); |
| } |
| |
| /** |
| * Determine all type elements for the classes and interfaces referenced (directly or |
| * indirectly) in the extends/implements clauses of the given type element. |
| * |
| * <p>TODO: can we learn from the implementation of |
| * com.sun.tools.javac.model.JavacElements.getAllMembers(TypeElement)? |
| */ |
| public static List<TypeElement> getSuperTypes(Elements elements, TypeElement type) { |
| |
| List<TypeElement> superelems = new ArrayList<TypeElement>(); |
| if (type == null) { |
| return superelems; |
| } |
| |
| // Set up a stack containing type, which is our starting point. |
| Deque<TypeElement> stack = new ArrayDeque<TypeElement>(); |
| 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. |
| TypeMirror supertypecls; |
| try { |
| supertypecls = current.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. |
| // TODO: Use an expanded ErrorReporter to output a message. |
| supertypecls = null; |
| } |
| |
| if (supertypecls != null && supertypecls.getKind() != TypeKind.NONE) { |
| TypeElement supercls = (TypeElement) ((DeclaredType) supertypecls).asElement(); |
| 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.<TypeElement>unmodifiableList(superelems); |
| } |
| |
| /** |
| * 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? |
| */ |
| public static List<VariableElement> getAllFieldsIn(Elements elements, TypeElement type) { |
| List<VariableElement> fields = new ArrayList<VariableElement>(); |
| fields.addAll(ElementFilter.fieldsIn(type.getEnclosedElements())); |
| List<TypeElement> alltypes = getSuperTypes(elements, type); |
| for (TypeElement atype : alltypes) { |
| fields.addAll(ElementFilter.fieldsIn(atype.getEnclosedElements())); |
| } |
| return Collections.<VariableElement>unmodifiableList(fields); |
| } |
| |
| /** |
| * 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? |
| */ |
| public static List<ExecutableElement> getAllMethodsIn(Elements elements, TypeElement type) { |
| List<ExecutableElement> meths = new ArrayList<ExecutableElement>(); |
| meths.addAll(ElementFilter.methodsIn(type.getEnclosedElements())); |
| |
| List<TypeElement> alltypes = getSuperTypes(elements, type); |
| for (TypeElement atype : alltypes) { |
| meths.addAll(ElementFilter.methodsIn(atype.getEnclosedElements())); |
| } |
| return Collections.<ExecutableElement>unmodifiableList(meths); |
| } |
| |
| public static boolean isTypeDeclaration(Element elt) { |
| switch (elt.getKind()) { |
| // These tree kinds are always declarations. Uses of the declared |
| // types have tree kind IDENTIFIER. |
| case ANNOTATION_TYPE: |
| case CLASS: |
| case ENUM: |
| case INTERFACE: |
| case TYPE_PARAMETER: |
| return true; |
| |
| default: |
| return false; |
| } |
| } |
| |
| /** |
| * Check that a method Element matches a signature. |
| * |
| * <p>Note: Matching the receiver type must be done elsewhere as the Element receiver type is |
| * only populated when annotated. |
| * |
| * @param method the method Element |
| * @param methodName the name of the method |
| * @param parameters the formal parameters' Classes |
| * @return true if the method matches |
| */ |
| public static boolean matchesElement( |
| ExecutableElement method, String methodName, Class<?>... parameters) { |
| |
| if (!method.getSimpleName().toString().equals(methodName)) { |
| return false; |
| } |
| |
| if (method.getParameters().size() != parameters.length) { |
| return false; |
| } else { |
| for (int i = 0; i < method.getParameters().size(); i++) { |
| if (!method.getParameters() |
| .get(i) |
| .asType() |
| .toString() |
| .equals(parameters[i].getName())) { |
| |
| return false; |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| /** Returns true if the given element is, or overrides, method. */ |
| public static boolean isMethod( |
| ExecutableElement questioned, ExecutableElement method, ProcessingEnvironment env) { |
| TypeElement enclosing = (TypeElement) questioned.getEnclosingElement(); |
| return questioned.equals(method) |
| || env.getElementUtils().overrides(questioned, method, enclosing); |
| } |
| } |