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

org.checkerframework.javacutil.AnnotationBuilder 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.47.0
Show newest version
package org.checkerframework.javacutil;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.AnnotationValueVisitor;
import javax.lang.model.element.Element;
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.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import org.checkerframework.checker.interning.qual.Interned;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.dataflow.qual.SideEffectFree;

/**
 * Builds an annotation mirror that may have some values.
 *
 * 

Constructing an {@link AnnotationMirror} requires: * *

    *
  1. Constructing the builder with the desired annotation class *
  2. Setting each value individually using {@code setValue} methods *
  3. Calling {@link #build()} to get the annotation *
* * Once an annotation is built, no further modification or calls to build can be made. Otherwise, a * {@link IllegalStateException} is thrown. * *

All setter methods throw {@link IllegalArgumentException} if the specified element is not * found, or if the given value is not a subtype of the expected type. * *

TODO: Doesn't type-check arrays yet */ public class AnnotationBuilder { /** The element utilities to use. */ private final Elements elements; /** The type utilities to use. */ private final Types types; /** The type element of the annotation. */ private final TypeElement annotationElt; /** The type of the annotation. */ private final DeclaredType annotationType; /** A mapping from element to AnnotationValue. */ private final Map elementValues; /** * Create a new AnnotationBuilder for the given annotation and environment (with no * elements/fields, but they can be added later). * * @param env the processing environment * @param anno the class of the annotation to build */ @SuppressWarnings("nullness") // getCanonicalName expected to be non-null public AnnotationBuilder(ProcessingEnvironment env, Class anno) { this(env, anno.getCanonicalName()); } /** * Create a new AnnotationBuilder for the given annotation name (with no elements/fields, but * they can be added later). * * @param env the processing environment * @param name the fully-qualified name of the annotation to build */ public AnnotationBuilder(ProcessingEnvironment env, CharSequence name) { this.elements = env.getElementUtils(); this.types = env.getTypeUtils(); this.annotationElt = elements.getTypeElement(name); if (annotationElt == null) { throw new UserError("Could not find annotation: " + name + ". Is it on the classpath?"); } assert annotationElt.getKind() == ElementKind.ANNOTATION_TYPE; this.annotationType = (DeclaredType) annotationElt.asType(); this.elementValues = new LinkedHashMap<>(); } /** * Create a new AnnotationBuilder that copies the given annotation, including its * elements/fields. * * @param env the processing environment * @param annotation the annotation to copy */ public AnnotationBuilder(ProcessingEnvironment env, AnnotationMirror annotation) { this.elements = env.getElementUtils(); this.types = env.getTypeUtils(); this.annotationType = annotation.getAnnotationType(); this.annotationElt = (TypeElement) annotationType.asElement(); this.elementValues = new LinkedHashMap<>(); // AnnotationValues are immutable so putAll should suffice this.elementValues.putAll(annotation.getElementValues()); } /** * Creates an {@link AnnotationMirror} given by a particular annotation class. getElementValues * on the result returns an empty map. This may be in conflict with the annotation's definition, * which might contain elements (annotation fields). Use an AnnotationBuilder for annotations * that contain elements. * *

This method raises an user error if the annotation corresponding to the class could not be * loaded. * *

Clients can use {@link #fromName} and check the result for null manually, if the error * from this method is not desired. This method is provided as a convenience to create an * AnnotationMirror from scratch in a checker's code. * * @param elements the element utilities to use * @param aClass the annotation class * @return an {@link AnnotationMirror} of the given type */ public static AnnotationMirror fromClass( Elements elements, Class aClass) { String name = aClass.getCanonicalName(); assert name != null : "@AssumeAssertion(nullness): assumption"; AnnotationMirror res = fromName(elements, name); if (res == null) { throw new UserError( "AnnotationBuilder: error: fromClass can't load Class %s%n" + "ensure the class is on the compilation classpath", name); } return res; } /** * Creates an {@link AnnotationMirror} given by a particular fully-qualified name. * getElementValues on the result returns an empty map. This may be in conflict with the * annotation's definition, which might contain elements (annotation fields). Use an * AnnotationBuilder for annotations that contain elements. * *

This method returns null if the annotation corresponding to the name could not be loaded. * * @param elements the element utilities to use * @param name the name of the annotation to create * @return an {@link AnnotationMirror} of type {@code} name or null if the annotation couldn't * be loaded */ public static @Nullable AnnotationMirror fromName(Elements elements, CharSequence name) { final TypeElement annoElt = elements.getTypeElement(name); if (annoElt == null) { return null; } if (annoElt.getKind() != ElementKind.ANNOTATION_TYPE) { throw new BugInCF(annoElt + " is not an annotation"); } final DeclaredType annoType = (DeclaredType) annoElt.asType(); if (annoType == null) { return null; } AnnotationMirror result = new CheckerFrameworkAnnotationMirror(annoType, Collections.emptyMap()); return result; } /** Whether or not {@link #build()} has been called. */ private boolean wasBuilt = false; private void assertNotBuilt() { if (wasBuilt) { throw new BugInCF("AnnotationBuilder: error: type was already built"); } } public AnnotationMirror build() { assertNotBuilt(); wasBuilt = true; return new CheckerFrameworkAnnotationMirror(annotationType, elementValues); } /** * Copies every element value from the given annotation. If an element in the given annotation * doesn't exist in the annotation to be built, an error is raised unless the element is * specified in {@code ignorableElements}. * * @param valueHolder the annotation that holds the values to be copied * @param ignorableElements the elements that can be safely dropped */ public void copyElementValuesFromAnnotation( AnnotationMirror valueHolder, String... ignorableElements) { Set ignorableElementsSet = new HashSet<>(Arrays.asList(ignorableElements)); for (Map.Entry eltValToCopy : valueHolder.getElementValues().entrySet()) { Name eltNameToCopy = eltValToCopy.getKey().getSimpleName(); if (ignorableElementsSet.contains(eltNameToCopy.toString())) { continue; } elementValues.put(findElement(eltNameToCopy), eltValToCopy.getValue()); } return; } /** * Copies the specified element values from the given annotation, using the specified renaming * map. Each value in the map must be an element name in the annotation being built. If an * element from the given annotation is not a key in the map, it is ignored. * * @param valueHolder the annotation that holds the values to be copied * @param elementNameRenaming a map from element names in {@code valueHolder} to element names * of the annotation being built */ public void copyRenameElementValuesFromAnnotation( AnnotationMirror valueHolder, Map elementNameRenaming) { for (Map.Entry eltValToCopy : valueHolder.getElementValues().entrySet()) { String sourceName = eltValToCopy.getKey().getSimpleName().toString(); String targetName = elementNameRenaming.get(sourceName); if (targetName == null) { continue; } elementValues.put(findElement(targetName), eltValToCopy.getValue()); } } /** Set the element/field with the given name, to the given value. */ public AnnotationBuilder setValue(CharSequence elementName, AnnotationMirror value) { setValue(elementName, (Object) value); return this; } /** Set the element/field with the given name, to the given value. */ public AnnotationBuilder setValue(CharSequence elementName, List values) { assertNotBuilt(); List avalues = new ArrayList<>(values.size()); ExecutableElement var = findElement(elementName); TypeMirror expectedType = var.getReturnType(); if (expectedType.getKind() != TypeKind.ARRAY) { throw new BugInCF("value is an array while expected type is not"); } expectedType = ((ArrayType) expectedType).getComponentType(); for (Object v : values) { checkSubtype(expectedType, v); avalues.add(createValue(v)); } AnnotationValue aval = createValue(avalues); elementValues.put(var, aval); return this; } /** Set the element/field with the given name, to the given value. */ public AnnotationBuilder setValue(CharSequence elementName, Object[] values) { return setValue(elementName, Arrays.asList(values)); } /** Set the element/field with the given name, to the given value. */ public AnnotationBuilder setValue(CharSequence elementName, Boolean value) { return setValue(elementName, (Object) value); } /** Set the element/field with the given name, to the given value. */ public AnnotationBuilder setValue(CharSequence elementName, Character value) { return setValue(elementName, (Object) value); } /** Set the element/field with the given name, to the given value. */ public AnnotationBuilder setValue(CharSequence elementName, Double value) { return setValue(elementName, (Object) value); } /** Set the element/field with the given name, to the given value. */ public AnnotationBuilder setValue(CharSequence elementName, Float value) { return setValue(elementName, (Object) value); } /** Set the element/field with the given name, to the given value. */ public AnnotationBuilder setValue(CharSequence elementName, Integer value) { return setValue(elementName, (Object) value); } /** Set the element/field with the given name, to the given value. */ public AnnotationBuilder setValue(CharSequence elementName, Long value) { return setValue(elementName, (Object) value); } /** Set the element/field with the given name, to the given value. */ public AnnotationBuilder setValue(CharSequence elementName, Short value) { return setValue(elementName, (Object) value); } /** Set the element/field with the given name, to the given value. */ public AnnotationBuilder setValue(CharSequence elementName, String value) { return setValue(elementName, (Object) value); } /** * Remove the element/field with the given name. Does not err if no such element/field is * present. */ public AnnotationBuilder removeElement(CharSequence elementName) { assertNotBuilt(); ExecutableElement var = findElement(elementName); elementValues.remove(var); return this; } private TypeMirror getErasedOrBoxedType(TypeMirror type) { // See com.sun.tools.javac.code.Attribute.Class.makeClassType() return type.getKind().isPrimitive() ? types.boxedClass((PrimitiveType) type).asType() : types.erasure(type); } public AnnotationBuilder setValue(CharSequence elementName, TypeMirror value) { assertNotBuilt(); value = getErasedOrBoxedType(value); AnnotationValue val = createValue(value); ExecutableElement var = findElement(elementName); // Check subtyping if (!TypesUtils.isClass(var.getReturnType())) { throw new BugInCF("expected " + var.getReturnType()); } elementValues.put(var, val); return this; } private TypeMirror typeFromClass(Class clazz) { if (clazz == void.class) { return types.getNoType(TypeKind.VOID); } else if (clazz.isPrimitive()) { String primitiveName = clazz.getName().toUpperCase(); TypeKind primitiveKind = TypeKind.valueOf(primitiveName); return types.getPrimitiveType(primitiveKind); } else if (clazz.isArray()) { TypeMirror componentType = typeFromClass(clazz.getComponentType()); return types.getArrayType(componentType); } else { String name = clazz.getCanonicalName(); assert name != null : "@AssumeAssertion(nullness): assumption"; TypeElement element = elements.getTypeElement(name); if (element == null) { throw new BugInCF("Unrecognized class: " + clazz); } return element.asType(); } } public AnnotationBuilder setValue(CharSequence elementName, Class value) { TypeMirror type = typeFromClass(value); return setValue(elementName, getErasedOrBoxedType(type)); } public AnnotationBuilder setValue(CharSequence elementName, Enum value) { assertNotBuilt(); VariableElement enumElt = findEnumElement(value); return setValue(elementName, enumElt); } public AnnotationBuilder setValue(CharSequence elementName, VariableElement value) { ExecutableElement var = findElement(elementName); if (var.getReturnType().getKind() != TypeKind.DECLARED) { throw new BugInCF("expected a non enum: " + var.getReturnType()); } if (!((DeclaredType) var.getReturnType()).asElement().equals(value.getEnclosingElement())) { throw new BugInCF("expected a different type of enum: " + value.getEnclosingElement()); } elementValues.put(var, createValue(value)); return this; } // Keep this version synchronized with the VariableElement[] version below public AnnotationBuilder setValue(CharSequence elementName, Enum[] values) { assertNotBuilt(); if (values.length == 0) { setValue(elementName, Collections.emptyList()); return this; } VariableElement enumElt = findEnumElement(values[0]); ExecutableElement var = findElement(elementName); TypeMirror expectedType = var.getReturnType(); if (expectedType.getKind() != TypeKind.ARRAY) { throw new BugInCF("expected a non array: " + var.getReturnType()); } expectedType = ((ArrayType) expectedType).getComponentType(); if (expectedType.getKind() != TypeKind.DECLARED) { throw new BugInCF("expected a non enum component type: " + var.getReturnType()); } if (!((DeclaredType) expectedType).asElement().equals(enumElt.getEnclosingElement())) { throw new BugInCF( "expected a different type of enum: " + enumElt.getEnclosingElement()); } List res = new ArrayList<>(values.length); for (Enum ev : values) { checkSubtype(expectedType, ev); enumElt = findEnumElement(ev); res.add(createValue(enumElt)); } AnnotationValue val = createValue(res); elementValues.put(var, val); return this; } // Keep this version synchronized with the Enum[] version above. // Which one is more useful/general? Unifying adds overhead of creating // another array. public AnnotationBuilder setValue(CharSequence elementName, VariableElement[] values) { assertNotBuilt(); ExecutableElement var = findElement(elementName); TypeMirror expectedType = var.getReturnType(); if (expectedType.getKind() != TypeKind.ARRAY) { throw new BugInCF("expected an array, but found: " + expectedType); } expectedType = ((ArrayType) expectedType).getComponentType(); if (expectedType.getKind() != TypeKind.DECLARED) { throw new BugInCF( "expected a declared component type, but found: " + expectedType + " kind: " + expectedType.getKind()); } if (!types.isSameType((DeclaredType) expectedType, values[0].asType())) { throw new BugInCF( "expected a different declared component type: " + expectedType + " vs. " + values[0]); } List res = new ArrayList<>(values.length); for (VariableElement ev : values) { checkSubtype(expectedType, ev); // Is there a better way to distinguish between enums and // references to constants? if (ev.getConstantValue() != null) { res.add(createValue(ev.getConstantValue())); } else { res.add(createValue(ev)); } } AnnotationValue val = createValue(res); elementValues.put(var, val); return this; } /** Find the VariableElement for the given enum. */ private VariableElement findEnumElement(Enum value) { String enumClass = value.getDeclaringClass().getCanonicalName(); assert enumClass != null : "@AssumeAssertion(nullness): assumption"; TypeElement enumClassElt = elements.getTypeElement(enumClass); assert enumClassElt != null; for (Element enumElt : enumClassElt.getEnclosedElements()) { if (enumElt.getSimpleName().contentEquals(value.name())) { return (VariableElement) enumElt; } } throw new BugInCF("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; } public ExecutableElement findElement(CharSequence key) { for (ExecutableElement elt : ElementFilter.methodsIn(annotationElt.getEnclosedElements())) { if (elt.getSimpleName().contentEquals(key)) { return elt; } } throw new BugInCF("Couldn't find " + key + " element in " + annotationElt); } /** @throws BugInCF if the type of {@code givenValue} is not the same as {@code expected} */ private void checkSubtype(TypeMirror expected, Object givenValue) { if (expected.getKind().isPrimitive()) { expected = types.boxedClass((PrimitiveType) expected).asType(); } if (expected.getKind() == TypeKind.DECLARED && TypesUtils.isClass(expected) && givenValue instanceof TypeMirror) { return; } 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(); // TODO: why is this always failing??? isSubtype = false; } else if (givenValue instanceof VariableElement) { found = ((VariableElement) givenValue).asType(); if (expected.getKind() == TypeKind.DECLARED) { isSubtype = types.isSubtype(types.erasure(found), types.erasure(expected)); } else { isSubtype = false; } } else { String name = givenValue.getClass().getCanonicalName(); assert name != null : "@AssumeAssertion(nullness): assumption"; found = elements.getTypeElement(name).asType(); isSubtype = types.isSubtype(types.erasure(found), types.erasure(expected)); } if (!isSubtype) { // Annotations in stub files sometimes are the same type, but Types#isSubtype fails // anyways. isSubtype = found.toString().equals(expected.toString()); } if (!isSubtype) { throw new BugInCF( "given value differs from expected; " + "found: " + found + "; expected: " + expected); } } /** * Create an AnnotationValue -- a value for an annotation element/field. * * @param obj the value to be stored in an annotation element/field * @return an AnnotationValue for the given Java value */ private static AnnotationValue createValue(final Object obj) { return new CheckerFrameworkAnnotationValue(obj); } /** Implementation of AnnotationMirror used by the Checker Framework. */ /* default visibility to allow access from within package. */ static class CheckerFrameworkAnnotationMirror implements AnnotationMirror { /** The interned toString value. */ private @Nullable @Interned String toStringVal; /** The annotation type. */ private final DeclaredType annotationType; /** The element values. */ private final Map elementValues; /** The annotation name. */ // default visibility to allow access from within package. final @Interned String annotationName; CheckerFrameworkAnnotationMirror( DeclaredType at, Map ev) { this.annotationType = at; final TypeElement elm = (TypeElement) at.asElement(); this.annotationName = elm.getQualifiedName().toString().intern(); this.elementValues = ev; } @Override public DeclaredType getAnnotationType() { return annotationType; } @Override public Map getElementValues() { return Collections.unmodifiableMap(elementValues); } @SideEffectFree @Override public String toString() { if (toStringVal != null) { return toStringVal; } StringBuilder buf = new StringBuilder(); buf.append("@"); buf.append(annotationName); int len = elementValues.size(); if (len > 0) { buf.append('('); boolean first = true; for (Map.Entry 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(')'); } toStringVal = buf.toString().intern(); return toStringVal; // return "@" + annotationType + "(" + elementValues + ")"; } } /** Implementation of AnnotationValue used by the Checker Framework. */ private static class CheckerFrameworkAnnotationValue implements AnnotationValue { /** The value. */ private final Object value; /** The interned value of toString. */ private @Nullable @Interned String toStringVal; /** Create an annotation value. */ CheckerFrameworkAnnotationValue(Object obj) { this.value = obj; } @Override public Object getValue() { return value; } @SideEffectFree @Override public String toString() { if (this.toStringVal != null) { return this.toStringVal; } String toStringVal; if (value instanceof String) { toStringVal = "\"" + value + "\""; } else if (value instanceof Character) { toStringVal = "\'" + value + "\'"; } 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(Objects.toString(o)); } sb.append('}'); toStringVal = sb.toString(); } else if (value instanceof VariableElement) { // for Enums VariableElement var = (VariableElement) value; String encl = var.getEnclosingElement().toString(); if (!encl.isEmpty()) { encl = encl + '.'; } toStringVal = encl + var; } else if (value instanceof TypeMirror && TypesUtils.isClassType((TypeMirror) value)) { toStringVal = value.toString() + ".class"; } else { toStringVal = value.toString(); } this.toStringVal = toStringVal.intern(); return this.toStringVal; } @SuppressWarnings("unchecked") @Override public R accept(AnnotationValueVisitor v, P p) { if (value instanceof AnnotationMirror) { return v.visitAnnotation((AnnotationMirror) value, p); } else if (value instanceof List) { return v.visitArray((List) 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); } } @Override public boolean equals(@Nullable Object obj) { // System.out.printf("Calling CFAV.equals()%n"); if (!(obj instanceof AnnotationValue)) { return false; } AnnotationValue other = (AnnotationValue) obj; return Objects.equals(this.getValue(), other.getValue()); } @Override public int hashCode() { return Objects.hashCode(this.value); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy