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

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

package org.checkerframework.javacutil;

/*>>>
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.interning.qual.Interned;
*/

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.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
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.util.ElementFilter;

/** 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() {
        AnnotationBuilder.clear();
        annotationClassNames.clear();
    }

    // **********************************************************************
    // Factory Methods to create instances of AnnotationMirror
    // **********************************************************************

    private static final int ANNOTATION_CACHE_SIZE = 500;

    /** Maps classes representing AnnotationMirrors to their names. */
    private static final Map, String> annotationClassNames =
            Collections.synchronizedMap(CollectionUtils.createLRUCache(ANNOTATION_CACHE_SIZE));

    // **********************************************************************
    // 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 String annotationName(AnnotationMirror annotation) {
        if (annotation instanceof AnnotationBuilder.CheckerFrameworkAnnotationMirror) {
            return ((AnnotationBuilder.CheckerFrameworkAnnotationMirror) annotation).annotationName;
        }
        final DeclaredType annoType = annotation.getAnnotationType();
        final TypeElement elm = (TypeElement) annoType.asElement();
        String name = elm.getQualifiedName().toString();
        return name;
    }

    /**
     * Checks if both annotations are the same.
     *
     * 

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 == a2) { return true; } if (!areSameIgnoringValues(a1, a2)) { return false; } // This commented implementation is less efficient. It is also wrong: it requires a // particular order for fields, and it distinguishes the long constants "33" and "33L". // Map elval1 = // getElementValuesWithDefaults(a1); // Map elval2 = // getElementValuesWithDefaults(a2); // return elval1.toString().equals(elval2.toString()); return sameElementValues(a1, a2); } /** * @see #areSame(AnnotationMirror, AnnotationMirror) * @return true iff a1 and a2 have the same annotation type */ public static boolean areSameIgnoringValues( /*@Nullable*/ AnnotationMirror a1, /*@Nullable*/ AnnotationMirror a2) { if (a1 == a2) { return true; } if (a1 == null || a2 == null) { return false; } return annotationName(a1).equals(annotationName(a2)); } /** * Checks that the annotation {@code am} has the name {@code aname} (a fully-qualified type * name). Values are ignored. * *

(Use {@link #areSameByClass} instead of this method when possible. It is faster.) */ public static boolean areSameByName(AnnotationMirror am, String aname) { return aname.equals(annotationName(am)); } /** * Checks that the annotation {@code am} has the name of {@code annoClass}. Values are ignored. * *

(Use this method rather than {@link #areSameByName} when possible. This method is faster.) */ public static boolean areSameByClass( AnnotationMirror am, Class annoClass) { String canonicalName = annotationClassNames.get(annoClass); if (canonicalName == null) { // This method is faster than #areSameByName because of this cache. canonicalName = annoClass.getCanonicalName(); annotationClassNames.put(annoClass, 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 c1, Collection c2) { if (c1.size() != c2.size()) { return false; } if (c1.size() == 1) { return areSame(c1.iterator().next(), c2.iterator().next()); } Set s1 = createAnnotationSet(); Set s2 = createAnnotationSet(); s1.addAll(c1); s2.addAll(c2); // depend on the fact that Set is an ordered set. Iterator iter1 = s1.iterator(); Iterator 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 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 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 c, Class 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 c, Class anno) { for (AnnotationMirror an : c) { if (AnnotationUtils.areSameByClass(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 areSameByName */ public static boolean containsSameByName( Collection c, String anno) { return getAnnotationByName(c, anno) != null; } /** * Returns the AnnotationMirror in {@code c} that has the same name as {@code anno}. * * @return AnnotationMirror with the same name as {@code anno} iff c contains anno, according to * areSameByName; otherwise, {@code null} */ public static AnnotationMirror getAnnotationByName( Collection c, String anno) { for (AnnotationMirror an : c) { if (AnnotationUtils.areSameByName(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 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 c, AnnotationMirror anno) { for (AnnotationMirror an : c) { if (AnnotationUtils.areSameIgnoringValues(an, anno)) { return an; } } return null; } private static final Comparator ANNOTATION_ORDERING = new Comparator() { @Override public int compare(AnnotationMirror a1, AnnotationMirror a2) { // AnnotationMirror.toString() prints the elements of an annotation in the // order in which they were written. So, use areSame to check for equality. if (AnnotationUtils.areSame(a1, a2)) { return 0; } String n1 = a1.toString(); String n2 = a2.toString(); // Because the AnnotationMirror.toString prints the annotation as it appears // in source code, the order in which annotations of the same class are // sorted may be confusing. For example, it might order // @IntRange(from=1, to=MAX) before @IntRange(to=MAX,from=0). return n1.compareTo(n2); } }; /** * provide ordering for {@link AnnotationMirror} based on their fully qualified name. The * ordering ignores annotation values when ordering. * *

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 annotationOrdering() { return ANNOTATION_ORDERING; } /** * Create a map suitable for storing {@link AnnotationMirror} as keys. * *

It can store one instance of {@link AnnotationMirror} of a given declared type, regardless * of the annotation element values. * * @param the value of the map * @return a new map with {@link AnnotationMirror} as key */ public static Map createAnnotationMap() { return new TreeMap(annotationOrdering()); } /** * Constructs a {@link Set} suitable for storing {@link AnnotationMirror}s. * *

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 createAnnotationSet() { return new TreeSet(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; } /** * @return the set of {@link ElementKind}s to which {@code target} applies, ignoring TYPE_USE */ public static EnumSet getElementKindsForTarget(/*@Nullable*/ Target target) { if (target == null) { // A missing @Target implies that the annotation can be written everywhere. return EnumSet.allOf(ElementKind.class); } EnumSet eleKinds = EnumSet.noneOf(ElementKind.class); for (ElementType elementType : target.value()) { eleKinds.addAll(getElementKindsForElementType(elementType)); } return eleKinds; } /** * Returns the set of {@link ElementKind}s corresponding to {@code elementType}. If the element * type is TYPE_USE, then ElementKinds returned should be the same as those returned for TYPE * and TYPE_PARAMETER, but this method returns the empty set instead. * * @return the set of {@link ElementKind}s corresponding to {@code elementType} */ public static EnumSet getElementKindsForElementType(ElementType elementType) { switch (elementType) { case TYPE: return EnumSet.of( ElementKind.CLASS, ElementKind.INTERFACE, ElementKind.ANNOTATION_TYPE, ElementKind.ENUM); case FIELD: return EnumSet.of(ElementKind.FIELD, ElementKind.ENUM_CONSTANT); case METHOD: return EnumSet.of(ElementKind.METHOD); case PARAMETER: return EnumSet.of(ElementKind.PARAMETER); case CONSTRUCTOR: return EnumSet.of(ElementKind.CONSTRUCTOR); case LOCAL_VARIABLE: return EnumSet.of( ElementKind.LOCAL_VARIABLE, ElementKind.RESOURCE_VARIABLE, ElementKind.EXCEPTION_PARAMETER); case ANNOTATION_TYPE: return EnumSet.of(ElementKind.ANNOTATION_TYPE); case PACKAGE: return EnumSet.of(ElementKind.PACKAGE); case TYPE_PARAMETER: return EnumSet.of(ElementKind.TYPE_PARAMETER); case TYPE_USE: return EnumSet.noneOf(ElementKind.class); default: ErrorReporter.errorAbort("New ElementType: %s", elementType); return EnumSet.noneOf(ElementKind.class); } } // ********************************************************************** // 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 getElementValuesWithDefaults(AnnotationMirror ad) { Map valMap = new HashMap(); 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; } /** * Returns true if the two annotations have the same elements (fields). The arguments {@code * am1} and {@code am2} must be the same type of annotation. */ public static boolean sameElementValues(AnnotationMirror am1, AnnotationMirror am2) { Map vals1 = am1.getElementValues(); Map vals2 = am2.getElementValues(); for (ExecutableElement meth : ElementFilter.methodsIn( am1.getAnnotationType().asElement().getEnclosedElements())) { AnnotationValue aval1 = vals1.get(meth); AnnotationValue aval2 = vals2.get(meth); if (aval1 == null) { aval1 = meth.getDefaultValue(); } if (aval2 == null) { aval2 = meth.getDefaultValue(); } if (!sameAnnotationValue(aval1, aval2)) { return false; } } return true; } /** * Return true iff the two AnnotationValue objects are the same. Use this instead of * CheckerFrameworkAnnotationValue.equals, which wouldn't get called if the receiver is some * AnnotationValue other than CheckerFrameworkAnnotationValue. */ public static boolean sameAnnotationValue(AnnotationValue av1, AnnotationValue av2) { if (av1 == av2) { return true; } if (av1 == null || av2 == null) { return false; } return sameAnnotationValueValue(av1.getValue(), av2.getValue()); } /** * Return true if the two annotation values are the same. The arguments to this method are * values that are returned by {@code AnnotationValue.getValue()}. */ private static boolean sameAnnotationValueValue(Object val1, Object val2) { if (val1 == val2) { return true; } // Can't use deepEquals() to compare val1 and val2, because they might have mismatched // AnnotationValue vs. CheckerFrameworkAnnotationValue, and AnnotationValue doesn't override // equals(). So, write my own version of deepEquals(). if ((val1 instanceof List) && (val2 instanceof List)) { List list1 = (List) val1; List list2 = (List) val2; if (list1.size() != list2.size()) { return false; } // Don't compare setwise, because order can matter. These mean different things: // @LTLengthOf(value={"a1","a2"}, offest={"0", "1"}) // @LTLengthOf(value={"a2","a1"}, offest={"0", "1"}) for (int i = 0; i < list1.size(); i++) { if (!sameAnnotationValueValue(list1.get(i), list2.get(i))) { return false; } } return true; } else if ((val1 instanceof AnnotationMirror) && (val2 instanceof AnnotationMirror)) { return areSame((AnnotationMirror) val1, (AnnotationMirror) val2); } else if ((val1 instanceof AnnotationValue) && (val2 instanceof AnnotationValue)) { // This case occurs because of the recursive call when comparing arrays of // annotation values. return sameAnnotationValue((AnnotationValue) val1, (AnnotationValue) val2); } else if ((val1 instanceof Type.ClassType) && (val2 instanceof Type.ClassType)) { // Type.ClassType does not override equals return TypesUtils.areSameDeclaredTypes((Type.ClassType) val1, (Type.ClassType) val2); } else { return Objects.equals(val1, val2); } } /** * 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 boolean hasElementValue(AnnotationMirror anno, CharSequence name) { Map 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}. * *

Note 1: 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. * *

Note 2: 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 getElementValue( AnnotationMirror anno, CharSequence name, Class expectedType, boolean useDefaults) { Map 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 getElementValueEnum( AnnotationMirror anno, CharSequence name, Class 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}. * *

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 List getElementValueArray( AnnotationMirror anno, CharSequence name, Class expectedType, boolean useDefaults) { @SuppressWarnings("unchecked") List la = getElementValue(anno, name, List.class, useDefaults); List result = new ArrayList(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 > List getElementValueEnumArray( AnnotationMirror anno, CharSequence name, Class t, boolean useDefaults) { @SuppressWarnings("unchecked") List la = getElementValue(anno, name, List.class, useDefaults); List result = new ArrayList(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}. * *

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 getElementValueClassNames( AnnotationMirror anno, CharSequence name, boolean useDefaults) { List la = getElementValueArray(anno, name, Type.ClassType.class, useDefaults); List 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 void updateMappingToImmutableSet( Map> map, T key, Set newQual) { Set 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 getExplicitAnnotationsOnConstructorResult( MethodTree constructorDeclaration) { Set annotationSet = AnnotationUtils.createAnnotationSet(); ModifiersTree modifiersTree = constructorDeclaration.getModifiers(); if (modifiersTree != null) { List annotationTrees = modifiersTree.getAnnotations(); annotationSet.addAll(TreeUtils.annotationsFromTypeAnnotationTrees(annotationTrees)); } return annotationSet; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy