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