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);
}
}
}