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

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

Go to download

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

There is a newer version: 3.42.0-eisop4
Show newest version
package org.checkerframework.javacutil;

import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ModifiersTree;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.model.JavacElements;

import org.checkerframework.checker.interning.qual.CompareToMethod;
import org.checkerframework.checker.interning.qual.EqualsMethod;
import org.checkerframework.checker.interning.qual.Interned;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.signature.qual.BinaryName;
import org.checkerframework.checker.signature.qual.CanonicalName;
import org.checkerframework.dataflow.qual.Pure;
import org.checkerframework.dataflow.qual.SideEffectFree;
import org.checkerframework.javacutil.AnnotationBuilder.CheckerFrameworkAnnotationMirror;
import org.plumelib.util.ArrayMap;
import org.plumelib.util.CollectionsPlume;

import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Target;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.StringJoiner;
import java.util.TreeSet;

import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.util.ElementFilter;

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

Note: {@code AnnotationMirror}s are immutable. */ public class AnnotationUtils { // Class cannot be instantiated. private AnnotationUtils() { throw new AssertionError("Class AnnotationUtils cannot be instantiated."); } // ********************************************************************** // Helper methods to handle annotations. mainly workaround // AnnotationMirror.equals undesired property // (I think the undesired property is that it's reference equality.) // ********************************************************************** /** * Returns the fully-qualified name of an annotation as a String. * * @param annotation the annotation whose name to return * @return the fully-qualified name of an annotation as a String */ public static final @CanonicalName String annotationName(AnnotationMirror annotation) { if (annotation instanceof AnnotationBuilder.CheckerFrameworkAnnotationMirror) { return ((AnnotationBuilder.CheckerFrameworkAnnotationMirror) annotation).annotationName; } DeclaredType annoType = annotation.getAnnotationType(); TypeElement elm = (TypeElement) annoType.asElement(); @SuppressWarnings("signature:assignment.type.incompatible") // 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) { DeclaredType annoType = annotation.getAnnotationType(); TypeElement elm = (TypeElement) annoType.asElement(); return ElementUtils.getBinaryName(elm); } /** * Returns true iff both annotations are of the same type and have the same annotation values. * *

This behavior differs from {@code AnnotationMirror.equals(Object)}. The equals method * returns true iff both annotations are the same and annotate the same annotation target (e.g. * field, variable, etc) -- that is, if its arguments are the same annotation instance. * * @param a1 the first AnnotationMirror to compare * @param a2 the second AnnotationMirror to compare * @return true iff a1 and a2 are the same annotation */ @EqualsMethod public static boolean areSame(AnnotationMirror a1, AnnotationMirror a2) { if (a1 == a2) { return true; } if (!areSameByName(a1, a2)) { return false; } return sameElementValues(a1, a2); } /** * Return -1, 0, or 1 depending on whether the name of a1 is less, equal to, or greater than * that of a2 (lexicographically). * * @param a1 the first AnnotationMirror to compare * @param a2 the second AnnotationMirror to compare * @return true iff a1 and a2 have the same annotation name * @see #areSame(AnnotationMirror, AnnotationMirror) */ @EqualsMethod public static int compareByName(AnnotationMirror a1, AnnotationMirror a2) { if (a1 == a2) { return 0; } if (a1 == null || a2 == null) { throw new BugInCF("Unexpected null argument: compareByName(%s, %s)", a1, a2); } if (a1 instanceof CheckerFrameworkAnnotationMirror && a2 instanceof CheckerFrameworkAnnotationMirror) { @Interned @CanonicalName String name1 = ((CheckerFrameworkAnnotationMirror) a1).annotationName; @Interned @CanonicalName String name2 = ((CheckerFrameworkAnnotationMirror) a2).annotationName; if (name1 == name2) { return 0; } else { return name1.compareTo(name2); } } return annotationName(a1).compareTo(annotationName(a2)); } /** * Return true iff a1 and a2 have the same annotation type. * * @param a1 the first AnnotationMirror to compare * @param a2 the second AnnotationMirror to compare * @return true iff a1 and a2 have the same annotation name * @see #areSame(AnnotationMirror, AnnotationMirror) */ @EqualsMethod public static boolean areSameByName(AnnotationMirror a1, AnnotationMirror a2) { return compareByName(a1, a2) == 0; } /** * Checks that the annotation {@code am} has the name {@code aname} (a fully-qualified type * name). Values are ignored. * * @param am the AnnotationMirror whose name to compare * @param aname the string to compare * @return true if aname is the name of am */ public static boolean areSameByName(AnnotationMirror am, String aname) { return aname.equals(annotationName(am)); } /** * Checks that the annotation {@code am} has the name of {@code annoClass}. Values are ignored. * *

This method is not very efficient. It is more efficient to use {@code * AnnotatedTypeFactory#areSameByClass} or {@link #areSameByName}. * * @param am the AnnotationMirror whose class to compare * @param annoClass the class to compare * @return true if annoclass is the class of am * @deprecated use {@code AnnotatedTypeFactory#areSameByClass} or {@link #areSameByName} */ @Deprecated // for use only by the framework public static boolean areSameByClass( AnnotationMirror am, Class annoClass) { String canonicalName = annoClass.getCanonicalName(); assert canonicalName != null : "@AssumeAssertion(nullness): assumption"; return areSameByName(am, canonicalName); } /** * Checks that two collections contain the same annotations. * * @param c1 the first collection to compare * @param c2 the second collection to compare * @return true iff c1 and c2 contain the same annotations, according to {@link * #areSame(AnnotationMirror, AnnotationMirror)} */ public static boolean areSame( Collection c1, Collection c2) { if (c1.size() != c2.size()) { return false; } if (c1.size() == 1) { return areSame(c1.iterator().next(), c2.iterator().next()); } // while loop depends on NavigableSet implementation. AnnotationMirrorSet s1 = new AnnotationMirrorSet(); AnnotationMirrorSet s2 = new AnnotationMirrorSet(); s1.addAll(c1); s2.addAll(c2); Iterator iter1 = s1.iterator(); Iterator iter2 = s2.iterator(); while (iter1.hasNext()) { AnnotationMirror anno1 = iter1.next(); AnnotationMirror anno2 = iter2.next(); if (!areSame(anno1, anno2)) { return false; } } return true; } /** * Checks that the collection contains the annotation. Using Collection.contains does not always * work, because it does not use areSame for comparison. * * @param c a collection of AnnotationMirrors * @param anno the AnnotationMirror to search for in c * @return true iff c contains anno, according to areSame */ public static boolean containsSame( Collection c, AnnotationMirror anno) { return getSame(c, anno) != null; } /** * Returns the AnnotationMirror in {@code c} that is the same annotation as {@code anno}. * * @param c a collection of AnnotationMirrors * @param anno the AnnotationMirror to search for in c * @return AnnotationMirror with the same class as {@code anno} iff c contains anno, according * to areSame; otherwise, {@code null} */ public static @Nullable AnnotationMirror getSame( Collection c, AnnotationMirror anno) { for (AnnotationMirror an : c) { if (AnnotationUtils.areSame(an, anno)) { return an; } } return null; } /** * Checks that the collection contains the annotation. Using Collection.contains does not always * work, because it does not use areSame for comparison. * *

This method is not very efficient. It is more efficient to use {@code * AnnotatedTypeFactory#containsSameByClass} or {@link #containsSameByName}. * * @param c a collection of AnnotationMirrors * @param anno the annotation class to search for in c * @return true iff c contains anno, according to areSameByClass */ public static boolean containsSameByClass( Collection c, Class anno) { return getAnnotationByClass(c, anno) != null; } /** * Returns the AnnotationMirror in {@code c} that has the same class as {@code anno}. * *

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

This method is intended only for use when the class of the annotation is not on the user's * classpath. This is for users of the Dataflow Framework that do not use the rest of the * Checker Framework. Type-checkers can assume that checker-qual.jar is on the classpath and * should use {@link #getElementValue(AnnotationMirror, ExecutableElement, Class)} or {@link * #getElementValue(AnnotationMirror, ExecutableElement, Class, Object)}. * * @param anno the annotation whose element to access * @param elementName the name of the element to access * @param expectedType the type of the element and the return value * @param defaultValue the value to return if the element is not present * @param the class of the type * @return the value of the element with the given name */ public static T getElementValueNotOnClasspath( AnnotationMirror anno, CharSequence elementName, Class expectedType, T defaultValue) { Map valmap = anno.getElementValues(); for (Map.Entry entry : valmap.entrySet()) { ExecutableElement elem = entry.getKey(); if (elem.getSimpleName().contentEquals(elementName)) { AnnotationValue val = entry.getValue(); try { return expectedType.cast(val.getValue()); } catch (ClassCastException e) { throw new BugInCF( "getElementValueNotOnClasspath(%s, %s, %s): val=%s, val.getValue()=%s [%s]", anno, elementName, expectedType, val, val.getValue(), val.getValue().getClass()); } } } return defaultValue; } /** * Returns the values of an annotation's elements, including defaults. The method with the same * name in JavacElements cannot be used directly, because it includes a cast to * Attribute.Compound, which doesn't hold for annotations generated by the Checker Framework. * *

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

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

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

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

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

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

Parameter useDefaults is used to determine whether default values should be used for * annotation values. Finding defaults requires more computation, so should be false when no * defaulting is needed. * *

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

See * org.checkerframework.framework.type.QualifierHierarchy#updateMappingToMutableSet(QualifierHierarchy, * Map, Object, AnnotationMirror). * * @param map the map to update * @param key the key whose value to update * @param newQual the element to add to the given key's value * @param the key type */ public static void updateMappingToImmutableSet( Map map, T key, AnnotationMirrorSet newQual) { AnnotationMirrorSet result = new AnnotationMirrorSet(); // TODO: if T is also an AnnotationMirror, should we use areSame? if (!map.containsKey(key)) { result.addAll(newQual); } else { result.addAll(map.get(key)); result.addAll(newQual); } result.makeUnmodifiable(); map.put(key, result); } /** * Returns the annotations explicitly written on a constructor result. Callers should check that * {@code constructorDeclaration} is in fact a declaration of a constructor. * * @param constructorDeclaration declaration tree of constructor * @return set of annotations explicit on the resulting type of the constructor */ public static AnnotationMirrorSet getExplicitAnnotationsOnConstructorResult( MethodTree constructorDeclaration) { AnnotationMirrorSet annotationSet = new AnnotationMirrorSet(); ModifiersTree modifiersTree = constructorDeclaration.getModifiers(); if (modifiersTree != null) { List annotationTrees = modifiersTree.getAnnotations(); annotationSet.addAll(TreeUtils.annotationsFromTypeAnnotationTrees(annotationTrees)); } return annotationSet; } /** * Returns true if anno is a declaration annotation. In other words, returns true if anno cannot * be written on uses of types. * * @param anno the AnnotationMirror * @return true if anno is a declaration annotation */ public static boolean isDeclarationAnnotation(AnnotationMirror anno) { TypeElement elem = (TypeElement) anno.getAnnotationType().asElement(); Target t = elem.getAnnotation(Target.class); if (t == null) { return true; } for (ElementType elementType : t.value()) { if (elementType == ElementType.TYPE_USE) { return false; } } return true; } /** * Returns true if the given array contains {@link ElementType#TYPE_USE}, false otherwise. * * @param elements an array of {@link ElementType} values * @param cls the annotation class being tested; used for diagnostic messages only * @return true iff the give array contains {@link ElementType#TYPE_USE} * @throws RuntimeException if the array contains both {@link ElementType#TYPE_USE} and * something besides {@link ElementType#TYPE_PARAMETER} */ public static boolean hasTypeQualifierElementTypes(ElementType[] elements, Class cls) { // True if the array contains TYPE_USE boolean hasTypeUse = false; // Non-null if the array contains an element other than TYPE_USE or TYPE_PARAMETER ElementType otherElementType = null; for (ElementType element : elements) { if (element == ElementType.TYPE_USE) { hasTypeUse = true; } else if (element != ElementType.TYPE_PARAMETER) { otherElementType = element; } if (hasTypeUse && otherElementType != null) { throw new BugInCF( "@Target meta-annotation should not contain both TYPE_USE and " + otherElementType + ", for annotation " + cls.getName()); } } return hasTypeUse; } /** * Returns a string representation of the annotation mirrors, using simple (not fully-qualified) * names. * * @param annos annotations to format * @return the string representation, using simple (not fully-qualified) names */ @SideEffectFree public static String toStringSimple(AnnotationMirrorSet annos) { StringJoiner result = new StringJoiner(" "); for (AnnotationMirror am : annos) { result.add(toStringSimple(am)); } return result.toString(); } /** * Returns a string representation of the annotation mirror, using simple (not fully-qualified) * names. * * @param am annotation to format * @return the string representation, using simple (not fully-qualified) names */ @SideEffectFree public static String toStringSimple(AnnotationMirror am) { StringBuilder sb = new StringBuilder(); toStringSimple(am, sb); return sb.toString(); } /** * Appends a string representation of the annotation mirror, using simple (not fully-qualified) * names, to the StringBuilder. * * @param am annotation to format * @param sb StringBuilder to which the string representation of am, using simple (not * fully-qualified) names, is appended */ public static void toStringSimple(AnnotationMirror am, StringBuilder sb) { sb.append("@"); sb.append(am.getAnnotationType().asElement().getSimpleName()); Map args = removeDefaultValues(am.getElementValues()); if (!args.isEmpty()) { sb.append("("); boolean oneValue = false; if (args.size() == 1) { Map.Entry first = args.entrySet().iterator().next(); if (first.getKey().getSimpleName().contentEquals("value")) { formatAnnotationMirrorArg(first.getValue(), sb); oneValue = true; } } if (!oneValue) { boolean notfirst = false; for (Map.Entry arg : args.entrySet()) { if (!"{}".equals(arg.getValue().toString())) { if (notfirst) { sb.append(", "); } notfirst = true; sb.append(arg.getKey().getSimpleName() + "="); formatAnnotationMirrorArg(arg.getValue(), sb); } } } sb.append(")"); } } /** * Returns a new map that only has the values in {@code elementValues} that are not the same as * the default value. * * @param elementValues a mapping of annotation element to annotation value * @return a new map with only the non-default values of {@code elementValues} */ private static Map removeDefaultValues( Map elementValues) { // Most annotations have no elements. Map nonDefaults = new ArrayMap<>(0); elementValues.forEach( (element, value) -> { if (element.getDefaultValue() == null || !Objects.equals( value.getValue(), element.getDefaultValue().getValue())) { nonDefaults.put(element, value); } }); return nonDefaults; } /** * A helper method to print AnnotationValues (annotation arguments), without showing full * package names. * * @param av AnnotationValue to print * @param sb StringBuilder to modify */ private static void formatAnnotationMirrorArg(AnnotationValue av, StringBuilder sb) { Object val = av.getValue(); if (List.class.isAssignableFrom(val.getClass())) { @SuppressWarnings("unchecked") List vallist = (List) val; if (vallist.size() == 1) { formatAnnotationMirrorArg(vallist.get(0), sb); } else { sb.append('{'); boolean notfirst = false; for (AnnotationValue nav : vallist) { if (notfirst) { sb.append(", "); } notfirst = true; formatAnnotationMirrorArg(nav, sb); } sb.append('}'); } } else if (VariableElement.class.isAssignableFrom(val.getClass())) { VariableElement ve = (VariableElement) val; sb.append(ve.getEnclosingElement().getSimpleName() + "." + ve.getSimpleName()); } else { sb.append(av.toString()); } } /** * Converts an AnnotationMirror to a Class. Throws an exception if it is not able to do so. * * @param am an AnnotationMirror * @return the Class corresponding to the given AnnotationMirror */ public static Class annotationMirrorToClass(AnnotationMirror am) { try { return Class.forName(AnnotationUtils.annotationBinaryName(am)); } catch (ClassNotFoundException e) { throw new BugInCF(e); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy