package checkers.util; import static javax.lang.model.util.ElementFilter.methodsIn; import checkers.quals.*; import checkers.nullness.quals.*; import com.sun.source.tree.*; import com.sun.source.util.*; import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Target; import java.util.*; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.*; import javax.lang.model.type.*; import javax.lang.model.util.*; /** * A utility class for working with annotations. */ public class AnnotationUtils { private static AnnotationUtils instance; public static AnnotationUtils getInstance(ProcessingEnvironment env) { if (instance == null || instance.env != env) instance = new AnnotationUtils(env); return instance; } private final ProcessingEnvironment env; private final Elements elements; private final Trees trees; private AnnotationUtils(ProcessingEnvironment env) { this.env = env; this.elements = env.getElementUtils(); /*@Nullable*/ Trees trees = Trees.instance(env); assert trees != null; /*nninvariant*/ this.trees = trees; } // ********************************************************************** // Factory Methods to create instances of AnnotationMirror // ********************************************************************** /** Caching for annotation creation. */ private static final Map<String, AnnotationMirror> annotationsFromNames = new HashMap<String, AnnotationMirror>(); /** * Creates an {@link AnnotationMirror} given by a particular * fully-qualified name. * * @param name the name of the annotation to create * @return an {@link AnnotationMirror} of type {@code} name */ public AnnotationMirror fromName(CharSequence name) { return fromName(name.toString()); } /** * Creates an {@link AnnotationMirror} given by a particular * fully-qualified name. * * @param name the name of the annotation to create * @return an {@link AnnotationMirror} of type {@code} name */ public AnnotationMirror fromName(String name) { if (annotationsFromNames.containsKey(name)) return annotationsFromNames.get(name); final DeclaredType annoType = typeFromName(name); if (annoType == null) return null; if (annoType.asElement().getKind() != ElementKind.ANNOTATION_TYPE) throw new AssertionError(annoType + " is not an annotation"); AnnotationMirror result = new AnnotationMirror() { String toString = "@" + annoType; @Override public DeclaredType getAnnotationType() { return annoType; } @Override public Map<? extends ExecutableElement, ? extends AnnotationValue> getElementValues() { return Collections.emptyMap(); } @Override public String toString() { return toString; } }; annotationsFromNames.put(name, result); return result; } /** * Creates an {@link AnnotationMirror} given by a particular annotation * class. * * @param clazz the annotation class * @return an {@link AnnotationMirror} of type given type */ public AnnotationMirror fromClass(Class<? extends Annotation> clazz) { return fromName(clazz.getCanonicalName()); } /** * A utility method that converts a {@link CharSequence} (usually a {@link * String}) into a {@link TypeMirror} named thereby. * * @param name the name of a type * @return the {@link TypeMirror} corresponding to that name */ private DeclaredType typeFromName(CharSequence name) { /*@Nullable*/ TypeElement typeElt = elements.getTypeElement(name); if (typeElt == null) return null; return (DeclaredType)typeElt.asType(); } // ********************************************************************** // Query methods to find default locations for default annotations // ********************************************************************** /** * Finds default annotations starting at the leaf of the given tree path by * inspecting enclosing variable, method, and class declarations for * {@link DefaultQualifier} annotations. * * @param path the tree path from which to start searching * @return a mapping from annotations (as {@link TypeElement}s) to the * {@link DefaultLocation}s for those annotations * * @see #findDefaultLocations(Element) */ public Map<TypeElement, Set<DefaultLocation>> findDefaultLocations(TreePath path) { // Attempt to find a starting search point. If the tree itself has an // element, start there. Otherwise, try the enclosing method and // enclosing class. /*@Nullable*/ Element typeElt = trees.getElement(path); // FIXME: eventually replace this with Scope if (typeElt == null) { /*@Nullable*/ MethodTree method = TreeUtils.enclosingMethod(path); if (method != null) typeElt = InternalUtils.symbol(method); } if (typeElt == null) { /*@Nullable*/ ClassTree cls = TreeUtils.enclosingClass(path); if (cls != null) typeElt = InternalUtils.symbol(cls); } if (typeElt == null) throw new IllegalArgumentException("no element or enclosing element"); return findDefaultLocations(typeElt); } /** * Finds default annotations starting at the given element, inspecting the * element and its enclosing method and class declarations for * {@link DefaultQualifier} annotations. * * @param elt the element from which to start searching * @return a mapping from annotations (as {@link TypeElement}s) to the * {@link DefaultLocation}s for those annotations * * @see #findDefaultLocations(TreePath) */ public Map<TypeElement, Set<DefaultLocation>> findDefaultLocations(Element elt) { /*@Nullable*/ TypeElement defaultElt = elements.getTypeElement("checkers.quals.DefaultQualifier"); assert defaultElt != null : "couldn't get element for @DefaultQualifier"; Map<TypeElement, Set</*@NonNull*/ DefaultLocation>> locations = new HashMap<TypeElement, Set<DefaultLocation>>(); List<? extends AnnotationMirror> annos = elt.getAnnotationMirrors(); for (AnnotationMirror a : annos) { if (!defaultElt.equals(a.getAnnotationType().asElement())) continue; /*@Nullable*/ String name = parseStringValue(a, "value"); /*@Nullable*/ TypeElement aElt = elements.getTypeElement(name); if (aElt == null) throw new RuntimeException("illegal annotation name: " + name); /*@Nullable*/ Set<DefaultLocation> locs = parseEnumConstantArrayValue(a, "types", DefaultLocation.class); if (!locations.containsKey(aElt)) locations.put(aElt, new HashSet<DefaultLocation>()); if (locs == null) continue; /*nnbug*/ locations.get(aElt).addAll(locs); } /*@Nullable*/ Element encl = elt.getEnclosingElement(); if (encl != null) locations.putAll(findDefaultLocations(encl)); return Collections.</*@NonNull*/ TypeElement, /*@NonNull*/ Set<DefaultLocation>>unmodifiableMap(locations); } // ********************************************************************** // Parsers for annotations values // ********************************************************************** /** * Returns the values of an annotation's elements, including defaults. * * @see AnnotationMirror#getElementValues() * @param ad annotation to examine * @return the values of the annotation's elements, including defaults */ public static Map<? extends ExecutableElement, ? extends AnnotationValue> getElementValuesWithDefaults(AnnotationMirror ad) { if (ad == null) return Collections.emptyMap(); Map<ExecutableElement, AnnotationValue> valMap = new HashMap<ExecutableElement, AnnotationValue>(); if (ad.getElementValues() != null) valMap.putAll(ad.getElementValues()); for (ExecutableElement meth : methodsIn(ad.getAnnotationType().asElement().getEnclosedElements())) { AnnotationValue defaultValue = meth.getDefaultValue(); if (defaultValue != null && !valMap.containsKey(meth)) valMap.put(meth, defaultValue); } return valMap; } /** * A generalized method for obtaining annotation values using any parser * that operates on any field. * * @param <R> the type of value to parse * @param parser the annotation value parser * @param ad the annotation for which a field will be parsed * @param fieldName the name of the annotation field for which a value will be returned * @return the value of {@code fieldName} in {@code ad} as determined by * {@code parser}, with type {@code R} */ private static <R> /*@Nullable*/ R parseAnnotationValue(AbstractAnnotationValueParser<R> parser, AnnotationMirror ad, String fieldName) { Map<? extends ExecutableElement, ? extends AnnotationValue> values = getElementValuesWithDefaults(ad); for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : values.entrySet()) { ExecutableElement name = entry.getKey(); AnnotationValue value = entry.getValue(); { Name eltName = name.getSimpleName(); if (!(fieldName.equals(eltName.toString()))) continue; } parser.visit(value); return parser.getValue(); } return null; } /** * @param <R> the enum type * @param ad the annotation for which a value will be parsed * @param field the name of the field to parse * @param enumType the type of the enum * @return the enum constant values of the given field */ public static <R extends Enum<R>> /*@Nullable*/ Set<R> parseEnumConstantArrayValue(AnnotationMirror ad, String field, Class<R> enumType) { return parseAnnotationValue(new EnumConstantArrayValueParser<R>(enumType), ad, field); } /** * @param ad the annotation for which a value will be parsed * @param field the name of the field to parse * @return the String value of the given field */ public static /*@Nullable*/ String parseStringValue(AnnotationMirror ad, String field) { return parseAnnotationValue(new StringValueParser(), ad, field); } /** * @param ad the annotation for which a value will be parsed * @param field the name of the field to parse * @return the String values of the given field */ public static /*@Nullable*/ List<String> parseStringArrayValue(AnnotationMirror ad, String field) { return AnnotationUtils.<List</*@NonNull*/ String>>parseAnnotationValue(new StringArrayValueParser(), ad, field); } // ********************************************************************** // Parsers for annotations values // ********************************************************************** /** * A generic base class for parsers of annotation values. */ private abstract static class AbstractAnnotationValueParser<A> extends SimpleAnnotationValueVisitor6<Void, Boolean> { /** * @return the value of an annotation field */ public abstract A getValue(); @Override public /*@Nullable*/ Void visitArray(List<? extends AnnotationValue> vals, Boolean p) { if (p != null && p) return null; for (AnnotationValue a : vals) visit(a, Boolean.TRUE); return null; } } /** * A utility class for parsing an enum-constant-valued annotation. */ private static class EnumConstantArrayValueParser<R extends Enum<R>> extends AbstractAnnotationValueParser<Set<R>> { private Set<R> values = new HashSet<R>(); private Class<R> enumType; public EnumConstantArrayValueParser(Class<R> enumType) { this.enumType = enumType; } @Override public /*@Nullable*/ Void visitEnumConstant(VariableElement c, Boolean p) { if (p == null || !p) return null; /*@Nullable*/ R r = Enum.<R>valueOf(enumType, (/*@NonNull*/ String)c.getSimpleName().toString()); assert r != null; /*nninvariant*/ values.add(r); return null; } @Override public Set<R> getValue() { return Collections.</*@NonNull*/ R>unmodifiableSet(values); } } /** * A utility class for parsing a String-valued annotation. */ private static class StringValueParser extends AbstractAnnotationValueParser<String> { private /*@Nullable*/ String value = null; @Override public /*@Nullable*/ Void visitString(String s, Boolean p) { value = s; return null; } @Override public String getValue() { assert value != null; /*nninvariant*/ return value; } } /** * A utility class for parsing a String[]-valued annotation. */ private static class StringArrayValueParser extends AbstractAnnotationValueParser<List<String>> { private final List<String> values = new ArrayList<String>(); @Override public /*@Nullable*/ Void visitString(String s, Boolean p) { if (p == null || !p) return null; values.add(s); return null; } @Override public List<String> getValue() { return Collections.</*@NonNull*/ String>unmodifiableList(values); } } // ********************************************************************** // Helper methods to handle annotations. mainly workaround // AnnotationMirror.equals undesired property // ********************************************************************** /** * @return the fully-qualified name of an annotation as a String */ public static final /*@Nullable*/ Name annotationName(/*@Nullable*/ AnnotationMirror annotation) { if (annotation == null) return null; final DeclaredType annoType = annotation.getAnnotationType(); final TypeElement elm = (TypeElement) annoType.asElement(); return elm.getQualifiedName(); } /** * Checks if both annotations are the same. * * Returns true iff both annotations are of the same type and have the * same annotation values. This behavior defers from * {@code AnnotationMirror.equals(Object)}. The equals method returns * true iff both annotations are the same and annotate the same annotation * target (e.g. field, variable, etc). * * @return true iff a1 and a2 are the same annotation */ public static boolean areSame(/*@Nullable*/ AnnotationMirror a1, /*@Nullable*/ AnnotationMirror a2) { if (a1 != null && a2 != null) return annotationName(a1).equals(annotationName(a2)) && a1.toString().equals(a2.toString()); // AnnotationValues don't override equals return a1 == a2; } /** * @see #areSame(AnnotationMirror, AnnotationMirror) * @return true iff a1 and a2 have the same annotation type */ public static boolean areSameIgnoringValues(AnnotationMirror a1, AnnotationMirror a2) { if (a1 != null && a2 != null) return annotationName(a1).equals(annotationName(a2)); return a1 == a2; } /** * Checks that two collections contain the same annotations. * * @return true iff c1 and c2 contain the same annotations */ public static boolean areSame(Collection<AnnotationMirror> c1, Collection<AnnotationMirror> c2) { if (c1.size() != c2.size()) return false; if (c1.size() == 1 && c2.size() == 1) return areSame(c1.iterator().next(), c2.iterator().next()); Set<AnnotationMirror> s1 = createAnnotationSet(); Set<AnnotationMirror> s2 = createAnnotationSet(); s1.addAll(c1); s2.addAll(c2); // depend on the fact that Set is an ordered set. 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; } private static final Comparator<AnnotationMirror> ANNOTATION_ORDERING = new Comparator<AnnotationMirror>() { @Override public int compare(AnnotationMirror a1, AnnotationMirror a2) { if (a1 == null || a2 == null) { if (a1 == a2) return 0; else if (a1 == null) return -1; else if (a2 == null) return 1; } Name n1 = annotationName(a1); Name n2 = annotationName(a2); if (n1.equals(n2)) return 0; int comp = n1.hashCode() - n2.hashCode(); if (comp != 0) return comp; return n1.toString().compareTo(n2.toString()); } }; /** * provide ordering for {@link AnnotationMirror} based on their fully * qualified name. The ordering ignores annotation values when ordering. * * The ordering is meant to be used as {@link TreeSet} or {@link TreeMap} * ordering. A {@link Set} should not contain two annotations that only * differ in values. */ public static Comparator<AnnotationMirror> annotationOrdering() { return ANNOTATION_ORDERING; } /** * Create a map suitable for storing {@link AnnotationMirror} as keys. * * It can store one instance of {@link AnnotationMirror} of a given * declared type, regardless of the annotation element values. * * @param <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<AnnotationMirror, V>(annotationOrdering()); } /** * Constructs a {@link Set} suitable for storing {@link AnnotationMirror}s. * * It stores at most once instance of {@link AnnotationMirror} of a given * type, regardless of the annotation element values. * * @return a new set to store {@link AnnotationMirror} as element */ public static Set<AnnotationMirror> createAnnotationSet() { return new TreeSet<AnnotationMirror>(annotationOrdering()); } /** * Builds an annotation mirror that may have some values. * * Constructing an {@link AnnotationMirror} requires: <br /> * 1. Constructing the builder with the desired annotation class <br /> * 2. Setting each value individually using {@code SetValue} methods<br /> * 3. Calling {@link #build()} to get the annotation build so far * * Once an annotation is built, no further modification or calls to * build can be made. Otherwise, a {@link IllegalStateException}. * * All setter methods throw {@link IllegalArgumentException} if the * specified element is not found, or that the given value is not a * subtype of the expected type. * * TODO: Doesn't type check arrays yet */ public static class AnnotationBuilder { private final ProcessingEnvironment env; private final TypeElement annotationElt; private final DeclaredType annotationType; private final Map<ExecutableElement, AnnotationValue> elementValues; public AnnotationBuilder(ProcessingEnvironment env, Class<? extends Annotation> anno) { this(env, anno.getCanonicalName()); } public AnnotationBuilder(ProcessingEnvironment env, CharSequence name) { this.env = env; this.annotationElt = env.getElementUtils().getTypeElement(name); assert annotationElt.getKind() == ElementKind.ANNOTATION_TYPE; this.annotationType = (DeclaredType)annotationElt.asType(); this.elementValues = new LinkedHashMap<ExecutableElement, AnnotationValue>(); } public AnnotationBuilder(ProcessingEnvironment env, AnnotationMirror annotation) { this.env = env; this.annotationType = annotation.getAnnotationType(); this.annotationElt = (TypeElement) annotationType.asElement(); this.elementValues = new LinkedHashMap<ExecutableElement, AnnotationValue>(); // AnnotationValues are immutable so putAll should suffice this.elementValues.putAll(annotation.getElementValues()); } private boolean wasBuilt = false; private void assertNotBuilt() { if (wasBuilt) throw new IllegalStateException("type was already built"); } public AnnotationMirror build() { assertNotBuilt(); wasBuilt = true; return new AnnotationMirror() { @Override public DeclaredType getAnnotationType() { return annotationType; } @Override public Map<? extends ExecutableElement, ? extends AnnotationValue> getElementValues() { return Collections.unmodifiableMap(elementValues); } @Override public String toString() { StringBuilder buf = new StringBuilder(); buf.append("@"); buf.append(annotationType); int len = elementValues.size(); if (len > 0) { buf.append('('); boolean first = true; for (Map.Entry<ExecutableElement, AnnotationValue> pair : elementValues.entrySet()) { if (!first) buf.append(", "); first = false; String name = pair.getKey().getSimpleName().toString(); if (len > 1 || !name.equals("value")) { buf.append(name); buf.append('='); } buf.append(pair.getValue()); } buf.append(')'); } return buf.toString(); // return "@" + annotationType + "(" + elementValues + ")"; } }; } public AnnotationBuilder setValue(CharSequence elementName, AnnotationMirror value) { setValue(elementName, (Object)value); return this; } public AnnotationBuilder setValue(CharSequence elementName, List<? extends Object> values) { assertNotBuilt(); List<AnnotationValue> value = new ArrayList<AnnotationValue>(); ExecutableElement var = findElement(elementName); TypeMirror expectedType = var.getReturnType(); if (expectedType.getKind() != TypeKind.ARRAY) throw new IllegalArgumentException("value is an array while expected type is not"); expectedType = ((ArrayType)expectedType).getComponentType(); for (Object v : values) { checkSubtype(expectedType, v); value.add(createValue(v)); } AnnotationValue val = createValue(value); elementValues.put(var, val); return this; } public AnnotationBuilder setValue(CharSequence elementName, Object[] values) { return setValue(elementName, Arrays.asList(values)); } public AnnotationBuilder setValue(CharSequence elementName, Boolean value) { return setValue(elementName, (Object)value); } public AnnotationBuilder setValue(CharSequence elementName, Character value) { return setValue(elementName, (Object)value); } public AnnotationBuilder setValue(CharSequence elementName, Double value) { return setValue(elementName, (Object)value); } public AnnotationBuilder setValue(CharSequence elementName, VariableElement value) { return setValue(elementName, (Object)value); } public AnnotationBuilder setValue(CharSequence elementName, Float value) { return setValue(elementName, (Object)value); } public AnnotationBuilder setValue(CharSequence elementName, Integer value) { return setValue(elementName, (Object)value); } public AnnotationBuilder setValue(CharSequence elementName, Long value) { return setValue(elementName, (Object)value); } public AnnotationBuilder setValue(CharSequence elementName, Short value) { return setValue(elementName, (Object)value); } public AnnotationBuilder setValue(CharSequence elementName, String value) { return setValue(elementName, (Object)value); } public AnnotationBuilder setValue(CharSequence elementName, TypeMirror value) { assertNotBuilt(); AnnotationValue val = createValue(value); ExecutableElement var = findElement(elementName); // Check subtyping { if (!TypesUtils.isClass(var.getReturnType())) throw new IllegalArgumentException("expected " + var.getReturnType()); } elementValues.put(var, val); return this; } private TypeMirror typeFromClass(Class<?> clazz) { if (clazz == void.class) { return env.getTypeUtils().getNoType(TypeKind.VOID); } else if (clazz.isPrimitive()) { String primitiveName = clazz.getName().toUpperCase(); TypeKind primitiveKind = TypeKind.valueOf(primitiveName); return env.getTypeUtils().getPrimitiveType(primitiveKind); } else if (clazz.isArray()) { TypeMirror componentType = typeFromClass(clazz.getComponentType()); return env.getTypeUtils().getArrayType(componentType); } else { TypeElement element = env.getElementUtils().getTypeElement(clazz.getCanonicalName()); if (element == null) throw new IllegalArgumentException("Unrecognized class: " + clazz); return element.asType(); } } public AnnotationBuilder setValue(CharSequence elementName, Class<?> value) { return setValue(elementName, typeFromClass(value)); } public AnnotationBuilder setValue(CharSequence elementName, Enum<?> value) { assertNotBuilt(); VariableElement enumElt = findEnumElement(value); ExecutableElement var = findElement(elementName); if (var.getReturnType().getKind() != TypeKind.DECLARED) throw new IllegalArgumentException("exptected a non enum: " + var.getReturnType()); if (!((DeclaredType)var.getReturnType()).asElement().equals(enumElt.getEnclosingElement())) throw new IllegalArgumentException("expected a different type of enum: " + enumElt.getEnclosingElement()); elementValues.put(var, createValue(enumElt)); return this; } private VariableElement findEnumElement(Enum<?> value) { String enumClass = value.getDeclaringClass().getCanonicalName(); TypeElement enumClassElt = env.getElementUtils().getTypeElement(enumClass); assert enumClassElt != null; for (Element enumElt : enumClassElt.getEnclosedElements()) { if (enumElt.getSimpleName().contentEquals(value.name())) return (VariableElement)enumElt; } throw new AssertionError("cannot be here"); } private AnnotationBuilder setValue(CharSequence key, Object value) { assertNotBuilt(); AnnotationValue val = createValue(value); ExecutableElement var = findElement(key); checkSubtype(var.getReturnType(), value); elementValues.put(var, val); return this; } private ExecutableElement findElement(CharSequence key) { for (ExecutableElement elt : ElementFilter.methodsIn(annotationElt.getEnclosedElements())) { if (elt.getSimpleName().contentEquals(key)) { return elt; } } throw new IllegalArgumentException("Couldn't find " + key + " element in " + annotationElt); } private boolean checkSubtype(TypeMirror expected, Object givenValue) { final String newLine = System.getProperty("line.separator"); Types types = env.getTypeUtils(); if (expected.getKind().isPrimitive()) expected = types.boxedClass((PrimitiveType)expected).asType(); if (expected.getKind() == TypeKind.DECLARED && TypesUtils.isClass(expected) && givenValue instanceof TypeMirror) return true; TypeMirror found; boolean isSubtype; if (expected.getKind() == TypeKind.DECLARED && ((DeclaredType)expected).asElement().getKind() == ElementKind.ANNOTATION_TYPE && givenValue instanceof AnnotationMirror) { found = ((AnnotationMirror)givenValue).getAnnotationType(); isSubtype = ((DeclaredType)expected).asElement().equals(((DeclaredType)found).asElement()); } else if (givenValue instanceof AnnotationMirror) { found = ((AnnotationMirror)givenValue).getAnnotationType(); isSubtype = false; } else { found = env.getElementUtils().getTypeElement(givenValue.getClass().getCanonicalName()).asType(); isSubtype = types.isSubtype(types.erasure(found), types.erasure(expected)); } if (!isSubtype) throw new IllegalArgumentException( "given value differs from expected" + newLine + "found: " + found + newLine + "expected: " + expected); return true; } private AnnotationValue createValue(final Object obj) { return new AnnotationValue() { final Object value = obj; @Override public Object getValue() { return value; } @Override public String toString() { if (value instanceof String) return "\"" + value.toString() + "\""; else if (value instanceof Character) return "\'" + value.toString() + "\'"; else if (value instanceof List<?>) { StringBuilder sb = new StringBuilder(); List<?> list = (List<?>)value; sb.append('{'); boolean isFirst = true; for (Object o : list) { if (!isFirst) sb.append(", "); isFirst = false; sb.append(o.toString()); } sb.append('}'); return sb.toString(); } else return value.toString(); } @SuppressWarnings("unchecked") @Override public <R, P> R accept(AnnotationValueVisitor<R, P> v, P p) { if (value instanceof AnnotationMirror) return v.visitAnnotation((AnnotationMirror)value, p); else if (value instanceof List) return v.visitArray((List<? extends AnnotationValue>)value, p); else if (value instanceof Boolean) return v.visitBoolean((Boolean)value, p); else if (value instanceof Character) return v.visitChar((Character)value, p); else if (value instanceof Double) return v.visitDouble((Double)value, p); else if (value instanceof VariableElement) return v.visitEnumConstant((VariableElement)value, p); else if (value instanceof Float) return v.visitFloat((Float)value, p); else if (value instanceof Integer) return v.visitInt((Integer)value, p); else if (value instanceof Long) return v.visitLong((Long)value, p); else if (value instanceof Short) return v.visitShort((Short)value, p); else if (value instanceof String) return v.visitString((String)value, p); else if (value instanceof TypeMirror) return v.visitType((TypeMirror)value, p); else { assert false : " unknown type : " + v.getClass(); return v.visitUnknown(this, p); } } }; } } public static <T> T elementValue(AnnotationMirror anno, CharSequence name, Class<T> expectedType) { for (ExecutableElement elem : anno.getElementValues().keySet()) { if (elem.getSimpleName().contentEquals(name)) { AnnotationValue val = anno.getElementValues().get(elem); return expectedType.cast(val.getValue()); } } throw new IllegalArgumentException("No element with name " + name + " in annotation " + anno); } public static boolean hasInheritiedMeta(AnnotationMirror anno) { return anno.getAnnotationType().asElement().getAnnotation(Inherited.class) != null; } public static boolean hasTarget(AnnotationMirror a, ElementType type) { Target t = (Target) a.getAnnotationType().asElement().getAnnotation(Target.class); if (t != null) { ElementType [] targets = t.value(); for (ElementType tp : targets) { if (tp.equals(type)) { return true; } } }; return false; } public static boolean isTypeAnnotation(AnnotationMirror a) { Target t = (Target) a.getAnnotationType().asElement().getAnnotation(Target.class); if (t != null) { return hasTarget(a, ElementType.TYPE_USE); } /* If no @Target annotation is present, Checker Framework considers the * annotation to be a type qualifier. */ return true; } }