package annotations; /*>>> import org.checkerframework.checker.nullness.qual.*; */ import java.util.*; import annotations.field.*; import annotations.el.AnnotationDef; /** * An {@link AnnotationBuilder} builds a single annotation object after the * annotation's fields have been supplied one by one. * *

* It is not possible to specify the type name or the retention policy. * Either the {@link AnnotationBuilder} expects a certain definition (and * may throw exceptions if the fields deviate from it) or it determines the * definition automatically from the supplied fields. * *

* Each {@link AnnotationBuilder} is mutable and single-use; the purpose of an * {@link AnnotationFactory} is to produce as many {@link AnnotationBuilder}s * as needed. */ public class AnnotationBuilder { // Sometimes, we build the AnnotationDef at the very end, and sometimes // we have it before starting. AnnotationDef def; private String typeName; Set tlAnnotationsHere; boolean arrayInProgress = false; boolean active = true; // Generally, don't use this. Use method fieldTypes() instead. private Map fieldTypes = new LinkedHashMap(); Map fieldValues = new LinkedHashMap(); public String typeName() { if (def != null) { return def.name; } else { return typeName; } } public Map fieldTypes() { if (def != null) { return def.fieldTypes; } else { return fieldTypes; } } class SimpleArrayBuilder implements ArrayBuilder { boolean abActive = true; String fieldName; AnnotationFieldType aft; // the type for the elements List arrayElements = new ArrayList(); SimpleArrayBuilder(String fieldName, AnnotationFieldType aft) { assert aft != null; assert fieldName != null; this.fieldName = fieldName; this.aft = aft; } public void appendElement(Object x) { if (!abActive) { throw new IllegalStateException("Array is finished"); } if (!aft.isValidValue(x)) { throw new IllegalArgumentException(String.format("Bad array element value%n %s (%s)%nfor field %s%n %s (%s)", x, x.getClass(), fieldName, aft, aft.getClass())); } arrayElements.add(x); } public void finish() { if (!abActive) { throw new IllegalStateException("Array is finished"); } fieldValues.put(fieldName, Collections .unmodifiableList(arrayElements)); arrayInProgress = false; abActive = false; } } private void checkAddField(String fieldName) { if (!active) { throw new IllegalStateException("Already finished"); } if (arrayInProgress) { throw new IllegalStateException("Array in progress"); } if (fieldValues.containsKey(fieldName)) { throw new IllegalArgumentException("Duplicate field \'" + fieldName + "\' in " + fieldValues); } } /** * Supplies a scalar field of the given name, type, and value for inclusion * in the annotation returned by {@link #finish}. See the rules for values * on {@link Annotation#getFieldValue}. * *

* Each field may be supplied only once. This method may throw an exception * if the {@link AnnotationBuilder} expects a certain definition for * the built annotation and the given field does not exist in that * definition or has the wrong type. */ public void addScalarField(String fieldName, ScalarAFT aft, Object x) { checkAddField(fieldName); if (x instanceof Annotation && !(x instanceof Annotation)) { throw new IllegalArgumentException("All subannotations must be Annotations"); } if (def == null) { fieldTypes.put(fieldName, aft); } fieldValues.put(fieldName, x); } /** * Begins supplying an array field of the given name and type. The elements * of the array must be passed to the returned {@link ArrayBuilder} in * order, and the {@link ArrayBuilder} must be finished before any other * methods on this {@link AnnotationBuilder} are called. * aft.{@link ArrayAFT#elementType elementType} must be known * (not null). * *

* Each field may be supplied only once. This method may throw an exception * if the {@link AnnotationBuilder} expects a certain definition for * the built annotation and the given field does not exist in that * definition or has the wrong type. */ public ArrayBuilder beginArrayField(String fieldName, ArrayAFT aft) { checkAddField(fieldName); if (def == null) { fieldTypes.put(fieldName, aft); } else { aft = (ArrayAFT) fieldTypes().get(fieldName); if (aft == null) { throw new Error(String.format("Definition for %s lacks field %s:%n %s", def.name, fieldName, def)); } assert aft != null; } arrayInProgress = true; assert aft.elementType != null; return new SimpleArrayBuilder(fieldName, aft.elementType); } /** * Supplies an zero-element array field whose element type is unknown. The * field type of this array is represented by an {@link ArrayAFT} with * {@link ArrayAFT#elementType elementType} == null. * *

* This can sometimes happen due to a design flaw in the format of * annotations in class files. An array value does not specify an type * itself; instead, each element carries a type. Thus, a zero-length array * carries no indication of its element type. */ public void addEmptyArrayField(String fieldName) { checkAddField(fieldName); if (def == null) { fieldTypes.put(fieldName, new ArrayAFT(null)); } fieldValues.put(fieldName, Collections.emptyList()); } /** * Returns the completed annotation. This method may throw an exception if * the {@link AnnotationBuilder} expects a certain definition for the * built annotation and one or more fields in that definition were not * supplied. Once this method has been called, no more method calls may be * made on this {@link AnnotationBuilder}. */ public Annotation finish() { if (!active) { throw new IllegalStateException("Already finished: " + this); } if (arrayInProgress) { throw new IllegalStateException("Array in progress: " + this); } active = false; if (def == null) { assert fieldTypes != null; def = new AnnotationDef(typeName, tlAnnotationsHere, fieldTypes); } else { assert typeName == null; assert fieldTypes.isEmpty(); } return new Annotation(def, fieldValues); } AnnotationBuilder(AnnotationDef def) { assert def != null; this.def = def; } AnnotationBuilder(String typeName) { assert typeName != null; this.typeName = typeName; } AnnotationBuilder(String typeName, Set tlAnnotationsHere) { assert typeName != null; this.typeName = typeName; this.tlAnnotationsHere = tlAnnotationsHere; } public String toString() { if (def != null) { return String.format("AnnotationBuilder %s", def); } else { return String.format("(AnnotationBuilder %s : %s)", typeName, tlAnnotationsHere); } } }