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

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

There is a newer version: 3.48.2
Show newest version
package org.checkerframework.javacutil;

import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ModifiersTree;
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.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.StringJoiner;
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.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.util.ElementFilter;
import org.checkerframework.checker.interning.qual.CompareToMethod;
import org.checkerframework.checker.interning.qual.EqualsMethod;
import org.checkerframework.checker.interning.qual.Interned;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.signature.qual.BinaryName;
import org.checkerframework.checker.signature.qual.CanonicalName;
import org.checkerframework.dataflow.qual.Pure;
import org.checkerframework.dataflow.qual.SideEffectFree;
import org.checkerframework.javacutil.AnnotationBuilder.CheckerFrameworkAnnotationMirror;
import org.plumelib.util.ArrayMap;
import org.plumelib.util.CollectionsPlume;

/**
 * A utility class for working with annotations.
 *
 * 

Note: {@code AnnotationMirror}s are immutable. */ public class AnnotationUtils { // Class cannot be instantiated. private AnnotationUtils() { throw new AssertionError("Class AnnotationUtils cannot be instantiated."); } // ********************************************************************** // Helper methods to handle annotations. mainly workaround // AnnotationMirror.equals undesired property // (I think the undesired property is that it's reference equality.) // ********************************************************************** /** * Returns the fully-qualified name of an annotation as a String. * * @param annotation the annotation whose name to return * @return the fully-qualified name of an annotation as a String */ public static final @CanonicalName String annotationName(AnnotationMirror annotation) { if (annotation instanceof AnnotationBuilder.CheckerFrameworkAnnotationMirror) { return ((AnnotationBuilder.CheckerFrameworkAnnotationMirror) annotation).annotationName; } DeclaredType annoType = annotation.getAnnotationType(); TypeElement elm = (TypeElement) annoType.asElement(); @SuppressWarnings("signature:assignment") // JDK needs annotations @CanonicalName String name = elm.getQualifiedName().toString(); return name; } /** * Returns the fully-qualified name of an annotation as a String. * * @param annotation the annotation whose name to return * @return the fully-qualified name of an annotation as a String */ public static final @CanonicalName @Interned String annotationNameInterned( AnnotationMirror annotation) { if (annotation instanceof AnnotationBuilder.CheckerFrameworkAnnotationMirror) { return ((AnnotationBuilder.CheckerFrameworkAnnotationMirror) annotation).annotationName; } DeclaredType annoType = annotation.getAnnotationType(); TypeElement elm = (TypeElement) annoType.asElement(); @SuppressWarnings("signature:assignment") // JDK needs annotations @CanonicalName String name = elm.getQualifiedName().toString(); return name.intern(); } /** * Returns the binary name of an annotation as a String. * * @param annotation the annotation whose binary name to return * @return the binary name of an annotation as a String */ public static final @BinaryName String annotationBinaryName(AnnotationMirror annotation) { DeclaredType annoType = annotation.getAnnotationType(); TypeElement elm = (TypeElement) annoType.asElement(); return ElementUtils.getBinaryName(elm); } /** * 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) -- that is, if its arguments are the same annotation instance. * * @param a1 the first AnnotationMirror to compare * @param a2 the second AnnotationMirror to compare * @return true iff a1 and a2 are the same annotation */ @EqualsMethod public static boolean areSame(AnnotationMirror a1, AnnotationMirror a2) { if (a1 == a2) { return true; } if (!areSameByName(a1, a2)) { return false; } return sameElementValues(a1, a2); } /** * Return -1, 0, or 1 depending on whether the name of a1 is less, equal to, or greater than that * of a2 (lexicographically). * * @param a1 the first AnnotationMirror to compare * @param a2 the second AnnotationMirror to compare * @return true iff a1 and a2 have the same annotation name * @see #areSame(AnnotationMirror, AnnotationMirror) */ @EqualsMethod public static int compareByName(AnnotationMirror a1, AnnotationMirror a2) { if (a1 == a2) { return 0; } if (a1 == null || a2 == null) { throw new BugInCF("Unexpected null argument: compareByName(%s, %s)", a1, a2); } if (a1 instanceof CheckerFrameworkAnnotationMirror && a2 instanceof CheckerFrameworkAnnotationMirror) { @Interned @CanonicalName String name1 = ((CheckerFrameworkAnnotationMirror) a1).annotationName; @Interned @CanonicalName String name2 = ((CheckerFrameworkAnnotationMirror) a2).annotationName; if (name1 == name2) { return 0; } else { return name1.compareTo(name2); } } return annotationName(a1).compareTo(annotationName(a2)); } /** * Return true iff a1 and a2 have the same annotation type. * * @param a1 the first AnnotationMirror to compare * @param a2 the second AnnotationMirror to compare * @return true iff a1 and a2 have the same annotation name * @see #areSame(AnnotationMirror, AnnotationMirror) */ @EqualsMethod public static boolean areSameByName(AnnotationMirror a1, AnnotationMirror a2) { return compareByName(a1, a2) == 0; } /** * Checks that the annotation {@code am} has the name {@code aname} (a fully-qualified type name). * Values are ignored. * * @param am the AnnotationMirror whose name to compare * @param aname the string to compare * @return true if aname is the name of am */ 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. * *

This method is not very efficient. It is more efficient to use {@code * AnnotatedTypeFactory#areSameByClass} or {@link #areSameByName}. * * @param am the AnnotationMirror whose class to compare * @param annoClass the class to compare * @return true if annoclass is the class of am * @deprecated use {@code AnnotatedTypeFactory#areSameByClass} or {@link #areSameByName} */ @Deprecated // for use only by the framework public static boolean areSameByClass(AnnotationMirror am, Class annoClass) { String canonicalName = annoClass.getCanonicalName(); assert canonicalName != null : "@AssumeAssertion(nullness): assumption"; return areSameByName(am, canonicalName); } /** * Checks that two collections contain the same annotations. * * @param c1 the first collection to compare * @param c2 the second collection to compare * @return true iff c1 and c2 contain the same annotations, according to {@link * #areSame(AnnotationMirror, AnnotationMirror)} */ 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()); } // while loop depends on NavigableSet implementation. AnnotationMirrorSet s1 = new AnnotationMirrorSet(); AnnotationMirrorSet s2 = new AnnotationMirrorSet(); s1.addAll(c1); s2.addAll(c2); 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. * * @param c a collection of AnnotationMirrors * @param anno the AnnotationMirror to search for in c * @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}. * * @param c a collection of AnnotationMirrors * @param anno the AnnotationMirror to search for in c * @return AnnotationMirror with the same class as {@code anno} iff c contains anno, according to * areSame; otherwise, {@code null} */ public static @Nullable 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. * *

This method is not very efficient. It is more efficient to use {@code * AnnotatedTypeFactory#containsSameByClass} or {@link #containsSameByName}. * * @param c a collection of AnnotationMirrors * @param anno the annotation class to search for in c * @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}. * *

This method is not very efficient. It is more efficient to use {@code * AnnotatedTypeFactory#getAnnotationByClass} or {@link #getAnnotationByName}. * * @param c a collection of AnnotationMirrors * @param anno the class to search for in c * @return AnnotationMirror with the same class as {@code anno} iff c contains anno, according to * areSameByClass; otherwise, {@code null} */ public static @Nullable AnnotationMirror getAnnotationByClass( Collection c, Class anno) { for (AnnotationMirror an : c) { if (AnnotationUtils.areSameByClass(an, anno)) { return an; } } return null; } /** * Checks that the collection contains an annotation of the given name. Differs from using * Collection.contains, which does not use areSameByName for comparison. * * @param c a collection of AnnotationMirrors * @param anno the name to search for in c * @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}. * * @param c a collection of AnnotationMirrors * @param anno the name to search for in c * @return AnnotationMirror with the same name as {@code anno} iff c contains anno, according to * areSameByName; otherwise, {@code null} */ public static @Nullable AnnotationMirror getAnnotationByName( Collection c, String anno) { for (AnnotationMirror an : c) { if (AnnotationUtils.areSameByName(an, anno)) { return an; } } return null; } /** * Checks that the collection contains an annotation of the given name. Differs from using * Collection.contains, which does not use areSameByName for comparison. * * @param c a collection of AnnotationMirrors * @param anno the annotation whose name to search for in c * @return true iff c contains anno, according to areSameByName */ public static boolean containsSameByName( Collection c, AnnotationMirror anno) { return getSameByName(c, anno) != null; } /** * Returns the AnnotationMirror in {@code c} that is the same annotation as {@code anno} ignoring * values. * * @param c a collection of AnnotationMirrors * @param anno the annotation whose name to search for in c * @return AnnotationMirror with the same class as {@code anno} iff c contains anno, according to * areSameByName; otherwise, {@code null} */ public static @Nullable AnnotationMirror getSameByName( Collection c, AnnotationMirror anno) { for (AnnotationMirror an : c) { if (AnnotationUtils.areSameByName(an, anno)) { return an; } } return null; } /** * Provide an ordering for {@link AnnotationMirror}s. AnnotationMirrors are first compared by * their fully-qualified names, then by their element values in order of the name of the element. * * @param a1 the first annotation * @param a2 the second annotation * @return an ordering over AnnotationMirrors based on their name and values */ public static int compareAnnotationMirrors(AnnotationMirror a1, AnnotationMirror a2) { int nameComparison = compareByName(a1, a2); if (nameComparison != 0) { return nameComparison; } // The annotations have the same name, but different values, so compare values. Map vals1 = a1.getElementValues(); Map vals2 = a2.getElementValues(); Set sortedElements = new TreeSet<>(Comparator.comparing(ElementUtils::getSimpleSignature)); sortedElements.addAll( ElementFilter.methodsIn(a1.getAnnotationType().asElement().getEnclosedElements())); // getDefaultValue() returns null if the method is not an annotation interface element. for (ExecutableElement meth : sortedElements) { AnnotationValue aval1 = vals1.get(meth); if (aval1 == null) { aval1 = meth.getDefaultValue(); } AnnotationValue aval2 = vals2.get(meth); if (aval2 == null) { aval2 = meth.getDefaultValue(); } int result = compareAnnotationValue(aval1, aval2); if (result != 0) { return result; } } return 0; } /** * Return 0 iff the two AnnotationValue objects are the same. * * @param av1 the first AnnotationValue to compare * @param av2 the second AnnotationValue to compare * @return 0 if the two annotation values are the same */ @CompareToMethod private static int compareAnnotationValue(AnnotationValue av1, AnnotationValue av2) { if (av1 == av2) { return 0; } else if (av1 == null) { return -1; } else if (av2 == null) { return 1; } return compareAnnotationValueValue(av1.getValue(), av2.getValue()); } /** * Compares two annotation values for order. * * @param val1 a value returned by {@code AnnotationValue.getValue()} * @param val2 a value returned by {@code AnnotationValue.getValue()} * @return a negative integer, zero, or a positive integer as the first annotation value is less * than, equal to, or greater than the second annotation value */ @CompareToMethod private static int compareAnnotationValueValue(@Nullable Object val1, @Nullable Object val2) { if (val1 == val2) { return 0; } else if (val1 == null) { return -1; } else if (val2 == null) { return 1; } // 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 list1.size() - list2.size(); } // 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++) { Object v1 = list1.get(i); Object v2 = list2.get(i); int result = compareAnnotationValueValue(v1, v2); if (result != 0) { return result; } } return 0; } else if ((val1 instanceof AnnotationMirror) && (val2 instanceof AnnotationMirror)) { return compareAnnotationMirrors((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 compareAnnotationValue((AnnotationValue) val1, (AnnotationValue) val2); } if ((val1 instanceof Type.ClassType) && (val2 instanceof Type.ClassType)) { // Type.ClassType does not override equals if (TypesUtils.areSameDeclaredTypes((Type.ClassType) val1, (Type.ClassType) val2)) { return 0; } } if (Objects.equals(val1, val2)) { return 0; } int result = val1.toString().compareTo(val2.toString()); if (result == 0) { result = -1; } return result; } /** * Returns true if the given annotation has a @Inherited meta-annotation. * * @param anno the annotation to check for an @Inherited meta-annotation * @return true if the given annotation has a @Inherited meta-annotation */ public static boolean hasInheritedMeta(AnnotationMirror anno) { return anno.getAnnotationType().asElement().getAnnotation(Inherited.class) != null; } /** * Returns the set of {@link ElementKind}s to which {@code target} applies, ignoring TYPE_USE. * * @param target a location where an annotation can be written * @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. * * @param elementType the elementType to find ElementKinds for * @return the set of {@link ElementKind}s corresponding to {@code elementType} */ public static EnumSet getElementKindsForElementType(ElementType elementType) { switch (elementType) { case TYPE: return EnumSet.copyOf(ElementUtils.typeElementKinds()); 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: // TODO: Use MODULE enum constants directly instead of looking them up by name. // (Java 11) if (elementType.name().equals("MODULE")) { return EnumSet.of(ElementKind.valueOf("MODULE")); } if (elementType.name().equals("RECORD_COMPONENT")) { return EnumSet.of(ElementKind.valueOf("RECORD_COMPONENT")); } throw new BugInCF("Unrecognized ElementType: " + elementType); } } // ********************************************************************** // Annotation values: inefficient extractors that take an element name // ********************************************************************** /** * Get the element with the name {@code elementName} of the annotation {@code anno}. The result * has type {@code expectedType}. If there is no value for {@code elementName}, {@code * defaultValue} is returned * *

This method is intended only for use when the class of the annotation is not on the user's * classpath. This is for users of the Dataflow Framework that do not use the rest of the Checker * Framework. Type-checkers can assume that checker-qual.jar is on the classpath and should use * {@link #getElementValue(AnnotationMirror, ExecutableElement, Class)} or {@link * #getElementValue(AnnotationMirror, ExecutableElement, Class, Object)}. * * @param anno the annotation whose element to access * @param elementName the name of the element to access * @param expectedType the type of the element and the return value * @param defaultValue the value to return if the element is not present * @param the class of the type * @return the value of the element with the given name */ public static T getElementValueNotOnClasspath( AnnotationMirror anno, CharSequence elementName, Class expectedType, T defaultValue) { Map valmap = anno.getElementValues(); for (Map.Entry entry : valmap.entrySet()) { ExecutableElement elem = entry.getKey(); if (elem.getSimpleName().contentEquals(elementName)) { AnnotationValue val = entry.getValue(); try { return expectedType.cast(val.getValue()); } catch (ClassCastException e) { throw new BugInCF( "getElementValueNotOnClasspath(%s, %s, %s): val=%s, val.getValue()=%s [%s]", anno, elementName, expectedType, val, val.getValue(), val.getValue().getClass()); } } } return defaultValue; } /** * Returns the values of an annotation's elements, 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. * *

This method is intended for use only by the framework. Clients should use a method that * takes an {@link ExecutableElement}. * * @see AnnotationMirror#getElementValues() * @see JavacElements#getElementValuesWithDefaults(AnnotationMirror) * @param ad annotation to examine * @return the values of the annotation's elements, including defaults */ private static Map getElementValuesWithDefaults(AnnotationMirror ad) { // Most annotations have no elements. Map valMap = new ArrayMap<>(0); 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.putIfAbsent(meth, defaultValue); } } return valMap; } /** * Get the element with the name {@code elementName} of the annotation {@code anno}. The result * has type {@code expectedType}. * *

If the return type is an array, use {@link #getElementValueArray} instead. * *

If the return type is an enum, use {@link #getElementValueEnum} instead. * *

This method is intended only for use by the framework. A checker implementation should use * {@link #getElementValue(AnnotationMirror, ExecutableElement, Class)} or {@link * #getElementValue(AnnotationMirror, ExecutableElement, Class, Object)}. * * @param anno the annotation whose element to access * @param elementName the name of the element to access * @param expectedType the type of the element and the return value * @param the class of the type * @param useDefaults whether to apply default values to the element * @return the value of the element with the given name * @deprecated use {@link #getElementValue(AnnotationMirror, ExecutableElement, Class)} or {@link * #getElementValue(AnnotationMirror, ExecutableElement, Class, Object)} */ @Deprecated // for use only by the framework, not by clients public static T getElementValue( AnnotationMirror anno, CharSequence elementName, Class expectedType, boolean useDefaults) { Map valmap; if (useDefaults) { Map valmapTmp = getElementValuesWithDefaults(anno); valmap = valmapTmp; } else { valmap = anno.getElementValues(); } for (Map.Entry entry : valmap.entrySet()) { ExecutableElement elem = entry.getKey(); if (elem.getSimpleName().contentEquals(elementName)) { AnnotationValue val = entry.getValue(); try { return expectedType.cast(val.getValue()); } catch (ClassCastException e) { throw new BugInCF( "getElementValue(%s, %s, %s, %s): val=%s, val.getValue()=%s [%s]", anno, elementName, expectedType, useDefaults, val, val.getValue(), val.getValue().getClass()); } } } throw new NoSuchElementException( String.format( "No element with name \'%s\' in annotation %s; useDefaults=%s, valmap.keySet()=%s", elementName, anno, useDefaults, valmap.keySet())); } /** Differentiates NoSuchElementException from other BugInCF, for use by getElementValueOrNull. */ @SuppressWarnings("serial") private static class NoSuchElementException extends BugInCF { /** * Constructs a new NoSuchElementException. * * @param message the detail message */ @Pure public NoSuchElementException(String message) { super(message); } } /** * Get the element with the name {@code elementName} of the annotation {@code anno}, or return * null if no such element exists. * *

This method is intended only for use by the framework. A checker implementation should use * {@link #getElementValue(AnnotationMirror, ExecutableElement, Class, Object)}. * * @param anno the annotation whose element to access * @param elementName the name of the element to access * @param expectedType the type of the element and the return value * @param the class of the type * @param useDefaults whether to apply default values to the element * @return the value of the element with the given name, or null */ public static @Nullable T getElementValueOrNull( AnnotationMirror anno, CharSequence elementName, Class expectedType, boolean useDefaults) { // This implementation permits getElementValue to give a more detailed error message than if // getElementValue called getElementValueOrNull and threw an error if the result was null. try { return getElementValue(anno, elementName, expectedType, useDefaults); } catch (NoSuchElementException e) { return null; } } /** * Get the element with the name {@code elementName} of the annotation {@code anno}, or return * null if no such element exists. One element of the result has type {@code expectedType}. * *

This method is intended only for use by the framework. A checker implementation should use * {@link #getElementValue(AnnotationMirror, ExecutableElement, Class, Object)}. * * @param anno the annotation whose element to access * @param elementName the name of the element to access * @param expectedType the component type of the element and of the return value * @param the class of the component type * @param useDefaults whether to apply default values to the element * @return the value of the element with the given name, or null */ public static @Nullable List getElementValueArrayOrNull( AnnotationMirror anno, CharSequence elementName, Class expectedType, boolean useDefaults) { // This implementation permits getElementValue to give a more detailed error message than if // getElementValue called getElementValueOrNull and threw an error if the result was null. try { return getElementValueArray(anno, elementName, expectedType, useDefaults); } catch (NoSuchElementException e) { return null; } } /** * Get the element with the name {@code elementName} of the annotation {@code anno}, where the * element has an array type. One element of the result has 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. * *

This method is intended only for use by the framework. A checker implementation should use * {@code #getElementValueArray(AnnotationMirror, ExecutableElement, Class)} or {@code * #getElementValueArray(AnnotationMirror, ExecutableElement, Class, Object)}. * * @param anno the annotation to disassemble * @param elementName the name of the element to access * @param expectedType the component type of the element and of the return type * @param the class of the type * @param useDefaults whether to apply default values to the element * @return the value of the element with the given name; it is a new list, so it is safe for * clients to side-effect * @deprecated use {@code #getElementValueArray(AnnotationMirror, ExecutableElement, Class)} or * {@code #getElementValueArray(AnnotationMirror, ExecutableElement, Class, Object)} */ @Deprecated // for use only by the framework public static List getElementValueArray( AnnotationMirror anno, CharSequence elementName, Class expectedType, boolean useDefaults) { @SuppressWarnings("unchecked") List la = getElementValue(anno, elementName, List.class, useDefaults); List result = new ArrayList<>(la.size()); for (AnnotationValue a : la) { try { result.add(expectedType.cast(a.getValue())); } catch (Throwable t) { String err1 = String.format( "getElementValueArray(%n" + " anno=%s,%n" + " elementName=%s,%n" + " expectedType=%s,%n" + " useDefaults=%s)%n", anno, elementName, expectedType, useDefaults); String err2 = String.format( "Error in cast:%n expectedType=%s%n a=%s [%s]%n a.getValue()=%s [%s]", expectedType, a, a.getClass(), a.getValue(), a.getValue().getClass()); throw new BugInCF(err1 + "; " + err2, t); } } return result; } /** * Get the Name of the class that is referenced by element {@code elementName}. * *

This is a convenience method for the most common use-case. It is like {@code * getElementValue(anno, elementName, ClassType.class).getQualifiedName()}, but this method * ensures consistent use of the qualified name. * *

This method is intended only for use by the framework. A checker implementation should use * {@code anno.getElementValues().get(someElement).getValue().asElement().getQualifiedName();}. * * @param anno the annotation to disassemble * @param elementName the name of the element to access; it must be present in the annotation * @param useDefaults whether to apply default values to the element * @return the name of the class that is referenced by element with the given name; may be an * empty name, for a local or anonymous class * @deprecated use an ExecutableElement */ @Deprecated // permitted for use by the framework public static @CanonicalName Name getElementValueClassName( AnnotationMirror anno, CharSequence elementName, boolean useDefaults) { Type.ClassType ct = getElementValue(anno, elementName, Type.ClassType.class, useDefaults); // TODO: Is it a problem that this returns the type parameters too? Should I cut them off? @CanonicalName Name result = ct.asElement().getQualifiedName(); return result; } // ********************************************************************** // Annotation values: efficient extractors that take an ExecutableElement // ********************************************************************** /** * Get the given element of the annotation {@code anno}. The result has type {@code expectedType}. * *

If the return type is primitive, use {@link #getElementValueInt} or {@link * #getElementValueLong} instead. * *

If the return type is an array, use {@link #getElementValueArray} instead. * *

If the return type is an enum, use {@link #getElementValueEnum} instead. * * @param anno the annotation whose element to access * @param element the element to access; it must be present in the annotation * @param expectedType the type of the element and the return value * @param the class of the type * @return the value of the element with the given name */ public static T getElementValue( AnnotationMirror anno, ExecutableElement element, Class expectedType) { AnnotationValue av = anno.getElementValues().get(element); if (av == null) { throw new BugInCF("getElementValue(%s, %s, ...)", anno, element); } return expectedType.cast(av.getValue()); } /** * Get the given element of the annotation {@code anno}. The result has type {@code expectedType}. * *

If the return type is primitive, use {@link #getElementValueInt} or {@link * #getElementValueLong} instead. * *

If the return type is an array, use {@link #getElementValueArray} instead. * *

If the return type is an enum, use {@link #getElementValueEnum} instead. * * @param anno the annotation whose element to access * @param element the element to access * @param expectedType the type of the element and the return value * @param the class of the type * @param defaultValue the value to return if the element is not present * @return the value of the element with the given name */ public static T getElementValue( AnnotationMirror anno, ExecutableElement element, Class expectedType, T defaultValue) { AnnotationValue av = anno.getElementValues().get(element); if (av == null) { return defaultValue; } else { return expectedType.cast(av.getValue()); } } /** * Get the given boolean element of the annotation {@code anno}. * * @param anno the annotation whose element to access * @param element the element to access * @param defaultValue the value to return if the element is not present * @return the value of the element with the given name */ public static boolean getElementValueBoolean( AnnotationMirror anno, ExecutableElement element, boolean defaultValue) { AnnotationValue av = anno.getElementValues().get(element); if (av == null) { return defaultValue; } else { return (boolean) av.getValue(); } } /** * Get the given integer element of the annotation {@code anno}. * * @param anno the annotation whose element to access * @param element the element to access * @return the value of the element with the given name */ public static int getElementValueInt(AnnotationMirror anno, ExecutableElement element) { AnnotationValue av = anno.getElementValues().get(element); if (av == null) { throw new BugInCF("getElementValueInt(%s, %s, ...)", anno, element); } else { return (int) av.getValue(); } } /** * Get the given integer element of the annotation {@code anno}. * * @param anno the annotation whose element to access * @param element the element to access * @param defaultValue the value to return if the element is not present * @return the value of the element with the given name */ public static int getElementValueInt( AnnotationMirror anno, ExecutableElement element, int defaultValue) { AnnotationValue av = anno.getElementValues().get(element); if (av == null) { return defaultValue; } else { return (int) av.getValue(); } } /** * Get the given long element of the annotation {@code anno}. * * @param anno the annotation whose element to access * @param element the element to access * @param defaultValue the value to return if the element is not present * @return the value of the element with the given name */ public static long getElementValueLong( AnnotationMirror anno, ExecutableElement element, long defaultValue) { AnnotationValue av = anno.getElementValues().get(element); if (av == null) { return defaultValue; } else { return (long) av.getValue(); } } /** * Get the element with the name {@code name} of the annotation {@code anno}. The result is an * enum of type {@code T}. * * @param anno the annotation to disassemble * @param element the element to access; it must be present in the annotation * @param expectedType the type of the element and the return value, an enum * @param the class of the type * @return the value of the element with the given name */ public static > T getElementValueEnum( AnnotationMirror anno, ExecutableElement element, Class expectedType) { AnnotationValue av = anno.getElementValues().get(element); if (av == null) { throw new BugInCF("getElementValueEnum(%s, %s, ...)", anno, element); } VariableElement ve = (VariableElement) av.getValue(); return Enum.valueOf(expectedType, ve.getSimpleName().toString()); } /** * Get the element with the name {@code name} of the annotation {@code anno}. The result is an * enum of type {@code T}. * * @param anno the annotation to disassemble * @param element the element to access * @param expectedType the type of the element and the return value, an enum * @param the class of the type * @param defaultValue the value to return if the element is not present * @return the value of the element with the given name */ public static > T getElementValueEnum( AnnotationMirror anno, ExecutableElement element, Class expectedType, T defaultValue) { AnnotationValue av = anno.getElementValues().get(element); if (av == null) { return defaultValue; } else { VariableElement ve = (VariableElement) av.getValue(); return Enum.valueOf(expectedType, ve.getSimpleName().toString()); } } /** * Get the element with the name {@code name} of the annotation {@code anno}. The result is an * array of type {@code T}. * * @param anno the annotation to disassemble * @param element the element to access; it must be present in the annotation * @param expectedType the component type of the element and of the return value, an enum * @param the enum class of the component type * @return the value of the element with the given name */ public static > T[] getElementValueEnumArray( AnnotationMirror anno, ExecutableElement element, Class expectedType) { AnnotationValue av = anno.getElementValues().get(element); if (av == null) { throw new BugInCF("getElementValueEnumArray(%s, %s, ...)", anno, element); } return AnnotationUtils.annotationValueListToEnumArray(av, expectedType); } /** * Get the element with the name {@code name} of the annotation {@code anno}. The result is an * array of type {@code T}. * * @param anno the annotation to disassemble * @param element the element to access * @param expectedType the component type of the element and of the return type * @param the enum class of the component type * @param defaultValue the value to return if the annotation does not have the element * @return the value of the element with the given name */ public static > T[] getElementValueEnumArray( AnnotationMirror anno, ExecutableElement element, Class expectedType, T[] defaultValue) { AnnotationValue av = anno.getElementValues().get(element); if (av == null) { return defaultValue; } else { return AnnotationUtils.annotationValueListToEnumArray(av, expectedType); } } /** * Get the given element of the annotation {@code anno}, where the element has an array type. One * element of the result has type {@code expectedType}. * * @param anno the annotation to disassemble * @param element the element to access; it must be present in the annotation * @param expectedType the component type of the element and of the return type * @param the class of the component type * @return the value of the element with the given name; it is a new list, so it is safe for * clients to side-effect */ public static List getElementValueArray( AnnotationMirror anno, ExecutableElement element, Class expectedType) { AnnotationValue av = anno.getElementValues().get(element); if (av == null) { throw new BugInCF("getElementValueArray(%s, %s, ...)", anno, element); } return annotationValueToList(av, expectedType); } /** * Get the given element of the annotation {@code anno}, where the element has an array type. One * element of the result has type {@code expectedType}. * * @param anno the annotation to disassemble * @param element the element to access * @param expectedType the component type of the element and of the return type * @param the class of the component type * @param defaultValue the value to return if the element is not present * @return the value of the element with the given name; it is a new list, so it is safe for * clients to side-effect */ public static List getElementValueArray( AnnotationMirror anno, ExecutableElement element, Class expectedType, List defaultValue) { AnnotationValue av = anno.getElementValues().get(element); if (av == null) { return defaultValue; } else { return annotationValueToList(av, expectedType); } } /** * Converts a list of AnnotationValue to an array of enum. * * @param the element type of the enum array * @param avList a list of AnnotationValue * @param expectedType the component type of the element and of the return type, an enum * @return an array of enum, converted from the input list */ public static > T[] annotationValueListToEnumArray( AnnotationValue avList, Class expectedType) { @SuppressWarnings("unchecked") List list = (List) avList.getValue(); return annotationValueListToEnumArray(list, expectedType); } /** * Converts a list of AnnotationValue to an array of enum. * * @param the element type of the enum array * @param la a list of AnnotationValue * @param expectedType the component type of the element and of the return type, an enum * @return an array of enum, converted from the input list */ public static > T[] annotationValueListToEnumArray( List la, Class expectedType) { int size = la.size(); @SuppressWarnings("unchecked") T[] result = (T[]) Array.newInstance(expectedType, size); for (int i = 0; i < size; i++) { AnnotationValue a = la.get(i); T value = Enum.valueOf(expectedType, a.getValue().toString()); result[i] = value; } return result; } /** * Get the Name of the class that is referenced by element {@code element}. * *

This is a convenience method for the most common use-case. It is like {@code * getElementValue(anno, element, ClassType.class).getQualifiedName()}, but this method ensures * consistent use of the qualified name. * *

This method is intended only for use by the framework. A checker implementation should use * {@code anno.getElementValues().get(someElement).getValue().asElement().getQualifiedName();}. * * @param anno the annotation to disassemble * @param element the element to access; it must be present in the annotation * @return the name of the class that is referenced by element with the given name; may be an * empty name, for a local or anonymous class */ public static @CanonicalName Name getElementValueClassName( AnnotationMirror anno, ExecutableElement element) { Type.ClassType ct = getElementValue(anno, element, Type.ClassType.class); if (ct == null) { throw new BugInCF("getElementValueClassName(%s, %s, ...)", anno, element); } // TODO: Is it a problem that this returns the type parameters too? Should I cut them off? @CanonicalName Name result = ct.asElement().getQualifiedName(); return result; } /** * Get the list of Names of the classes that are referenced by element {@code element}. It fails * if the class wasn't found. * * @param anno the annotation whose field to access; it must be present in the annotation * @param element the element/field of {@code anno} whose content is a list of classes * @return the names of classes in {@code anno.annoElement} */ public static List<@CanonicalName Name> getElementValueClassNames( AnnotationMirror anno, ExecutableElement element) { List la = getElementValueArray(anno, element, Type.ClassType.class); return CollectionsPlume.mapList( (Type.ClassType classType) -> classType.asElement().getQualifiedName(), la); } // ********************************************************************** // Annotation values: other methods (e.g., testing and transforming) // ********************************************************************** /** * 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. * * @param am1 the first AnnotationMirror to compare * @param am2 the second AnnotationMirror to compare * @return true if the two annotations have the same elements (fields) */ @EqualsMethod public static boolean sameElementValues(AnnotationMirror am1, AnnotationMirror am2) { if (am1 == am2) { return true; } 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); @SuppressWarnings("interning:not.interned") // optimization via equality test boolean identical = aval1 == aval2; if (identical) { // Handles when both aval1 and aval2 are null, and maybe other cases too. continue; } 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. * * @param av1 the first AnnotationValue to compare * @param av2 the second AnnotationValue to compare * @return true if the two annotation values are the same */ public static boolean sameAnnotationValue(AnnotationValue av1, AnnotationValue av2) { return compareAnnotationValue(av1, av2) == 0; } /** * Returns true if an AnnotationValue list contains the given value. * *

Using this method is slightly cheaper than creating a new {@code List} just for the * purpose of testing containment within it. * * @param avList an AnnotationValue that is null or a list of Strings * @param s a string * @return true if {@code av} contains {@code s} */ public static boolean annotationValueContains(@Nullable AnnotationValue avList, String s) { if (avList == null) { return false; } @SuppressWarnings("unchecked") List list = (List) avList.getValue(); return annotationValueContains(list, s); } /** * Returns true if an AnnotationValue list contains the given value. * *

Using this method is slightly cheaper than creating a new {@code List} just for the * purpose of testing containment within it. * * @param avList a list of Strings (as {@code AnnotationValue}s) * @param s a string * @return true if {@code av} contains {@code s} */ public static boolean annotationValueContains(List avList, String s) { for (AnnotationValue av : avList) { if (av.getValue().equals(s)) { return true; } } return false; } /** * Returns true if an AnnotationValue list contains a value whose {@code toString()} is the given * string. * *

Using this method is slightly cheaper than creating a new {@code List} just for the purpose * of testing containment within it. * * @param avList an AnnotationValue that is null or a list * @param s a string * @return true if {@code av} contains {@code s} */ public static boolean annotationValueContainsToString( @Nullable AnnotationValue avList, String s) { if (avList == null) { return false; } @SuppressWarnings("unchecked") List list = (List) avList.getValue(); return annotationValueContainsToString(list, s); } /** * Returns true if an AnnotationValue list contains a value whose {@code toString()} is the given * string. * *

Using this method is slightly cheaper than creating a new {@code List} just for the purpose * of testing containment within it. * * @param avList a list of Strings (as {@code AnnotationValue}s) * @param s a string * @return true if {@code av} contains {@code s} */ public static boolean annotationValueContainsToString( List avList, String s) { for (AnnotationValue av : avList) { if (av.getValue().toString().equals(s)) { return true; } } return false; } /** * Converts an annotation value to a list. * *

To test containment, use {@link #annotationValueContains(AnnotationValue, String)} or {@link * #annotationValueContainsToString(AnnotationValue, String)}. * * @param avList an AnnotationValue that is a list of Strings * @param expectedType the component type of the argument and of the return type, an enum * @param the class of the type * @return the annotation value, converted to a list */ public static List annotationValueToList(AnnotationValue avList, Class expectedType) { @SuppressWarnings("unchecked") List list = (List) avList.getValue(); return annotationValueToList(list, expectedType); } /** * Converts an annotation value to a list. * *

To test containment, use {@link #annotationValueContains(List, String)} or {@link * #annotationValueContainsToString(List, String)}. * * @param avList a list of Strings (as {@code AnnotationValue}s) * @param expectedType the component type of the argument and of the return type, an enum * @param the class of the type * @return the annotation value, converted to a list */ public static List annotationValueToList( List avList, Class expectedType) { List result = new ArrayList<>(avList.size()); for (AnnotationValue a : avList) { try { result.add(expectedType.cast(a.getValue())); } catch (Throwable t) { String err1 = String.format("annotationValueToList(%s, %s)", avList, expectedType); String err2 = String.format( "a=%s [%s]%n a.getValue()=%s [%s]", a, a.getClass(), a.getValue(), a.getValue().getClass()); throw new BugInCF(err1 + " " + err2, t); } } return result; } // ********************************************************************** // Other methods // ********************************************************************** // The Javadoc doesn't use @link because framework is a different project than this one // (javacutil). /** * Update a map, to add {@code newQual} to the set that {@code key} maps to. The mapped-to element * is an unmodifiable set. * *

See * org.checkerframework.framework.type.QualifierHierarchy#updateMappingToMutableSet(QualifierHierarchy, * Map, Object, AnnotationMirror). * * @param map the map to update * @param key the key whose value to update * @param newQual the element to add to the given key's value * @param the key type */ public static void updateMappingToImmutableSet( Map map, T key, AnnotationMirrorSet newQual) { AnnotationMirrorSet result = new AnnotationMirrorSet(); // 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); } result.makeUnmodifiable(); map.put(key, 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 AnnotationMirrorSet getExplicitAnnotationsOnConstructorResult( MethodTree constructorDeclaration) { AnnotationMirrorSet annotationSet = new AnnotationMirrorSet(); ModifiersTree modifiersTree = constructorDeclaration.getModifiers(); if (modifiersTree != null) { List annotationTrees = modifiersTree.getAnnotations(); annotationSet.addAll(TreeUtils.annotationsFromTypeAnnotationTrees(annotationTrees)); } return annotationSet; } /** * Returns true if anno is a declaration annotation. In other words, returns true if anno cannot * be written on uses of types. * * @param anno the AnnotationMirror * @return true if anno is a declaration annotation */ public static boolean isDeclarationAnnotation(AnnotationMirror anno) { TypeElement elem = (TypeElement) anno.getAnnotationType().asElement(); Target t = elem.getAnnotation(Target.class); if (t == null) { return true; } for (ElementType elementType : t.value()) { if (elementType == ElementType.TYPE_USE) { return false; } } return true; } /** * Returns true if the given array contains {@link ElementType#TYPE_USE}, false otherwise. * * @param elements an array of {@link ElementType} values * @param cls the annotation class being tested; used for diagnostic messages only * @return true iff the give array contains {@link ElementType#TYPE_USE} * @throws RuntimeException if the array contains both {@link ElementType#TYPE_USE} and something * besides {@link ElementType#TYPE_PARAMETER} */ public static boolean hasTypeQualifierElementTypes(ElementType[] elements, Class cls) { // True if the array contains TYPE_USE boolean hasTypeUse = false; // Non-null if the array contains an element other than TYPE_USE or TYPE_PARAMETER ElementType otherElementType = null; for (ElementType element : elements) { if (element == ElementType.TYPE_USE) { hasTypeUse = true; } else if (element != ElementType.TYPE_PARAMETER) { otherElementType = element; } if (hasTypeUse && otherElementType != null) { throw new BugInCF( "@Target meta-annotation should not contain both TYPE_USE and " + otherElementType + ", for annotation " + cls.getName()); } } return hasTypeUse; } /** * Returns a string representation of the annotation mirrors, using simple (not fully-qualified) * names. * * @param annos annotations to format * @return the string representation, using simple (not fully-qualified) names */ @SideEffectFree public static String toStringSimple(AnnotationMirrorSet annos) { DefaultAnnotationFormatter defaultAnnotationFormatter = new DefaultAnnotationFormatter(); StringJoiner result = new StringJoiner(" "); for (AnnotationMirror am : annos) { result.add(defaultAnnotationFormatter.formatAnnotationMirror(am)); } return result.toString(); } /** * Converts an AnnotationMirror to a Class. Throws an exception if it is not able to do so. * * @param am an AnnotationMirror * @return the Class corresponding to the given AnnotationMirror */ public static Class annotationMirrorToClass(AnnotationMirror am) { try { return Class.forName(AnnotationUtils.annotationBinaryName(am)); } catch (ClassNotFoundException e) { throw new BugInCF(e); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy