• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package annotations;
2 
3 /*>>>
4 import org.checkerframework.checker.nullness.qual.*;
5 */
6 
7 import java.util.*;
8 
9 import annotations.field.*;
10 import annotations.el.AnnotationDef;
11 
12 /**
13  * An {@link AnnotationBuilder} builds a single annotation object after the
14  * annotation's fields have been supplied one by one.
15  *
16  * <p>
17  * It is not possible to specify the type name or the retention policy.
18  * Either the {@link AnnotationBuilder} expects a certain definition (and
19  * may throw exceptions if the fields deviate from it) or it determines the
20  * definition automatically from the supplied fields.
21  *
22  * <p>
23  * Each {@link AnnotationBuilder} is mutable and single-use; the purpose of an
24  * {@link AnnotationFactory} is to produce as many {@link AnnotationBuilder}s
25  * as needed.
26  */
27 public class AnnotationBuilder {
28 
29     // Sometimes, we build the AnnotationDef at the very end, and sometimes
30     // we have it before starting.
31     AnnotationDef def;
32 
33     private String typeName;
34     Set<Annotation> tlAnnotationsHere;
35 
36     boolean arrayInProgress = false;
37 
38     boolean active = true;
39 
40     // Generally, don't use this.  Use method fieldTypes() instead.
41     private Map<String, AnnotationFieldType> fieldTypes =
42         new LinkedHashMap<String, AnnotationFieldType>();
43 
44     Map<String, Object> fieldValues =
45         new LinkedHashMap<String, Object>();
46 
typeName()47     public String typeName() {
48         if (def != null) {
49             return def.name;
50         } else {
51             return typeName;
52         }
53     }
54 
fieldTypes()55     public Map<String, AnnotationFieldType> fieldTypes() {
56         if (def != null) {
57             return def.fieldTypes;
58         } else {
59             return fieldTypes;
60         }
61     }
62 
63     class SimpleArrayBuilder implements ArrayBuilder {
64         boolean abActive = true;
65 
66         String fieldName;
67         AnnotationFieldType aft; // the type for the elements
68 
69         List<Object> arrayElements =
70             new ArrayList<Object>();
71 
SimpleArrayBuilder(String fieldName, AnnotationFieldType aft)72         SimpleArrayBuilder(String fieldName, AnnotationFieldType aft) {
73             assert aft != null;
74             assert fieldName != null;
75             this.fieldName = fieldName;
76             this.aft = aft;
77         }
78 
appendElement(Object x)79         public void appendElement(Object x) {
80             if (!abActive) {
81                 throw new IllegalStateException("Array is finished");
82             }
83             if (!aft.isValidValue(x)) {
84                 throw new IllegalArgumentException(String.format("Bad array element value%n  %s (%s)%nfor field %s%n  %s (%s)",
85                                                                  x, x.getClass(), fieldName, aft, aft.getClass()));
86             }
87             arrayElements.add(x);
88         }
89 
finish()90         public void finish() {
91             if (!abActive) {
92                 throw new IllegalStateException("Array is finished");
93             }
94             fieldValues.put(fieldName, Collections
95                             .<Object>unmodifiableList(arrayElements));
96             arrayInProgress = false;
97             abActive = false;
98         }
99     }
100 
checkAddField(String fieldName)101     private void checkAddField(String fieldName) {
102         if (!active) {
103             throw new IllegalStateException("Already finished");
104         }
105         if (arrayInProgress) {
106             throw new IllegalStateException("Array in progress");
107         }
108         if (fieldValues.containsKey(fieldName)) {
109             throw new IllegalArgumentException("Duplicate field \'"
110                                                + fieldName + "\' in " + fieldValues);
111         }
112     }
113 
114     /**
115      * Supplies a scalar field of the given name, type, and value for inclusion
116      * in the annotation returned by {@link #finish}. See the rules for values
117      * on {@link Annotation#getFieldValue}.
118      *
119      * <p>
120      * Each field may be supplied only once. This method may throw an exception
121      * if the {@link AnnotationBuilder} expects a certain definition for
122      * the built annotation and the given field does not exist in that
123      * definition or has the wrong type.
124      */
addScalarField(String fieldName, ScalarAFT aft, Object x)125     public void addScalarField(String fieldName, ScalarAFT aft, Object x) {
126         checkAddField(fieldName);
127         if (x instanceof Annotation && !(x instanceof Annotation)) {
128             throw new IllegalArgumentException("All subannotations must be Annotations");
129         }
130         if (def == null) {
131             fieldTypes.put(fieldName, aft);
132         }
133         fieldValues.put(fieldName, x);
134     }
135 
136     /**
137      * Begins supplying an array field of the given name and type. The elements
138      * of the array must be passed to the returned {@link ArrayBuilder} in
139      * order, and the {@link ArrayBuilder} must be finished before any other
140      * methods on this {@link AnnotationBuilder} are called.
141      * <code>aft.{@link ArrayAFT#elementType elementType}</code> must be known
142      * (not <code>null</code>).
143      *
144      * <p>
145      * Each field may be supplied only once. This method may throw an exception
146      * if the {@link AnnotationBuilder} expects a certain definition for
147      * the built annotation and the given field does not exist in that
148      * definition or has the wrong type.
149      */
beginArrayField(String fieldName, ArrayAFT aft)150     public ArrayBuilder beginArrayField(String fieldName, ArrayAFT aft) {
151         checkAddField(fieldName);
152         if (def == null) {
153             fieldTypes.put(fieldName, aft);
154         } else {
155             aft = (ArrayAFT) fieldTypes().get(fieldName);
156             if (aft == null) {
157                 throw new Error(String.format("Definition for %s lacks field %s:%n  %s",
158                                               def.name, fieldName, def));
159             }
160             assert aft != null;
161         }
162         arrayInProgress = true;
163         assert aft.elementType != null;
164         return new SimpleArrayBuilder(fieldName, aft.elementType);
165     }
166 
167     /**
168      * Supplies an zero-element array field whose element type is unknown.  The
169      * field type of this array is represented by an {@link ArrayAFT} with
170      * {@link ArrayAFT#elementType elementType} == <code>null</code>.
171      *
172      * <p>
173      * This can sometimes happen due to a design flaw in the format of
174      * annotations in class files.  An array value does not specify an type
175      * itself; instead, each element carries a type.  Thus, a zero-length array
176      * carries no indication of its element type.
177      */
addEmptyArrayField(String fieldName)178     public void addEmptyArrayField(String fieldName) {
179         checkAddField(fieldName);
180         if (def == null) {
181             fieldTypes.put(fieldName, new ArrayAFT(null));
182         }
183         fieldValues.put(fieldName, Collections.emptyList());
184     }
185 
186     /**
187      * Returns the completed annotation. This method may throw an exception if
188      * the {@link AnnotationBuilder} expects a certain definition for the
189      * built annotation and one or more fields in that definition were not
190      * supplied.  Once this method has been called, no more method calls may be
191      * made on this {@link AnnotationBuilder}.
192      */
finish()193     public Annotation finish() {
194         if (!active) {
195             throw new IllegalStateException("Already finished: " + this);
196         }
197         if (arrayInProgress) {
198             throw new IllegalStateException("Array in progress: " + this);
199         }
200         active = false;
201         if (def == null) {
202             assert fieldTypes != null;
203             def = new AnnotationDef(typeName, tlAnnotationsHere, fieldTypes);
204         } else {
205             assert typeName == null;
206             assert fieldTypes.isEmpty();
207         }
208         return new Annotation(def, fieldValues);
209     }
210 
AnnotationBuilder(AnnotationDef def)211     AnnotationBuilder(AnnotationDef def) {
212         assert def != null;
213         this.def = def;
214     }
215 
AnnotationBuilder(String typeName)216     AnnotationBuilder(String typeName) {
217         assert typeName != null;
218         this.typeName = typeName;
219     }
220 
AnnotationBuilder(String typeName, Set<Annotation> tlAnnotationsHere)221     AnnotationBuilder(String typeName, Set<Annotation> tlAnnotationsHere) {
222         assert typeName != null;
223         this.typeName = typeName;
224         this.tlAnnotationsHere = tlAnnotationsHere;
225     }
226 
toString()227     public String toString() {
228         if (def != null) {
229             return String.format("AnnotationBuilder %s", def);
230         } else {
231             return String.format("(AnnotationBuilder %s : %s)", typeName, tlAnnotationsHere);
232         }
233     }
234 
235 }
236