blob: c2d84f9993427baa28cdfa5eb90b9393fcc8875b [file] [log] [blame]
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);
}
}
}