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

javacutil.src.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.42.0
Show newest version
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 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;

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;
/**
 * 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.
    // {@link org.checkerframework.qualframework.util.QualifierContext} should
    // handle instantiation of utility classes.
    public static void clear() {
        annotationsFromNames.clear();
        annotationMirrorNames.clear();
        annotationClassNames.clear();
    }

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

    /** Caching for annotation creation. */
    private static final Map annotationsFromNames
        = new HashMap();


    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 annotationMirrorNames
        = CollectionUtils.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, /*@Interned*/ String> annotationClassNames
        = new HashMap, /*@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) {
        if (annotationsFromNames.containsKey(name)) {
            return annotationsFromNames.get(name);
        }
        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
                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 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) {
        if (annotationMirrorNames.containsKey(annotation)) {
            return annotationMirrorNames.get(annotation);
        }

        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 annotationName = annotationName(annotation);
        return annotationName.substring(annotationName.lastIndexOf('.') + 1 /* +1 to skip the last . as well */);
    }

    /**
     * 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 != null && a2 != null) {
            if (annotationName(a1) != annotationName(a2)) {
                return false;
            }

            Map elval1 = getElementValuesWithDefaults(a1);
            Map 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 anno) {
        /*@Interned*/ String canonicalName;
        if (annotationClassNames.containsKey(anno)) {
            canonicalName = annotationClassNames.get(anno);
        } else {
            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 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) {
        for (AnnotationMirror an : c) {
            if (AnnotationUtils.areSame(an, anno)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 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) {
        for (AnnotationMirror an : c) {
            if (AnnotationUtils.areSameByClass(an, anno)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 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) {
        for (AnnotationMirror an : c) {
            if (AnnotationUtils.areSameIgnoringValues(an, anno)) {
                return true;
            }
        }
        return false;
    }

    private static final Comparator ANNOTATION_ORDERING
    = new Comparator() {
        @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.
     *
     * 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;
    }


    // **********************************************************************
    // 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;
    }


    /**
     * 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 '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 Class that is referenced by attribute '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 { Class cls = Class.forName(cn.toString()); return cls; } catch (ClassNotFoundException e) { ErrorReporter.errorAbort("Could not load class '" + cn + "' for field '" + name + "' in annotation " + anno, 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(InternalUtils.annotationsFromTypeAnnotationTrees(annotationTrees)); } return annotationSet; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy