Home | History | Annotate | Download | only in annotations
      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 
     47     public String typeName() {
     48         if (def != null) {
     49             return def.name;
     50         } else {
     51             return typeName;
     52         }
     53     }
     54 
     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 
     72         SimpleArrayBuilder(String fieldName, AnnotationFieldType aft) {
     73             assert aft != null;
     74             assert fieldName != null;
     75             this.fieldName = fieldName;
     76             this.aft = aft;
     77         }
     78 
     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 
     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 
    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      */
    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      */
    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      */
    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      */
    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 
    211     AnnotationBuilder(AnnotationDef def) {
    212         assert def != null;
    213         this.def = def;
    214     }
    215 
    216     AnnotationBuilder(String typeName) {
    217         assert typeName != null;
    218         this.typeName = typeName;
    219     }
    220 
    221     AnnotationBuilder(String typeName, Set<Annotation> tlAnnotationsHere) {
    222         assert typeName != null;
    223         this.typeName = typeName;
    224         this.tlAnnotationsHere = tlAnnotationsHere;
    225     }
    226 
    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