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

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

Go to download

The Checker Framework enhances Java's type system to make it more powerful and useful. This lets software developers detect and prevent errors in their Java programs. The Checker Framework includes compiler plug-ins ("checkers") that find bugs or verify their absence. It also permits you to write your own compiler plug-ins.

There is a newer version: 3.49.2
Show newest version
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