| 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 "&&" 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(); |
| // } |
| // } |