package annotations;
/*>>>
import org.checkerframework.checker.nullness.qual.Nullable;
*/
import annotations.el.AnnotationDef;
import annotations.field.AnnotationFieldType;
import java.util.*;
import java.lang.reflect.*;
/**
* A very simple annotation representation constructed with a map of field names
* to values. See the rules for values on {@link Annotation#getFieldValue};
* furthermore, subannotations must be {@link Annotation}s.
* {@link Annotation}s are immutable.
*
*
* {@link Annotation}s can be constructed directly or through
* {@link AnnotationFactory#saf}. Either way works, but if you construct
* one directly, you must provide a matching {@link AnnotationDef} yourself.
*/
public final class Annotation {
/**
* The annotation definition.
*/
public final AnnotationDef def;
/**
* An unmodifiable copy of the passed map of field values.
*/
public final Map fieldValues;
/** Check the representation, throw assertion failure if it is violated. */
public void checkRep() {
assert fieldValues != null;
assert fieldValues.keySet() != null;
assert def != null;
assert def.fieldTypes != null;
assert def.fieldTypes.keySet() != null;
if (! fieldValues.keySet().equals(def.fieldTypes.keySet())) {
for (String s : fieldValues.keySet()) {
assert def.fieldTypes.containsKey(s)
: String.format("Annotation contains field %s but AnnotationDef does not%n annotation: %s%n def: %s%n", s, this, this.def);
}
// TODO: Faulty assertions, fails when default value is used
// for (String s : def.fieldTypes.keySet()) {
// assert fieldValues.containsKey(s)
// : String.format("AnnotationDef contains field %s but Annotation does not", s);
// }
// assert false : "This can't happen.";
}
for (String fieldname : fieldValues.keySet()) {
AnnotationFieldType aft = def.fieldTypes.get(fieldname);
Object value = fieldValues.get(fieldname);
String valueString;
String classString = value.getClass().toString();
if (value instanceof Object[]) {
Object[] arr = (Object[]) value;
valueString = Arrays.toString(arr);
classString += " {";
for (Object elt : arr) {
classString += " " + elt.getClass();
}
classString += "}";
} else if (value instanceof Collection) {
Collection> coll = (Collection>) value;
valueString = Arrays.toString(coll.toArray());
classString += " {";
for (Object elt : coll) {
classString += " " + elt.getClass();
}
classString += " }";
} else {
valueString = value.toString();
// No need to modify valueString.
}
assert aft.isValidValue(value)
: String.format("Bad field value%n %s (%s)%nfor field%n %s (%s)%nin annotation%n %s",
valueString, classString, aft, aft.getClass(), def);
}
}
// TODO make sure the field values are valid?
/**
* Constructs a {@link Annotation} with the given definition and
* field values. Make sure that the field values obey the rules given on
* {@link Annotation#getFieldValue} and that subannotations are also
* {@link Annotation}s; this constructor does not validate the
* values.
*/
public Annotation(AnnotationDef def,
Map fields) {
this.def = def;
this.fieldValues = Collections.unmodifiableMap(
new LinkedHashMap(fields));
checkRep();
}
/** Use adefs to look up (or insert into it) missing AnnotationDefs. */
public Annotation(java.lang.annotation.Annotation ja, Map adefs) {
Class extends java.lang.annotation.Annotation> jaType = ja.annotationType();
String name = jaType.getName();
if (adefs.containsKey(name)) {
def = adefs.get(name);
} else {
def = AnnotationDef.fromClass(jaType, adefs);
adefs.put(name, def);
}
fieldValues = new LinkedHashMap();
try {
for (String fieldname : def.fieldTypes.keySet()) {
AnnotationFieldType aft = def.fieldTypes.get(fieldname);
Method m = jaType.getDeclaredMethod(fieldname);
Object val = m.invoke(ja);
if (! aft.isValidValue(val)) {
if (val instanceof Class[]) {
Class>[] vala = (Class[]) val;
List> vall = new ArrayList>(vala.length);
for (Class> elt : vala) {
vall.add(elt);
}
val = vall;
} else if (val instanceof Object[]) {
Object[] vala = (Object[]) val;
List