blob: 03421f5267ea91a43ed4277052a9ec229444d870 [file] [log] [blame]
package org.checkerframework.javacutil;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
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.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.signature.qual.CanonicalName;
import org.checkerframework.checker.signature.qual.FullyQualifiedName;
import org.checkerframework.dataflow.qual.SideEffectFree;
import org.plumelib.util.StringsPlume;
/**
* Builds an annotation mirror that may have some values.
*
* <p>Constructing an {@link AnnotationMirror} requires:
*
* <ol>
* <li>Constructing the builder with the desired annotation class
* <li>Setting each value individually using {@code setValue} methods
* <li>Calling {@link #build()} to get the annotation
* </ol>
*
* Once an annotation is built, no further modification or calls to build can be made. Otherwise, a
* {@link IllegalStateException} is thrown.
*
* <p>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.
*
* <p>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<ExecutableElement, AnnotationValue> 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<? extends Annotation> 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 canonical name of the annotation to build
*/
public AnnotationBuilder(ProcessingEnvironment env, @FullyQualifiedName 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());
}
/**
* Returns the type element of the annotation that is being built.
*
* @return the type element of the annotation that is being built
*/
public TypeElement getAnnotationElt() {
return annotationElt;
}
/**
* Creates a mapping between element/field names and values.
*
* @param elementName the name of an element/field to initialize
* @param elementValue the initial value for the element/field
* @return a mappnig from the element name to the element value
*/
public static Map<String, AnnotationValue> elementNamesValues(
String elementName, Object elementValue) {
return Collections.singletonMap(elementName, createValue(elementValue));
}
/**
* Creates an {@link AnnotationMirror} that uses default values for elements/fields.
* getElementValues on the result returns default values. If any element does not have a default,
* this method throws an exception.
*
* <p>Most clients should use {@link #fromName}, using a Name created by the compiler. 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
* @throws UserError if the annotation corresponding to the class could not be loaded
*/
public static AnnotationMirror fromClass(Elements elements, Class<? extends Annotation> aClass) {
return fromClass(elements, aClass, Collections.emptyMap());
}
/**
* Creates an {@link AnnotationMirror} given by a particular annotation class and a name-to-value
* mapping for the elements/fields.
*
* <p>For other elements, getElementValues on the result returns default values. If any such
* element does not have a default, this method throws an exception.
*
* <p>Most clients should use {@link #fromName}, using a Name created by the compiler. 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
* @param elementNamesValues the values for the annotation's elements/fields
* @return an {@link AnnotationMirror} of the given type
*/
public static AnnotationMirror fromClass(
Elements elements,
Class<? extends Annotation> aClass,
Map<String, AnnotationValue> elementNamesValues) {
String name = aClass.getCanonicalName();
assert name != null : "@AssumeAssertion(nullness): assumption";
AnnotationMirror res = fromName(elements, name, elementNamesValues);
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 default values. If any element does not have a default,
* this method throws an exception.
*
* <p>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, @FullyQualifiedName CharSequence name) {
return fromName(elements, name, Collections.emptyMap());
}
/**
* Creates an {@link AnnotationMirror} given by a particular fully-qualified name and
* element/field values. If any element is not specified by the {@code elementValues} argument,
* the default value is used. If any such element does not have a default, this method throws an
* exception.
*
* <p>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
* @param elementNamesValues the values for the annotation's elements/fields
* @return an {@link AnnotationMirror} of type {@code} name or null if the annotation couldn't be
* loaded
*/
public static @Nullable AnnotationMirror fromName(
Elements elements,
@FullyQualifiedName CharSequence name,
Map<String, AnnotationValue> elementNamesValues) {
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;
}
List<ExecutableElement> methods = ElementFilter.methodsIn(annoElt.getEnclosedElements());
Map<ExecutableElement, AnnotationValue> elementValues = new LinkedHashMap<>(methods.size());
for (ExecutableElement annoElement : methods) {
AnnotationValue elementValue = elementNamesValues.get(annoElement.getSimpleName().toString());
if (elementValue == null) {
AnnotationValue defaultValue = annoElement.getDefaultValue();
if (defaultValue == null) {
throw new BugInCF(
"AnnotationBuilder.fromName: no value for element %s of %s", annoElement, name);
} else {
elementValue = defaultValue;
}
}
elementValues.put(annoElement, elementValue);
}
AnnotationMirror result = new CheckerFrameworkAnnotationMirror(annoType, elementValues);
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 other the annotation that holds the values to be copied; need not be an annotation of
* the same type of the one being built
* @param ignorableElements the names of elements of {@code other} that can be safely dropped
*/
public void copyElementValuesFromAnnotation(AnnotationMirror other, String... ignorableElements) {
List<String> ignorableElementsList = Arrays.asList(ignorableElements);
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> eltValToCopy :
other.getElementValues().entrySet()) {
Name eltNameToCopy = eltValToCopy.getKey().getSimpleName();
if (ignorableElementsList.contains(eltNameToCopy.toString())) {
continue;
}
elementValues.put(findElement(eltNameToCopy), eltValToCopy.getValue());
}
}
/**
* 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; must be the same type as
* the annotation being built
* @param ignorableElements the elements that can be safely dropped
*/
public void copyElementValuesFromAnnotation(
AnnotationMirror valueHolder, Collection<ExecutableElement> ignorableElements) {
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry :
valueHolder.getElementValues().entrySet()) {
if (ignorableElements.contains(entry.getKey())) {
continue;
}
elementValues.put(entry.getKey(), entry.getValue());
}
}
/**
* 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<String, String> elementNameRenaming) {
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> 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.
*
* @param elementName the element/field name
* @param values the new value for the element/field
* @return this
*/
public AnnotationBuilder setValue(CharSequence elementName, List<? extends Object> values) {
assertNotBuilt();
ExecutableElement var = findElement(elementName);
return setValue(var, values);
}
/**
* Set the element to the given value.
*
* @param element the element
* @param values the new value for the element
* @return this
*/
public AnnotationBuilder setValue(
ExecutableElement element, List<? extends @NonNull Object> values) {
assertNotBuilt();
TypeMirror expectedType = element.getReturnType();
if (expectedType.getKind() != TypeKind.ARRAY) {
throw new BugInCF("value is an array while expected type is not");
}
expectedType = ((ArrayType) expectedType).getComponentType();
List<AnnotationValue> avalues = new ArrayList<>(values.size());
for (Object v : values) {
checkSubtype(expectedType, v);
avalues.add(createValue(v));
}
AnnotationValue aval = createValue(avalues);
elementValues.put(element, 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;
}
/**
* Given a class, return the corresponding TypeMirror.
*
* @param clazz a class
* @return the TypeMirror corresponding to the given class
*/
private TypeMirror typeFromClass(Class<?> clazz) {
return TypesUtils.typeFromClass(clazz, types, elements);
}
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<AnnotationValue> 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<AnnotationValue> 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<ExecutableElement, AnnotationValue> elementValues;
/** The annotation name. */
// default visibility to allow access from within package.
final @Interned @CanonicalName String annotationName;
/**
* Create a CheckerFrameworkAnnotationMirror.
*
* @param annotationType the annotation type
* @param elementValues the element values
*/
@SuppressWarnings("signature:assignment") // needs JDK annotations
CheckerFrameworkAnnotationMirror(
DeclaredType annotationType, Map<ExecutableElement, AnnotationValue> elementValues) {
this.annotationType = annotationType;
final TypeElement elm = (TypeElement) annotationType.asElement();
this.annotationName = elm.getQualifiedName().toString().intern();
this.elementValues = elementValues;
}
@Override
public DeclaredType getAnnotationType() {
return annotationType;
}
@Override
public Map<? extends ExecutableElement, ? extends AnnotationValue> 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<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(')');
}
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<?>) {
List<?> list = (List<?>) value;
toStringVal = "{" + StringsPlume.join(", ", list) + "}";
} 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, 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);
}
}
@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);
}
}
}