1package annotations; 2 3/*>>> 4import org.checkerframework.checker.nullness.qual.Nullable; 5*/ 6 7import annotations.el.AnnotationDef; 8import annotations.field.AnnotationFieldType; 9 10import java.util.*; 11import java.lang.reflect.*; 12 13 14/** 15 * A very simple annotation representation constructed with a map of field names 16 * to values. See the rules for values on {@link Annotation#getFieldValue}; 17 * furthermore, subannotations must be {@link Annotation}s. 18 * {@link Annotation}s are immutable. 19 * 20 * <p> 21 * {@link Annotation}s can be constructed directly or through 22 * {@link AnnotationFactory#saf}. Either way works, but if you construct 23 * one directly, you must provide a matching {@link AnnotationDef} yourself. 24 */ 25public final class Annotation { 26 27 /** 28 * The annotation definition. 29 */ 30 public final AnnotationDef def; 31 32 /** 33 * An unmodifiable copy of the passed map of field values. 34 */ 35 public final Map<String, Object> fieldValues; 36 37 /** Check the representation, throw assertion failure if it is violated. */ 38 public void checkRep() { 39 assert fieldValues != null; 40 assert fieldValues.keySet() != null; 41 assert def != null; 42 assert def.fieldTypes != null; 43 assert def.fieldTypes.keySet() != null; 44 if (! fieldValues.keySet().equals(def.fieldTypes.keySet())) { 45 for (String s : fieldValues.keySet()) { 46 assert def.fieldTypes.containsKey(s) 47 : String.format("Annotation contains field %s but AnnotationDef does not%n annotation: %s%n def: %s%n", s, this, this.def); 48 } 49 // TODO: Faulty assertions, fails when default value is used 50// for (String s : def.fieldTypes.keySet()) { 51// assert fieldValues.containsKey(s) 52// : String.format("AnnotationDef contains field %s but Annotation does not", s); 53// } 54// assert false : "This can't happen."; 55 } 56 57 for (String fieldname : fieldValues.keySet()) { 58 AnnotationFieldType aft = def.fieldTypes.get(fieldname); 59 Object value = fieldValues.get(fieldname); 60 String valueString; 61 String classString = value.getClass().toString(); 62 if (value instanceof Object[]) { 63 Object[] arr = (Object[]) value; 64 valueString = Arrays.toString(arr); 65 classString += " {"; 66 for (Object elt : arr) { 67 classString += " " + elt.getClass(); 68 } 69 classString += "}"; 70 } else if (value instanceof Collection) { 71 Collection<?> coll = (Collection<?>) value; 72 valueString = Arrays.toString(coll.toArray()); 73 classString += " {"; 74 for (Object elt : coll) { 75 classString += " " + elt.getClass(); 76 } 77 classString += " }"; 78 } else { 79 valueString = value.toString(); 80 // No need to modify valueString. 81 } 82 assert aft.isValidValue(value) 83 : String.format("Bad field value%n %s (%s)%nfor field%n %s (%s)%nin annotation%n %s", 84 valueString, classString, aft, aft.getClass(), def); 85 } 86 } 87 88 // TODO make sure the field values are valid? 89 /** 90 * Constructs a {@link Annotation} with the given definition and 91 * field values. Make sure that the field values obey the rules given on 92 * {@link Annotation#getFieldValue} and that subannotations are also 93 * {@link Annotation}s; this constructor does not validate the 94 * values. 95 */ 96 public Annotation(AnnotationDef def, 97 Map<String, ? extends Object> fields) { 98 this.def = def; 99 this.fieldValues = Collections.unmodifiableMap( 100 new LinkedHashMap<String, Object>(fields)); 101 checkRep(); 102 } 103 104 /** Use adefs to look up (or insert into it) missing AnnotationDefs. */ 105 public Annotation(java.lang.annotation.Annotation ja, Map<String, AnnotationDef> adefs) { 106 Class<? extends java.lang.annotation.Annotation> jaType = ja.annotationType(); 107 String name = jaType.getName(); 108 if (adefs.containsKey(name)) { 109 def = adefs.get(name); 110 } else { 111 def = AnnotationDef.fromClass(jaType, adefs); 112 adefs.put(name, def); 113 } 114 fieldValues = new LinkedHashMap<String,Object>(); 115 try { 116 for (String fieldname : def.fieldTypes.keySet()) { 117 AnnotationFieldType aft = def.fieldTypes.get(fieldname); 118 Method m = jaType.getDeclaredMethod(fieldname); 119 Object val = m.invoke(ja); 120 if (! aft.isValidValue(val)) { 121 if (val instanceof Class[]) { 122 Class<?>[] vala = (Class[]) val; 123 List<Class<?>> vall = new ArrayList<Class<?>>(vala.length); 124 for (Class<?> elt : vala) { 125 vall.add(elt); 126 } 127 val = vall; 128 } else if (val instanceof Object[]) { 129 Object[] vala = (Object[]) val; 130 List<Object> vall = new ArrayList<Object>(vala.length); 131 for (Object elt : vala) { 132 vall.add(elt.toString()); 133 } 134 val = vall; 135 } else { 136 val = val.toString(); 137 } 138 } 139 assert aft.isValidValue(val) 140 : String.format("invalid value \"%s\" for field \"%s\" of class \"%s\" and expected type \"%s\"; ja=%s", val, val.getClass(), fieldname, aft, ja); 141 fieldValues.put(fieldname, val); 142 } 143 } catch (NoSuchMethodException e) { 144 throw new Error(String.format("no such method (annotation field) in %s%n from: %s %s", jaType, ja, adefs), e); 145 } catch (InvocationTargetException e) { 146 throw new Error(e); 147 } catch (IllegalAccessException e) { 148 throw new Error(e); 149 } 150 checkRep(); 151 } 152 153 /** 154 * Returns the value of the field whose name is given. 155 * 156 * <p> 157 * Everywhere in the annotation scene library, field values are to be 158 * represented as follows: 159 * 160 * <ul> 161 * <li>Primitive value: wrapper object, such as {@link Integer}. 162 * <li>{@link String}: {@link String}. 163 * <li>Class token: name of the type as a {@link String}, using the source 164 * code notation <code>int[]</code> for arrays. 165 * <li>Enumeration constant: name of the constant as a {@link String}. 166 * <li>Subannotation: <code>Annotation</code> object. 167 * <li>Array: {@link List} of elements in the formats defined here. If 168 * the element type is unknown (see 169 * {@link AnnotationBuilder#addEmptyArrayField}), the array must have zero 170 * elements. 171 * </ul> 172 */ 173 public Object getFieldValue(String fieldName) { 174 return fieldValues.get(fieldName); 175 } 176 177 /** 178 * Returns the definition of the annotation type to which this annotation 179 * belongs. 180 */ 181 public final AnnotationDef def() { 182 return def; 183 } 184 185 /** 186 * This {@link Annotation} equals <code>o</code> if and only if 187 * <code>o</code> is a nonnull {@link Annotation} and <code>this</code> and 188 * <code>o</code> have recursively equal definitions and field values, 189 * even if they were created by different {@link AnnotationFactory}s. 190 */ 191 @Override 192 public final boolean equals(Object o) { 193 return o instanceof Annotation && equals((Annotation) o); 194 } 195 196 /** 197 * Returns whether this annotation equals <code>o</code>; a slightly faster 198 * variant of {@link #equals(Object)} for when the argument is statically 199 * known to be another nonnull {@link Annotation}. Subclasses may wish to 200 * override this with a hard-coded "&&" of field comparisons to improve 201 * performance. 202 */ 203 public boolean equals(Annotation o) { 204 return def.equals(o.def()) 205 && fieldValues.equals(o.fieldValues); 206 } 207 208 /** 209 * Returns the hash code of this annotation as defined on 210 * {@link Annotation#hashCode}. Subclasses may wish to override 211 * this with a hard-coded XOR/addition of fields to improve performance. 212 */ 213 @Override 214 public int hashCode() { 215 return def.hashCode() + fieldValues.hashCode(); 216 } 217 218 /** 219 * Returns a string representation of this for 220 * debugging purposes. For now, this method relies on 221 * {@link AbstractMap#toString} and the {@link Object#toString toString} 222 * methods of the field values, so the representation is only a first 223 * approximation to how the annotation would appear in source code. 224 */ 225 @Override 226 public String toString() { 227 StringBuilder sb = new StringBuilder("@"); 228 sb.append(def.name); 229 if (!fieldValues.isEmpty()) { 230 sb.append('('); 231 sb.append(fieldValues.toString()); 232 sb.append(')'); 233 } 234 return sb.toString(); 235 } 236 237} 238 239// package annotations; 240// 241// import org.checkerframework.checker.nullness.qual.Nullable; 242// 243// import annotations.el.*; 244// import annotations.util.coll.Keyer; 245// 246// /** 247// * A top-level annotation containing an ordinary annotation plus a retention 248// * policy. These are attached to {@link AElement}s. 249// */ 250// public final class Annotation { 251// public static final Keyer<String, Annotation> nameKeyer 252// = new Keyer<String, Annotation>() { 253// public String getKeyFor( 254// Annotation v) { 255// return v.tldef.name; 256// } 257// }; 258// 259// /** 260// * The annotation definition. 261// */ 262// public final AnnotationDef tldef; 263// 264// /** 265// * The ordinary annotation, which contains the data and the ordinary 266// * definition. 267// */ 268// public final Annotation ann; 269// 270// /** 271// * Wraps the given annotation in a top-level annotation using the given 272// * top-level annotation definition, which provides a retention policy. 273// */ 274// public Annotation(AnnotationDef tldef, Annotation ann) { 275// if (!ann.def().equals(tldef)) 276// throw new IllegalArgumentException("Definitions mismatch"); 277// this.tldef = tldef; 278// this.ann = ann; 279// } 280// 281// /** 282// * Wraps the given annotation in a top-level annotation with the given 283// * retention policy, generating the top-level annotation definition 284// * automatically for convenience. 285// */ 286// public Annotation(Annotation ann1, 287// RetentionPolicy retention) { 288// this(new AnnotationDef(ann1.def(), retention), ann1); 289// } 290// 291// /** 292// * {@inheritDoc} 293// */ 294// @Override 295// public int hashCode() { 296// return tldef.hashCode() + ann.hashCode(); 297// } 298// 299// @Override 300// public String toString() { 301// StringBuilder sb = new StringBuilder(); 302// sb.append("tla: "); 303// sb.append(tldef.retention); 304// sb.append(":"); 305// sb.append(ann.toString()); 306// return sb.toString(); 307// } 308// } 309