| 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.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.lang.reflect.Array; |
| 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.NavigableSet; |
| import java.util.Objects; |
| import java.util.Set; |
| import java.util.StringJoiner; |
| 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.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.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.framework.util.DefaultAnnotationFormatter; |
| import org.checkerframework.javacutil.AnnotationBuilder.CheckerFrameworkAnnotationMirror; |
| import org.plumelib.util.CollectionsPlume; |
| |
| /** A utility class for working with annotations. */ |
| 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; |
| } |
| final DeclaredType annoType = annotation.getAnnotationType(); |
| final TypeElement elm = (TypeElement) annoType.asElement(); |
| @SuppressWarnings("signature:assignment") // JDK needs annotations |
| @CanonicalName String name = elm.getQualifiedName().toString(); |
| return name; |
| } |
| |
| /** |
| * 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) { |
| final DeclaredType annoType = annotation.getAnnotationType(); |
| final 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. |
| * |
| * <p>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 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) { |
| if (a1 == a2) { |
| return true; |
| } |
| if (a1 == null || a2 == null) { |
| throw new BugInCF("Unexpected null argument: areSameByName(%s, %s)", a1, a2); |
| } |
| |
| if (a1 instanceof CheckerFrameworkAnnotationMirror |
| && a2 instanceof CheckerFrameworkAnnotationMirror) { |
| return ((CheckerFrameworkAnnotationMirror) a1).annotationName |
| == ((CheckerFrameworkAnnotationMirror) a2).annotationName; |
| } |
| |
| 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. |
| * |
| * @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. |
| * |
| * <p>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<? extends Annotation> 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<? extends AnnotationMirror> c1, Collection<? extends AnnotationMirror> c2) { |
| if (c1.size() != c2.size()) { |
| return false; |
| } |
| if (c1.size() == 1) { |
| return areSame(c1.iterator().next(), c2.iterator().next()); |
| } |
| |
| // while loop depends on SortedSet implementation. |
| NavigableSet<AnnotationMirror> s1 = createAnnotationSet(); |
| NavigableSet<AnnotationMirror> s2 = createAnnotationSet(); |
| s1.addAll(c1); |
| s2.addAll(c2); |
| Iterator<AnnotationMirror> iter1 = s1.iterator(); |
| Iterator<AnnotationMirror> iter2 = s2.iterator(); |
| |
| while (iter1.hasNext()) { |
| AnnotationMirror anno1 = iter1.next(); |
| AnnotationMirror anno2 = iter2.next(); |
| if (!areSame(anno1, anno2)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Checks that the collection contains the annotation. Using Collection.contains does not always |
| * work, because it does not use areSame for comparison. |
| * |
| * @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<? extends AnnotationMirror> 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<? extends AnnotationMirror> c, AnnotationMirror anno) { |
| for (AnnotationMirror an : c) { |
| if (AnnotationUtils.areSame(an, anno)) { |
| return an; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Checks that the collection contains the annotation. Using Collection.contains does not always |
| * work, because it does not use areSame for comparison. |
| * |
| * <p>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<? extends AnnotationMirror> c, Class<? extends Annotation> anno) { |
| return getAnnotationByClass(c, anno) != null; |
| } |
| |
| /** |
| * Returns the AnnotationMirror in {@code c} that has the same class as {@code anno}. |
| * |
| * <p>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<? extends AnnotationMirror> c, Class<? extends Annotation> anno) { |
| for (AnnotationMirror an : c) { |
| if (AnnotationUtils.areSameByClass(an, anno)) { |
| return an; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Checks that the collection contains 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<? extends AnnotationMirror> 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<? extends AnnotationMirror> 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<? extends AnnotationMirror> 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<? extends AnnotationMirror> c, AnnotationMirror anno) { |
| for (AnnotationMirror an : c) { |
| if (AnnotationUtils.areSameByName(an, anno)) { |
| return an; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Provide 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) { |
| if (!AnnotationUtils.areSameByName(a1, a2)) { |
| return annotationName(a1).compareTo(annotationName(a2)); |
| } |
| |
| // The annotations have the same name, but different values, so compare values. |
| Map<? extends ExecutableElement, ? extends AnnotationValue> vals1 = a1.getElementValues(); |
| Map<? extends ExecutableElement, ? extends AnnotationValue> vals2 = a2.getElementValues(); |
| Set<ExecutableElement> sortedElements = |
| new TreeSet<>(Comparator.comparing(ElementUtils::getSimpleSignature)); |
| sortedElements.addAll( |
| ElementFilter.methodsIn(a1.getAnnotationType().asElement().getEnclosedElements())); |
| |
| 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 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; |
| } |
| |
| /** |
| * Create a map suitable for storing {@link AnnotationMirror} as keys. |
| * |
| * <p>It can store one instance of {@link AnnotationMirror} of a given declared type, regardless |
| * of the annotation element values. |
| * |
| * @param <V> the value of the map |
| * @return a new map with {@link AnnotationMirror} as key |
| */ |
| public static <V> Map<AnnotationMirror, V> createAnnotationMap() { |
| return new TreeMap<>(AnnotationUtils::compareAnnotationMirrors); |
| } |
| |
| /** |
| * Constructs a {@link Set} for storing {@link AnnotationMirror}s. |
| * |
| * <p>It stores at most once instance of {@link AnnotationMirror} of a given type, regardless of |
| * the annotation element values. |
| * |
| * @return a sorted new set to store {@link AnnotationMirror} as element |
| */ |
| public static NavigableSet<AnnotationMirror> createAnnotationSet() { |
| return new TreeSet<>(AnnotationUtils::compareAnnotationMirrors); |
| } |
| |
| /** |
| * Constructs a {@link Set} for storing {@link AnnotationMirror}s contain all the annotations in |
| * {@code annos}. |
| * |
| * <p>It stores at most once instance of {@link AnnotationMirror} of a given type, regardless of |
| * the annotation element values. |
| * |
| * @param annos a Collection of AnnotationMirrors to put in the created set |
| * @return a sorted new set to store {@link AnnotationMirror} as element |
| */ |
| public static NavigableSet<AnnotationMirror> createAnnotationSet( |
| Collection<AnnotationMirror> annos) { |
| TreeSet<AnnotationMirror> set = new TreeSet<>(AnnotationUtils::compareAnnotationMirrors); |
| set.addAll(annos); |
| return set; |
| } |
| |
| /** |
| * Constructs an unmodifiable {@link Set} for storing {@link AnnotationMirror}s contain all the |
| * annotations in {@code annos}. |
| * |
| * <p>It stores at most once instance of {@link AnnotationMirror} of a given type, regardless of |
| * the annotation element values. |
| * |
| * @param annos a Collection of AnnotationMirrors to put in the created set |
| * @return a sorted, unmodifiable, new set to store {@link AnnotationMirror} as element |
| */ |
| public static NavigableSet<AnnotationMirror> createUnmodifiableAnnotationSet( |
| Collection<AnnotationMirror> annos) { |
| TreeSet<AnnotationMirror> set = new TreeSet<>(AnnotationUtils::compareAnnotationMirrors); |
| set.addAll(annos); |
| return Collections.unmodifiableNavigableSet(set); |
| } |
| |
| /** |
| * 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<ElementKind> getElementKindsForTarget(@Nullable Target target) { |
| if (target == null) { |
| // A missing @Target implies that the annotation can be written everywhere. |
| return EnumSet.allOf(ElementKind.class); |
| } |
| EnumSet<ElementKind> 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<ElementKind> 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 |
| // ********************************************************************** |
| |
| /** |
| * 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. |
| * |
| * <p>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 |
| * @deprecated use a method that takes an {@link ExecutableElement} |
| */ |
| @Deprecated // 2021-03-29; do not remove, just make private |
| public static Map<? extends ExecutableElement, ? extends AnnotationValue> |
| getElementValuesWithDefaults(AnnotationMirror ad) { |
| Map<ExecutableElement, AnnotationValue> 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.putIfAbsent(meth, defaultValue); |
| } |
| } |
| return valMap; |
| } |
| |
| /** |
| * Verify whether the element with the name {@code elementName} exists in the annotation {@code |
| * anno}. |
| * |
| * <p>This method is intended for use only by the framework. Clients should use a method that |
| * takes an {@link ExecutableElement}. |
| * |
| * @param anno the annotation to examine |
| * @param elementName the name of the element |
| * @return whether the element exists in anno |
| * @deprecated use a method that takes an {@link ExecutableElement} |
| */ |
| @Deprecated // 2021-03-30 |
| public static boolean hasElementValue(AnnotationMirror anno, CharSequence elementName) { |
| Map<? extends ExecutableElement, ? extends AnnotationValue> valmap = anno.getElementValues(); |
| for (ExecutableElement elem : valmap.keySet()) { |
| if (elem.getSimpleName().contentEquals(elementName)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Get the element with the name {@code elementName} of the annotation {@code anno}. The result |
| * has type {@code expectedType}. |
| * |
| * <p>If the return type is an array, use {@link #getElementValueArray} instead. |
| * |
| * <p>If the return type is an enum, use {@link #getElementValueEnum} instead. |
| * |
| * <p>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 <T> 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 |
| public static <T> T getElementValue( |
| AnnotationMirror anno, CharSequence elementName, Class<T> expectedType, boolean useDefaults) { |
| Map<? extends ExecutableElement, ? extends AnnotationValue> valmap; |
| if (useDefaults) { |
| @SuppressWarnings( |
| "deprecation" // remove when getElementValuesWithDefaults is made private and |
| // non-deprecated |
| ) |
| Map<? extends ExecutableElement, ? extends AnnotationValue> valmapTmp = |
| getElementValuesWithDefaults(anno); |
| valmap = valmapTmp; |
| } else { |
| valmap = anno.getElementValues(); |
| } |
| for (ExecutableElement elem : valmap.keySet()) { |
| if (elem.getSimpleName().contentEquals(elementName)) { |
| AnnotationValue val = valmap.get(elem); |
| 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. |
| * |
| * <p>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 <T> 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 <T> @Nullable T getElementValueOrNull( |
| AnnotationMirror anno, CharSequence elementName, Class<T> expectedType, boolean useDefaults) { |
| // This implementation permits getElementValue 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}. |
| * |
| * <p>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 <T> 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 <T> @Nullable List<T> getElementValueArrayOrNull( |
| AnnotationMirror anno, CharSequence elementName, Class<T> expectedType, boolean useDefaults) { |
| // This implementation permits getElementValue 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 name} of the annotation {@code anno}. The result is an |
| * enum of type {@code T}. |
| * |
| * <p>This method is intended only for use by the framework. A checker implementation should use |
| * {@link #getElementValueEnum(AnnotationMirror, ExecutableElement, Class)} or {@link |
| * #getElementValueEnum(AnnotationMirror, ExecutableElement, Class, Enum)}. |
| * |
| * @param anno the annotation to disassemble |
| * @param elementName the name of the element to access |
| * @param expectedType the type of the element and the return value, an enum |
| * @param <T> 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 #getElementValueEnum(AnnotationMirror, ExecutableElement, Class)} or |
| * {@link #getElementValueEnum(AnnotationMirror, ExecutableElement, Class, Enum)} |
| */ |
| @Deprecated // 2021-03-29 |
| public static <T extends Enum<T>> T getElementValueEnum( |
| AnnotationMirror anno, CharSequence elementName, Class<T> expectedType, boolean useDefaults) { |
| VarSymbol vs = getElementValue(anno, elementName, VarSymbol.class, useDefaults); |
| T value = Enum.valueOf(expectedType, vs.getSimpleName().toString()); |
| return value; |
| } |
| |
| /** |
| * 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}. |
| * |
| * <p>Parameter useDefaults is used to determine whether default values should be used for |
| * annotation values. Finding defaults requires more computation, so should be false when no |
| * defaulting is needed. |
| * |
| * <p>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 <T> 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 <T> List<T> getElementValueArray( |
| AnnotationMirror anno, CharSequence elementName, Class<T> expectedType, boolean useDefaults) { |
| @SuppressWarnings("unchecked") |
| List<AnnotationValue> la = getElementValue(anno, elementName, List.class, useDefaults); |
| List<T> 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 element with the name {@code elementName} of the annotation {@code anno}, or the |
| * default value if no element is present explicitly, where the element has an array type and the |
| * elements are {@code Enum}s. One element of the result has type {@code expectedType}. |
| * |
| * <p>This method is intended only for use by the framework. A checker implementation should use |
| * {@link #getElementValueEnumArray(AnnotationMirror, ExecutableElement, Class)} or {@link |
| * #getElementValueEnumArray(AnnotationMirror, ExecutableElement, Class, Enum[])}. |
| * |
| * @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, an enum |
| * @param <T> 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 #getElementValueEnumArray(AnnotationMirror, ExecutableElement, Class)} |
| * or {@link #getElementValueEnumArray(AnnotationMirror, ExecutableElement, Class, Enum[])} |
| */ |
| @Deprecated // 2021-03-29 |
| public static <T extends Enum<T>> T[] getElementValueEnumArray( |
| AnnotationMirror anno, CharSequence elementName, Class<T> expectedType, boolean useDefaults) { |
| @SuppressWarnings("unchecked") |
| List<AnnotationValue> la = getElementValue(anno, elementName, List.class, useDefaults); |
| return annotationValueListToEnumArray(la, expectedType); |
| } |
| |
| /** |
| * Get the Name of the class that is referenced by element {@code elementName}. |
| * |
| * <p>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. |
| * |
| * <p>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; |
| } |
| |
| /** |
| * Get the list of Names of the classes that are referenced by element {@code elementName}. It |
| * fails if the class wasn't found. |
| * |
| * <p>This method is intended only for use by the framework. A checker implementation should use |
| * {@link #getElementValueClassNames(AnnotationMirror, ExecutableElement)}. |
| * |
| * @param anno the annotation whose field to access; it must be present in the annotation |
| * @param annoElement the element/field of {@code anno} whose content is a list of classes |
| * @param useDefaults whether to apply default values to the element |
| * @return the names of classes in {@code anno.annoElement} |
| * @deprecated use {@link #getElementValueClassNames(AnnotationMirror,ExecutableElement)} |
| */ |
| @Deprecated // 2021-03-29 |
| public static List<@CanonicalName Name> getElementValueClassNames( |
| AnnotationMirror anno, CharSequence annoElement, boolean useDefaults) { |
| List<Type.ClassType> la = |
| getElementValueArray(anno, annoElement, Type.ClassType.class, useDefaults); |
| return CollectionsPlume.<Type.ClassType, @CanonicalName Name>mapList( |
| (Type.ClassType classType) -> classType.asElement().getQualifiedName(), la); |
| } |
| |
| // ********************************************************************** |
| // Annotation values: efficient extractors that take an ExecutableElement |
| // ********************************************************************** |
| |
| /** |
| * Get the given element of the annotation {@code anno}. The result has type {@code expectedType}. |
| * |
| * <p>If the return type is primitive, use {@link #getElementValueInt} or {@link |
| * #getElementValueLong} instead. |
| * |
| * <p>If the return type is an array, use {@link #getElementValueArray} instead. |
| * |
| * <p>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 <T> the class of the type |
| * @return the value of the element with the given name |
| */ |
| public static <T> @Nullable T getElementValue( |
| AnnotationMirror anno, ExecutableElement element, Class<T> 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}. |
| * |
| * <p>If the return type is primitive, use {@link #getElementValueInt} or {@link |
| * #getElementValueLong} instead. |
| * |
| * <p>If the return type is an array, use {@link #getElementValueArray} instead. |
| * |
| * <p>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 <T> 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> @Nullable T getElementValue( |
| AnnotationMirror anno, ExecutableElement element, Class<T> 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 <T> the class of the type |
| * @return the value of the element with the given name |
| */ |
| public static <T extends Enum<T>> T getElementValueEnum( |
| AnnotationMirror anno, ExecutableElement element, Class<T> 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 <T> 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 extends Enum<T>> T getElementValueEnum( |
| AnnotationMirror anno, ExecutableElement element, Class<T> 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 <T> the enum class of the component type |
| * @return the value of the element with the given name |
| */ |
| public static <T extends Enum<T>> T[] getElementValueEnumArray( |
| AnnotationMirror anno, ExecutableElement element, Class<T> 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 <T> 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 extends Enum<T>> T[] getElementValueEnumArray( |
| AnnotationMirror anno, ExecutableElement element, Class<T> 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 <T> 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 <T> List<T> getElementValueArray( |
| AnnotationMirror anno, ExecutableElement element, Class<T> expectedType) { |
| AnnotationValue av = anno.getElementValues().get(element); |
| if (av == null) { |
| throw new BugInCF("getElementValueArray(%s, %s, ...)", anno, element); |
| } |
| return AnnotationUtils.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 <T> 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 <T> List<T> getElementValueArray( |
| AnnotationMirror anno, |
| ExecutableElement element, |
| Class<T> expectedType, |
| List<T> defaultValue) { |
| AnnotationValue av = anno.getElementValues().get(element); |
| if (av == null) { |
| return defaultValue; |
| } else { |
| return AnnotationUtils.annotationValueToList(av, expectedType); |
| } |
| } |
| |
| /** |
| * Converts a list of AnnotationValue to an array of enum. |
| * |
| * @param <T> 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 extends Enum<T>> T[] annotationValueListToEnumArray( |
| AnnotationValue avList, Class<T> expectedType) { |
| @SuppressWarnings("unchecked") |
| List<AnnotationValue> list = (List<AnnotationValue>) avList.getValue(); |
| return annotationValueListToEnumArray(list, expectedType); |
| } |
| |
| /** |
| * Converts a list of AnnotationValue to an array of enum. |
| * |
| * @param <T> 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 extends Enum<T>> T[] annotationValueListToEnumArray( |
| List<AnnotationValue> la, Class<T> 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}. |
| * |
| * <p>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. |
| * |
| * <p>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<Type.ClassType> la = getElementValueArray(anno, element, Type.ClassType.class); |
| return CollectionsPlume.<Type.ClassType, @CanonicalName Name>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 if the two annotations have the same elements (fields) |
| */ |
| @EqualsMethod |
| public static boolean sameElementValues(AnnotationMirror am1, AnnotationMirror am2) { |
| if (am1 == am2) { |
| return true; |
| } |
| |
| Map<? extends ExecutableElement, ? extends AnnotationValue> vals1 = am1.getElementValues(); |
| Map<? extends ExecutableElement, ? extends AnnotationValue> 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 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. |
| * |
| * <p>Using this method is slightly cheaper than creating a new {@code List<String>} 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<? extends AnnotationValue> list = (List<? extends AnnotationValue>) avList.getValue(); |
| return annotationValueContains(list, s); |
| } |
| |
| /** |
| * Returns true if an AnnotationValue list contains the given value. |
| * |
| * <p>Using this method is slightly cheaper than creating a new {@code List<String>} 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<? extends AnnotationValue> 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. |
| * |
| * <p>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<? extends AnnotationValue> list = (List<? extends AnnotationValue>) avList.getValue(); |
| return annotationValueContainsToString(list, s); |
| } |
| |
| /** |
| * Returns true if an AnnotationValue list contains a value whose {@code toString()} is the given |
| * string. |
| * |
| * <p>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<? extends AnnotationValue> avList, String s) { |
| for (AnnotationValue av : avList) { |
| if (av.getValue().toString().equals(s)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Converts an annotation value to a list. |
| * |
| * <p>To test containment, use {@link #annotationValueContains(AnnotationValue, String)} or {@link |
| * #annotationValueContainsToString(AnnotationValue, String)}. |
| * |
| * @param avList an AnnotationValue that is null or a list of Strings |
| * @param expectedType the component type of the argument and of the return type, an enum |
| * @param <T> the class of the type |
| * @return the annotation value, converted to a list |
| */ |
| public static <T> List<T> annotationValueToList(AnnotationValue avList, Class<T> expectedType) { |
| @SuppressWarnings("unchecked") |
| List<? extends AnnotationValue> list = (List<? extends AnnotationValue>) avList.getValue(); |
| return annotationValueToList(list, expectedType); |
| } |
| |
| /** |
| * Converts an annotation value to a list. |
| * |
| * <p>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 <T> the class of the type |
| * @return the annotation value, converted to a list |
| */ |
| public static <T> List<T> annotationValueToList( |
| List<? extends AnnotationValue> avList, Class<T> expectedType) { |
| List<T> 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. |
| * |
| * <p>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 <T> the key type |
| */ |
| public static <T extends @NonNull Object> void updateMappingToImmutableSet( |
| Map<T, Set<AnnotationMirror>> map, T key, Set<AnnotationMirror> newQual) { |
| |
| Set<AnnotationMirror> result = AnnotationUtils.createAnnotationSet(); |
| // TODO: if T is also an AnnotationMirror, should we use areSame? |
| if (!map.containsKey(key)) { |
| result.addAll(newQual); |
| } else { |
| result.addAll(map.get(key)); |
| result.addAll(newQual); |
| } |
| map.put(key, Collections.unmodifiableSet(result)); |
| } |
| |
| /** |
| * Returns the annotations explicitly written on a constructor result. Callers should check that |
| * {@code constructorDeclaration} is in fact a declaration of a constructor. |
| * |
| * @param constructorDeclaration declaration tree of constructor |
| * @return set of annotations explicit on the resulting type of the constructor |
| */ |
| public static Set<AnnotationMirror> getExplicitAnnotationsOnConstructorResult( |
| MethodTree constructorDeclaration) { |
| Set<AnnotationMirror> annotationSet = AnnotationUtils.createAnnotationSet(); |
| ModifiersTree modifiersTree = constructorDeclaration.getModifiers(); |
| if (modifiersTree != null) { |
| List<? extends AnnotationTree> annotationTrees = modifiersTree.getAnnotations(); |
| annotationSet.addAll(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(Set<AnnotationMirror> 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); |
| } |
| } |
| } |