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 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 vall = new ArrayList(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. * *

* Everywhere in the annotation scene library, field values are to be * represented as follows: * *

    *
  • Primitive value: wrapper object, such as {@link Integer}. *
  • {@link String}: {@link String}. *
  • Class token: name of the type as a {@link String}, using the source * code notation int[] for arrays. *
  • Enumeration constant: name of the constant as a {@link String}. *
  • Subannotation: Annotation object. *
  • 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. *
*/ 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 o if and only if * o is a nonnull {@link Annotation} and this and * o 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 o; 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 "&&" 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 nameKeyer // = new Keyer() { // 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(); // } // }