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 in the * extends/implements clauses of the given type element. 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 = current.getSuperclass(); if (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); } }