| 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 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.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 com.sun.tools.javac.code.Symbol; |
| |
| /** |
| * 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; |
| * |
| * 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) { |
| 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 |
| * |
| * @param element |
| * @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 |
| * |
| * @param element |
| * @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. |
| * |
| * @param 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. |
| * |
| * @param element |
| * @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()) { |
| 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 |
| && ((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; |
| } |
| |
| 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; |
| } |
| |
| 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() != ElementKind.LOCAL_VARIABLE |
| && element.getKind() != ElementKind.PARAMETER |
| && element.getKind() != ElementKind.PACKAGE |
| && !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(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); |
| } |
| } |
| } |
| |
| 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(TypeElement type) { |
| List<VariableElement> fields = new ArrayList<VariableElement>(); |
| fields.addAll(ElementFilter.fieldsIn(type.getEnclosedElements())); |
| List<TypeElement> alltypes = getSuperTypes(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(TypeElement type) { |
| List<ExecutableElement> meths = new ArrayList<ExecutableElement>(); |
| meths.addAll(ElementFilter.methodsIn(type.getEnclosedElements())); |
| |
| List<TypeElement> alltypes = getSuperTypes(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. |
| * |
| * 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; |
| } |
| } |