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 "&amp;&amp;" 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