blob: acbf54cb980194983b78d810ea75ae96659eb003 [file] [log] [blame]
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.
*
* <p>
* {@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<String, Object> 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<String, ? extends Object> fields) {
this.def = def;
this.fieldValues = Collections.unmodifiableMap(
new LinkedHashMap<String, Object>(fields));
checkRep();
}
/** Use adefs to look up (or insert into it) missing AnnotationDefs. */
public Annotation(java.lang.annotation.Annotation ja, Map<String, AnnotationDef> 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<String,Object>();
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<Class<?>> vall = new ArrayList<Class<?>>(vala.length);
for (Class<?> elt : vala) {
vall.add(elt);
}
val = vall;
} else if (val instanceof Object[]) {
Object[] vala = (Object[]) val;
List<Object> vall = new ArrayList<Object>(vala.length);
for (Object elt : vala) {
vall.add(elt.toString());
}
val = vall;
} else {
val = val.toString();
}
}
assert aft.isValidValue(val)
: String.format("invalid value \"%s\" for field \"%s\" of class \"%s\" and expected type \"%s\"; ja=%s", val, val.getClass(), fieldname, aft, ja);
fieldValues.put(fieldname, val);
}
} catch (NoSuchMethodException e) {
throw new Error(String.format("no such method (annotation field) in %s%n from: %s %s", jaType, ja, adefs), e);
} catch (InvocationTargetException e) {
throw new Error(e);
} catch (IllegalAccessException e) {
throw new Error(e);
}
checkRep();
}
/**
* Returns the value of the field whose name is given.
*
* <p>
* Everywhere in the annotation scene library, field values are to be
* represented as follows:
*
* <ul>
* <li>Primitive value: wrapper object, such as {@link Integer}.
* <li>{@link String}: {@link String}.
* <li>Class token: name of the type as a {@link String}, using the source
* code notation <code>int[]</code> for arrays.
* <li>Enumeration constant: name of the constant as a {@link String}.
* <li>Subannotation: <code>Annotation</code> object.
* <li>Array: {@link List} of elements in the formats defined here. If
* the element type is unknown (see
* {@link AnnotationBuilder#addEmptyArrayField}), the array must have zero
* elements.
* </ul>
*/
public Object getFieldValue(String fieldName) {
return fieldValues.get(fieldName);
}
/**
* Returns the definition of the annotation type to which this annotation
* belongs.
*/
public final AnnotationDef def() {
return def;
}
/**
* This {@link Annotation} equals <code>o</code> if and only if
* <code>o</code> is a nonnull {@link Annotation} and <code>this</code> and
* <code>o</code> have recursively equal definitions and field values,
* even if they were created by different {@link AnnotationFactory}s.
*/
@Override
public final boolean equals(Object o) {
return o instanceof Annotation && equals((Annotation) o);
}
/**
* Returns whether this annotation equals <code>o</code>; a slightly faster
* variant of {@link #equals(Object)} for when the argument is statically
* known to be another nonnull {@link Annotation}. Subclasses may wish to
* override this with a hard-coded "&amp;&amp;" of field comparisons to improve
* performance.
*/
public boolean equals(Annotation o) {
return def.equals(o.def())
&& fieldValues.equals(o.fieldValues);
}
/**
* Returns the hash code of this annotation as defined on
* {@link Annotation#hashCode}. Subclasses may wish to override
* this with a hard-coded XOR/addition of fields to improve performance.
*/
@Override
public int hashCode() {
return def.hashCode() + fieldValues.hashCode();
}
/**
* Returns a string representation of this for
* debugging purposes. For now, this method relies on
* {@link AbstractMap#toString} and the {@link Object#toString toString}
* methods of the field values, so the representation is only a first
* approximation to how the annotation would appear in source code.
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder("@");
sb.append(def.name);
if (!fieldValues.isEmpty()) {
sb.append('(');
sb.append(fieldValues.toString());
sb.append(')');
}
return sb.toString();
}
}
// package annotations;
//
// import org.checkerframework.checker.nullness.qual.Nullable;
//
// import annotations.el.*;
// import annotations.util.coll.Keyer;
//
// /**
// * A top-level annotation containing an ordinary annotation plus a retention
// * policy. These are attached to {@link AElement}s.
// */
// public final class Annotation {
// public static final Keyer<String, Annotation> nameKeyer
// = new Keyer<String, Annotation>() {
// public String getKeyFor(
// Annotation v) {
// return v.tldef.name;
// }
// };
//
// /**
// * The annotation definition.
// */
// public final AnnotationDef tldef;
//
// /**
// * The ordinary annotation, which contains the data and the ordinary
// * definition.
// */
// public final Annotation ann;
//
// /**
// * Wraps the given annotation in a top-level annotation using the given
// * top-level annotation definition, which provides a retention policy.
// */
// public Annotation(AnnotationDef tldef, Annotation ann) {
// if (!ann.def().equals(tldef))
// throw new IllegalArgumentException("Definitions mismatch");
// this.tldef = tldef;
// this.ann = ann;
// }
//
// /**
// * Wraps the given annotation in a top-level annotation with the given
// * retention policy, generating the top-level annotation definition
// * automatically for convenience.
// */
// public Annotation(Annotation ann1,
// RetentionPolicy retention) {
// this(new AnnotationDef(ann1.def(), retention), ann1);
// }
//
// /**
// * {@inheritDoc}
// */
// @Override
// public int hashCode() {
// return tldef.hashCode() + ann.hashCode();
// }
//
// @Override
// public String toString() {
// StringBuilder sb = new StringBuilder();
// sb.append("tla: ");
// sb.append(tldef.retention);
// sb.append(":");
// sb.append(ann.toString());
// return sb.toString();
// }
// }