package org.checkerframework.javacutil; /*>>> import org.checkerframework.dataflow.qual.Pure; import org.checkerframework.dataflow.qual.SideEffectFree; import org.checkerframework.checker.nullness.qual.*; import org.checkerframework.checker.interning.qual.*; */ import com.sun.source.tree.AnnotationTree; import com.sun.source.tree.MethodTree; import com.sun.source.tree.ModifiersTree; import com.sun.tools.javac.code.Symbol.VarSymbol; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.model.JavacElements; import java.lang.annotation.Annotation; import java.lang.annotation.Inherited; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Name; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; import javax.lang.model.util.Elements; /** A utility class for working with annotations. */ public class AnnotationUtils { // Class cannot be instantiated. private AnnotationUtils() { throw new AssertionError("Class AnnotationUtils cannot be instantiated."); } // TODO: hack to clear out static state. public static void clear() { annotationsFromNames.clear(); annotationMirrorNames.clear(); annotationMirrorSimpleNames.clear(); annotationClassNames.clear(); } // ********************************************************************** // Factory Methods to create instances of AnnotationMirror // ********************************************************************** /** Caching for annotation creation. */ private static final Map<CharSequence, AnnotationMirror> annotationsFromNames = Collections.synchronizedMap(new HashMap<CharSequence, AnnotationMirror>()); private static final int ANNOTATION_CACHE_SIZE = 500; /** * Cache names of AnnotationMirrors for faster access. Values in the map are interned Strings, * so they can be compared with ==. */ private static final Map<AnnotationMirror, /*@Interned*/ String> annotationMirrorNames = Collections.synchronizedMap( CollectionUtils.<AnnotationMirror, /*@Interned*/ String>createLRUCache( ANNOTATION_CACHE_SIZE)); /** * Cache simple names of AnnotationMirrors for faster access. Values in the map are interned * Strings, so they can be compared with ==. */ private static final Map<AnnotationMirror, /*@Interned*/ String> annotationMirrorSimpleNames = Collections.synchronizedMap( CollectionUtils.<AnnotationMirror, /*@Interned*/ String>createLRUCache( ANNOTATION_CACHE_SIZE)); /** * Cache names of classes representing AnnotationMirrors for faster access. Values in the map * are interned Strings, so they can be compared with ==. */ private static final Map<Class<? extends Annotation>, /*@Interned*/ String> annotationClassNames = Collections.synchronizedMap( new HashMap<Class<? extends Annotation>, /*@Interned*/ String>()); /** * Creates an {@link AnnotationMirror} given by a particular fully-qualified name. * getElementValues on the result returns an empty map. * * @param elements the element utilities to use * @param name the name of the annotation to create * @return an {@link AnnotationMirror} of type {@code} name */ public static AnnotationMirror fromName(Elements elements, CharSequence name) { AnnotationMirror res = annotationsFromNames.get(name); if (res != null) { return res; } final DeclaredType annoType = typeFromName(elements, name); if (annoType == null) { return null; } if (annoType.asElement().getKind() != ElementKind.ANNOTATION_TYPE) { ErrorReporter.errorAbort(annoType + " is not an annotation"); return null; // dead code } AnnotationMirror result = new AnnotationMirror() { String toString = "@" + annoType; @Override public DeclaredType getAnnotationType() { return annoType; } @Override public Map<? extends ExecutableElement, ? extends AnnotationValue> getElementValues() { return Collections.emptyMap(); } /*@SideEffectFree*/ @Override public String toString() { return toString; } }; annotationsFromNames.put(name, result); return result; } /** * Creates an {@link AnnotationMirror} given by a particular annotation class. * * @param elements the element utilities to use * @param clazz the annotation class * @return an {@link AnnotationMirror} of type given type */ public static AnnotationMirror fromClass(Elements elements, Class<? extends Annotation> clazz) { return fromName(elements, clazz.getCanonicalName()); } /** * A utility method that converts a {@link CharSequence} (usually a {@link String}) into a * {@link TypeMirror} named thereby. * * @param elements the element utilities to use * @param name the name of a type * @return the {@link TypeMirror} corresponding to that name */ private static DeclaredType typeFromName(Elements elements, CharSequence name) { /*@Nullable*/ TypeElement typeElt = elements.getTypeElement(name); if (typeElt == null) { return null; } return (DeclaredType) typeElt.asType(); } // ********************************************************************** // Helper methods to handle annotations. mainly workaround // AnnotationMirror.equals undesired property // (I think the undesired property is that it's reference equality.) // ********************************************************************** /** @return the fully-qualified name of an annotation as a String */ public static final /*@Interned*/ String annotationName(AnnotationMirror annotation) { String res = annotationMirrorNames.get(annotation); if (res != null) { return res; } final DeclaredType annoType = annotation.getAnnotationType(); final TypeElement elm = (TypeElement) annoType.asElement(); /*@Interned*/ String name = elm.getQualifiedName().toString().intern(); annotationMirrorNames.put(annotation, name); return name; } /** @return the simple name of an annotation as a String */ public static String annotationSimpleName(AnnotationMirror annotation) { String res = annotationMirrorSimpleNames.get(annotation); if (res != null) { return res; } final DeclaredType annoType = annotation.getAnnotationType(); final TypeElement elm = (TypeElement) annoType.asElement(); /*@Interned*/ String name = elm.getSimpleName().toString().intern(); annotationMirrorSimpleNames.put(annotation, name); return name; } /** * Checks if both annotations are the same. * * <p>Returns true iff both annotations are of the same type and have the same annotation * values. This behavior differs from {@code AnnotationMirror.equals(Object)}. The equals method * returns true iff both annotations are the same and annotate the same annotation target (e.g. * field, variable, etc). * * @return true iff a1 and a2 are the same annotation */ public static boolean areSame( /*@Nullable*/ AnnotationMirror a1, /*@Nullable*/ AnnotationMirror a2) { if (a1 != null && a2 != null) { if (annotationName(a1) != annotationName(a2)) { return false; } Map<? extends ExecutableElement, ? extends AnnotationValue> elval1 = getElementValuesWithDefaults(a1); Map<? extends ExecutableElement, ? extends AnnotationValue> elval2 = getElementValuesWithDefaults(a2); return elval1.toString().equals(elval2.toString()); } // only true, iff both are null return a1 == a2; } /** * @see #areSame(AnnotationMirror, AnnotationMirror) * @return true iff a1 and a2 have the same annotation type */ public static boolean areSameIgnoringValues(AnnotationMirror a1, AnnotationMirror a2) { if (a1 != null && a2 != null) { return annotationName(a1) == annotationName(a2); } return a1 == a2; } /** Checks that the annotation {@code am} has the name {@code aname}. Values are ignored. */ public static boolean areSameByName(AnnotationMirror am, /*@Interned*/ String aname) { // Both strings are interned. return annotationName(am) == aname; } /** Checks that the annotation {@code am} has the name of {@code anno}. Values are ignored. */ public static boolean areSameByClass(AnnotationMirror am, Class<? extends Annotation> anno) { /*@Interned*/ String canonicalName = annotationClassNames.get(anno); if (canonicalName == null) { canonicalName = anno.getCanonicalName().intern(); annotationClassNames.put(anno, canonicalName); } return areSameByName(am, canonicalName); } /** * Checks that two collections contain the same annotations. * * @return true iff c1 and c2 contain the same annotations */ public static boolean areSame( Collection<? extends AnnotationMirror> c1, Collection<? extends AnnotationMirror> c2) { if (c1.size() != c2.size()) { return false; } if (c1.size() == 1) { return areSame(c1.iterator().next(), c2.iterator().next()); } Set<AnnotationMirror> s1 = createAnnotationSet(); Set<AnnotationMirror> s2 = createAnnotationSet(); s1.addAll(c1); s2.addAll(c2); // depend on the fact that Set is an ordered set. Iterator<AnnotationMirror> iter1 = s1.iterator(); Iterator<AnnotationMirror> iter2 = s2.iterator(); while (iter1.hasNext()) { AnnotationMirror anno1 = iter1.next(); AnnotationMirror anno2 = iter2.next(); if (!areSame(anno1, anno2)) { return false; } } return true; } /** * Checks that the collection contains the annotation. Using Collection.contains does not always * work, because it does not use areSame for comparison. * * @return true iff c contains anno, according to areSame */ public static boolean containsSame( Collection<? extends AnnotationMirror> c, AnnotationMirror anno) { return getSame(c, anno) != null; } /** * Returns the AnnotationMirror in {@code c} that is the same annotation as {@code anno}. * * @return AnnotationMirror with the same class as {@code anno} iff c contains anno, according * to areSame; otherwise, {@code null} */ public static AnnotationMirror getSame( Collection<? extends AnnotationMirror> c, AnnotationMirror anno) { for (AnnotationMirror an : c) { if (AnnotationUtils.areSame(an, anno)) { return an; } } return null; } /** * Checks that the collection contains the annotation. Using Collection.contains does not always * work, because it does not use areSame for comparison. * * @return true iff c contains anno, according to areSameByClass */ public static boolean containsSameByClass( Collection<? extends AnnotationMirror> c, Class<? extends Annotation> anno) { return getAnnotationByClass(c, anno) != null; } /** * Returns the AnnotationMirror in {@code c} that has the same class as {@code anno}. * * @return AnnotationMirror with the same class as {@code anno} iff c contains anno, according * to areSameByClass; otherwise, {@code null} */ public static AnnotationMirror getAnnotationByClass( Collection<? extends AnnotationMirror> c, Class<? extends Annotation> anno) { for (AnnotationMirror an : c) { if (AnnotationUtils.areSameByClass(an, anno)) { return an; } } return null; } /** * Checks that the collection contains the annotation ignoring values. Using Collection.contains * does not always work, because it does not use areSameIgnoringValues for comparison. * * @return true iff c contains anno, according to areSameIgnoringValues */ public static boolean containsSameIgnoringValues( Collection<? extends AnnotationMirror> c, AnnotationMirror anno) { return getSameIgnoringValues(c, anno) != null; } /** * Returns the AnnotationMirror in {@code c} that is the same annotation as {@code anno} * ignoring values. * * @return AnnotationMirror with the same class as {@code anno} iff c contains anno, according * to areSameIgnoringValues; otherwise, {@code null} */ public static AnnotationMirror getSameIgnoringValues( Collection<? extends AnnotationMirror> c, AnnotationMirror anno) { for (AnnotationMirror an : c) { if (AnnotationUtils.areSameIgnoringValues(an, anno)) { return an; } } return null; } private static final Comparator<AnnotationMirror> ANNOTATION_ORDERING = new Comparator<AnnotationMirror>() { @Override public int compare(AnnotationMirror a1, AnnotationMirror a2) { String n1 = a1.toString(); String n2 = a2.toString(); return n1.compareTo(n2); } }; /** * provide ordering for {@link AnnotationMirror} based on their fully qualified name. The * ordering ignores annotation values when ordering. * * <p>The ordering is meant to be used as {@link TreeSet} or {@link TreeMap} ordering. A {@link * Set} should not contain two annotations that only differ in values. */ public static Comparator<AnnotationMirror> annotationOrdering() { return ANNOTATION_ORDERING; } /** * Create a map suitable for storing {@link AnnotationMirror} as keys. * * <p>It can store one instance of {@link AnnotationMirror} of a given declared type, regardless * of the annotation element values. * * @param <V> the value of the map * @return a new map with {@link AnnotationMirror} as key */ public static <V> Map<AnnotationMirror, V> createAnnotationMap() { return new TreeMap<AnnotationMirror, V>(annotationOrdering()); } /** * Constructs a {@link Set} suitable for storing {@link AnnotationMirror}s. * * <p>It stores at most once instance of {@link AnnotationMirror} of a given type, regardless of * the annotation element values. * * @return a new set to store {@link AnnotationMirror} as element */ public static Set<AnnotationMirror> createAnnotationSet() { return new TreeSet<AnnotationMirror>(annotationOrdering()); } /** Returns true if the given annotation has a @Inherited meta-annotation. */ public static boolean hasInheritedMeta(AnnotationMirror anno) { return anno.getAnnotationType().asElement().getAnnotation(Inherited.class) != null; } // ********************************************************************** // Extractors for annotation values // ********************************************************************** /** * Returns the values of an annotation's attributes, including defaults. The method with the * same name in JavacElements cannot be used directly, because it includes a cast to * Attribute.Compound, which doesn't hold for annotations generated by the Checker Framework. * * @see AnnotationMirror#getElementValues() * @see JavacElements#getElementValuesWithDefaults(AnnotationMirror) * @param ad annotation to examine * @return the values of the annotation's elements, including defaults */ public static Map<? extends ExecutableElement, ? extends AnnotationValue> getElementValuesWithDefaults(AnnotationMirror ad) { Map<ExecutableElement, AnnotationValue> valMap = new HashMap<ExecutableElement, AnnotationValue>(); if (ad.getElementValues() != null) { valMap.putAll(ad.getElementValues()); } for (ExecutableElement meth : ElementFilter.methodsIn(ad.getAnnotationType().asElement().getEnclosedElements())) { AnnotationValue defaultValue = meth.getDefaultValue(); if (defaultValue != null && !valMap.containsKey(meth)) { valMap.put(meth, defaultValue); } } return valMap; } /** * Verify whether the attribute with the name {@code name} exists in the annotation {@code * anno}. * * @param anno the annotation to examine * @param name the name of the attribute * @return whether the attribute exists in anno */ public static <T> boolean hasElementValue(AnnotationMirror anno, CharSequence name) { Map<? extends ExecutableElement, ? extends AnnotationValue> valmap = anno.getElementValues(); for (ExecutableElement elem : valmap.keySet()) { if (elem.getSimpleName().contentEquals(name)) { return true; } } return false; } /** * Get the attribute with the name {@code name} of the annotation {@code anno}. The result is * expected to have type {@code expectedType}. * * <p><em>Note 1</em>: The method does not work well for attributes of an array type (as it * would return a list of {@link AnnotationValue}s). Use {@code getElementValueArray} instead. * * <p><em>Note 2</em>: The method does not work for attributes of an enum type, as the * AnnotationValue is a VarSymbol and would be cast to the enum type, which doesn't work. Use * {@code getElementValueEnum} instead. * * @param anno the annotation to disassemble * @param name the name of the attribute to access * @param expectedType the expected type used to cast the return type * @param useDefaults whether to apply default values to the attribute * @return the value of the attribute with the given name */ public static <T> T getElementValue( AnnotationMirror anno, CharSequence name, Class<T> expectedType, boolean useDefaults) { Map<? extends ExecutableElement, ? extends AnnotationValue> valmap; if (useDefaults) { valmap = getElementValuesWithDefaults(anno); } else { valmap = anno.getElementValues(); } for (ExecutableElement elem : valmap.keySet()) { if (elem.getSimpleName().contentEquals(name)) { AnnotationValue val = valmap.get(elem); return expectedType.cast(val.getValue()); } } ErrorReporter.errorAbort("No element with name \'" + name + "\' in annotation " + anno); return null; // dead code } /** Version that is suitable for Enum elements. */ public static <T extends Enum<T>> T getElementValueEnum( AnnotationMirror anno, CharSequence name, Class<T> t, boolean useDefaults) { VarSymbol vs = getElementValue(anno, name, VarSymbol.class, useDefaults); T value = Enum.valueOf(t, vs.getSimpleName().toString()); return value; } /** * Get the attribute with the name {@code name} of the annotation {@code anno}, where the * attribute has an array type. One element of the result is expected to have type {@code * expectedType}. * * <p>Parameter useDefaults is used to determine whether default values should be used for * annotation values. Finding defaults requires more computation, so should be false when no * defaulting is needed. * * @param anno the annotation to disassemble * @param name the name of the attribute to access * @param expectedType the expected type used to cast the return type * @param useDefaults whether to apply default values to the attribute * @return the value of the attribute with the given name */ public static <T> List<T> getElementValueArray( AnnotationMirror anno, CharSequence name, Class<T> expectedType, boolean useDefaults) { @SuppressWarnings("unchecked") List<AnnotationValue> la = getElementValue(anno, name, List.class, useDefaults); List<T> result = new ArrayList<T>(la.size()); for (AnnotationValue a : la) { result.add(expectedType.cast(a.getValue())); } return result; } /** * Get the attribute with the name {@code name} of the annotation {@code anno}, or the default * value if no attribute is present explicitly, where the attribute has an array type and the * elements are {@code Enum}s. One element of the result is expected to have type {@code * expectedType}. */ public static <T extends Enum<T>> List<T> getElementValueEnumArray( AnnotationMirror anno, CharSequence name, Class<T> t, boolean useDefaults) { @SuppressWarnings("unchecked") List<AnnotationValue> la = getElementValue(anno, name, List.class, useDefaults); List<T> result = new ArrayList<T>(la.size()); for (AnnotationValue a : la) { T value = Enum.valueOf(t, a.getValue().toString()); result.add(value); } return result; } /** * Get the Name of the class that is referenced by attribute {@code name}. * * <p>This is a convenience method for the most common use-case. Like getElementValue(anno, * name, ClassType.class).getQualifiedName(), but this method ensures consistent use of the * qualified name. */ public static Name getElementValueClassName( AnnotationMirror anno, CharSequence name, boolean useDefaults) { Type.ClassType ct = getElementValue(anno, name, Type.ClassType.class, useDefaults); // TODO: Is it a problem that this returns the type parameters too? Should I cut them off? return ct.asElement().getQualifiedName(); } /** Get the list of Names of the classes that are referenced by attribute {@code name}. */ public static List<Name> getElementValueClassNames( AnnotationMirror anno, CharSequence name, boolean useDefaults) { List<Type.ClassType> la = getElementValueArray(anno, name, Type.ClassType.class, useDefaults); List<Name> names = new ArrayList<>(); for (Type.ClassType classType : la) { names.add(classType.asElement().getQualifiedName()); } return names; } /** * Get the Class that is referenced by attribute {@code name}. This method uses Class.forName to * load the class. It returns null if the class wasn't found. */ public static Class<?> getElementValueClass( AnnotationMirror anno, CharSequence name, boolean useDefaults) { Name cn = getElementValueClassName(anno, name, useDefaults); try { ClassLoader classLoader = InternalUtils.getClassLoaderForClass(AnnotationUtils.class); Class<?> cls = Class.forName(cn.toString(), true, classLoader); return cls; } catch (ClassNotFoundException e) { String msg = String.format( "Could not load class '%s' for field '%s' in annotation %s", cn, name, anno); ErrorReporter.errorAbort(msg, e); return null; // dead code } } /** * See checkers.types.QualifierHierarchy#updateMappingToMutableSet(QualifierHierarchy, Map, * Object, AnnotationMirror) (Not linked because it is in an independent project. */ public static <T> void updateMappingToImmutableSet( Map<T, Set<AnnotationMirror>> map, T key, Set<AnnotationMirror> newQual) { Set<AnnotationMirror> result = AnnotationUtils.createAnnotationSet(); // TODO: if T is also an AnnotationMirror, should we use areSame? if (!map.containsKey(key)) { result.addAll(newQual); } else { result.addAll(map.get(key)); result.addAll(newQual); } map.put(key, Collections.unmodifiableSet(result)); } /** * Returns the annotations explicitly written on a constructor result. Callers should check that * {@code constructorDeclaration} is in fact a declaration of a constructor. * * @param constructorDeclaration declaration tree of constructor * @return set of annotations explicit on the resulting type of the constructor */ public static Set<AnnotationMirror> getExplicitAnnotationsOnConstructorResult( MethodTree constructorDeclaration) { Set<AnnotationMirror> annotationSet = AnnotationUtils.createAnnotationSet(); ModifiersTree modifiersTree = constructorDeclaration.getModifiers(); if (modifiersTree != null) { List<? extends AnnotationTree> annotationTrees = modifiersTree.getAnnotations(); annotationSet.addAll(InternalUtils.annotationsFromTypeAnnotationTrees(annotationTrees)); } return annotationSet; } }