aboutsummaryrefslogtreecommitdiff
path: root/scene-lib/src/annotations/io/IndexFileWriter.java
diff options
context:
space:
mode:
Diffstat (limited to 'scene-lib/src/annotations/io/IndexFileWriter.java')
-rw-r--r--scene-lib/src/annotations/io/IndexFileWriter.java574
1 files changed, 574 insertions, 0 deletions
diff --git a/scene-lib/src/annotations/io/IndexFileWriter.java b/scene-lib/src/annotations/io/IndexFileWriter.java
new file mode 100644
index 0000000..0ca0912
--- /dev/null
+++ b/scene-lib/src/annotations/io/IndexFileWriter.java
@@ -0,0 +1,574 @@
+package annotations.io;
+
+/*>>>
+import org.checkerframework.checker.nullness.qual.*;
+*/
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import annotations.Annotation;
+import annotations.el.AClass;
+import annotations.el.AElement;
+import annotations.el.AField;
+import annotations.el.AMethod;
+import annotations.el.AScene;
+import annotations.el.ATypeElement;
+import annotations.el.ATypeElementWithType;
+import annotations.el.AnnotationDef;
+import annotations.el.BoundLocation;
+import annotations.el.DefCollector;
+import annotations.el.DefException;
+import annotations.el.InnerTypeLocation;
+import annotations.el.LocalLocation;
+import annotations.el.RelativeLocation;
+import annotations.el.TypeIndexLocation;
+import annotations.field.AnnotationAFT;
+import annotations.field.AnnotationFieldType;
+import annotations.field.ArrayAFT;
+import annotations.field.BasicAFT;
+import annotations.field.ClassTokenAFT;
+import annotations.util.Strings;
+
+import com.sun.tools.javac.code.TypeAnnotationPosition.TypePathEntry;
+
+/**
+ * IndexFileWriter provides two static methods named <code>write</code>
+ * that write a given {@link AScene} to a given {@link Writer} or filename,
+ * in index file format.
+ */
+public final class IndexFileWriter {
+ final AScene scene;
+
+ private static final String INDENT = " ";
+
+ void printAnnotationDefBody(AnnotationDef d) {
+ for (Map. Entry<String, AnnotationFieldType> f : d.fieldTypes.entrySet()) {
+ String fieldname = f.getKey();
+ AnnotationFieldType fieldType = f.getValue();
+ pw.println(INDENT + fieldType + " " + fieldname);
+ }
+ pw.println();
+ }
+
+ private class OurDefCollector extends DefCollector {
+ OurDefCollector() throws DefException {
+ super(IndexFileWriter.this.scene);
+ }
+
+ @Override
+ protected void visitAnnotationDef(AnnotationDef d) {
+ if (!d.name.contains("+")) {
+ pw.println("package " + annotations.io.IOUtils.packagePart(d.name) + ":");
+ pw.print("annotation @" + annotations.io.IOUtils.basenamePart(d.name) + ":");
+ // TODO: We would only print Retention and Target annotations
+ printAnnotations(requiredMetaannotations(d.tlAnnotationsHere));
+ pw.println();
+ printAnnotationDefBody(d);
+ }
+ }
+
+ private Collection<Annotation> requiredMetaannotations(
+ Collection<Annotation> annos) {
+ Set<Annotation> results = new HashSet<Annotation>();
+ for (Annotation a : annos) {
+ String aName = a.def.name;
+ if (aName.equals(Retention.class.getCanonicalName())
+ || aName.equals(Target.class.getCanonicalName())) {
+ results.add(a);
+ }
+ }
+ return results;
+ }
+ }
+
+ final PrintWriter pw;
+
+ private void printValue(AnnotationFieldType aft, Object o) {
+ if (aft instanceof AnnotationAFT) {
+ printAnnotation((Annotation) o);
+ } else if (aft instanceof ArrayAFT) {
+ ArrayAFT aaft = (ArrayAFT) aft;
+ pw.print('{');
+ if (!(o instanceof List)) {
+ printValue(aaft.elementType, o);
+ } else {
+ List<?> l =
+ (List<?>) o;
+ // watch out--could be an empty array of unknown type
+ // (see AnnotationBuilder#addEmptyArrayField)
+ if (aaft.elementType == null) {
+ if (l.size() != 0) {
+ throw new AssertionError();
+ }
+ } else {
+ boolean first = true;
+ for (Object o2 : l) {
+ if (!first) {
+ pw.print(',');
+ }
+ printValue(aaft.elementType, o2);
+ first = false;
+ }
+ }
+ }
+ pw.print('}');
+ } else if (aft instanceof ClassTokenAFT) {
+ pw.print(aft.format(o));
+ } else if (aft instanceof BasicAFT && o instanceof String) {
+ pw.print(Strings.escape((String) o));
+ } else {
+ pw.print(o.toString());
+ }
+ }
+
+ private void printAnnotation(Annotation a) {
+ pw.print("@" + a.def().name);
+ if (!a.fieldValues.isEmpty()) {
+ pw.print('(');
+ boolean first = true;
+ for (Map. Entry<String, Object> f
+ : a.fieldValues.entrySet()) {
+ if (!first) {
+ pw.print(',');
+ }
+ pw.print(f.getKey() + "=");
+ printValue(a.def().fieldTypes.get(f.getKey()), f.getValue());
+ first = false;
+ }
+ pw.print(')');
+ }
+ }
+
+ private void printAnnotations(Collection<? extends Annotation> annos) {
+ for (Annotation tla : annos) {
+ pw.print(' ');
+ printAnnotation(tla);
+ }
+ }
+
+ private void printAnnotations(AElement e) {
+ printAnnotations(e.tlAnnotationsHere);
+ }
+
+ private void printElement(String indentation,
+ String desc,
+ AElement e) {
+ pw.print(indentation + desc + ":");
+ printAnnotations(e);
+ pw.println();
+ }
+
+ private void printElementAndInnerTypes(String indentation,
+ String desc, AElement e) {
+ if (e.type != null) {
+ printElement(indentation, desc, e.type);
+ if (!e.type.innerTypes.isEmpty()) {
+ printInnerTypes(indentation + INDENT, e.type);
+ }
+ }
+ }
+
+ private void printTypeElementAndInnerTypes(String indentation,
+ String desc,
+ ATypeElement e) {
+ if (e.tlAnnotationsHere.isEmpty() && e.innerTypes.isEmpty() && desc.equals("type")) {
+ return;
+ }
+ printElement(indentation, desc, e);
+ printInnerTypes(indentation + INDENT, e);
+ }
+
+ private void printInnerTypes(String indentation, ATypeElement e) {
+ for (Map. Entry<InnerTypeLocation,
+ ATypeElement> ite : e.innerTypes.entrySet()) {
+ InnerTypeLocation loc = ite.getKey();
+ AElement it = ite.getValue();
+ pw.print(indentation + "inner-type");
+ char sep = ' ';
+ for (TypePathEntry l : loc.location) {
+ pw.print(sep);
+ pw.print(typePathEntryToString(l));
+ sep = ',';
+ }
+ pw.print(':');
+ printAnnotations(it);
+ pw.println();
+ }
+ }
+
+ private void printInnerTypes(String indentation, ATypeElement e,
+ ASTPath path) {
+ for (Map. Entry<InnerTypeLocation,
+ ATypeElement> ite : e.innerTypes.entrySet()) {
+ InnerTypeLocation loc = ite.getKey();
+ AElement it = ite.getValue();
+ pw.print(indentation + "inner-type");
+ char sep = ' ';
+ for (TypePathEntry l : loc.location) {
+ pw.print(sep);
+ pw.print(typePathEntryToString(l));
+ sep = ',';
+ }
+ pw.print(':');
+ printAnnotations(it);
+ pw.println();
+ }
+ }
+
+ /**
+ * Converts the given {@link TypePathEntry} to a string of the form
+ * {@code tag, arg}, where tag and arg are both integers.
+ */
+ private String typePathEntryToString(TypePathEntry t) {
+ return t.tag.tag + ", " + t.arg;
+ }
+
+ private void printNumberedAmbigiousElements(String indentation,
+ String desc,
+ Map<Integer, ? extends AElement> nels) {
+ for (Map. Entry<Integer,
+ ? extends AElement> te : nels.entrySet()) {
+ AElement t = te.getValue();
+ printAmbElementAndInnerTypes(indentation,
+ desc + " #" + te.getKey(), t);
+ }
+ }
+
+ private void printAmbElementAndInnerTypes(String indentation,
+ String desc,
+ AElement e) {
+ printElement(indentation, desc, e);
+ if (e.type.tlAnnotationsHere.isEmpty() && e.type.innerTypes.isEmpty()) {
+ return;
+ }
+ printElement(indentation + INDENT, "type", e.type);
+ for (Map. Entry<InnerTypeLocation, ATypeElement> ite
+ : e.type.innerTypes.entrySet()) {
+ InnerTypeLocation loc = ite.getKey();
+ AElement it = ite.getValue();
+ pw.print(indentation + INDENT + INDENT + "inner-type");
+ boolean first = true;
+ for (TypePathEntry l : loc.location) {
+ if (first) {
+ pw.print(' ');
+ } else {
+ pw.print(',');
+ }
+ pw.print(typePathEntryToString(l));
+ first = false;
+ }
+ pw.print(':');
+ printAnnotations(it);
+ pw.println();
+ }
+ }
+
+ private void printRelativeElements(String indentation,
+ String desc,
+ Map<RelativeLocation, ATypeElement> nels) {
+ for (Map. Entry<RelativeLocation, ATypeElement> te
+ : nels.entrySet()) {
+ ATypeElement t = te.getValue();
+ printTypeElementAndInnerTypes(indentation,
+ desc + " " + te.getKey().getLocationString(), t);
+ }
+ }
+
+ private void printRelativeElements(String indentation,
+ String desc1, String desc2,
+ Map<RelativeLocation, ATypeElement> nels) {
+ RelativeLocation prev = null;
+ for (Map. Entry<RelativeLocation, ATypeElement> te
+ : nels.entrySet()) {
+ ATypeElement t = te.getValue();
+ RelativeLocation loc = te.getKey();
+ boolean isOffset = loc.index < 0;
+ if (prev == null || loc.type_index < 0
+ || (isOffset ? loc.offset != prev.offset
+ : loc.index != prev.index)) {
+ pw.print(indentation + desc1 + " ");
+ pw.print(isOffset ? "#" + loc.offset : "*" + loc.index);
+ pw.print(":");
+ if (loc.type_index <= 0) { printAnnotations(t); }
+ pw.println();
+ printInnerTypes(indentation + INDENT, t);
+ }
+ if (loc.type_index > 0) {
+ printTypeElementAndInnerTypes(indentation + INDENT,
+ desc2 + " " + loc.type_index, t);
+ }
+ prev = loc;
+ }
+ }
+
+ private void printBounds(String indentation,
+ Map<BoundLocation, ATypeElement> bounds) {
+ for (Map. Entry<BoundLocation, ATypeElement> be
+ : bounds.entrySet()) {
+ BoundLocation bl = be.getKey();
+ ATypeElement b = be.getValue();
+ if (bl.boundIndex == -1) {
+ printTypeElementAndInnerTypes(indentation,
+ "typeparam " + bl.paramIndex, b);
+ } else {
+ printTypeElementAndInnerTypes(indentation,
+ "bound " + bl.paramIndex + " &" + bl.boundIndex, b);
+ }
+ }
+ }
+
+ private void printExtImpls(String indentation,
+ Map<TypeIndexLocation, ATypeElement> extImpls) {
+
+ for (Map. Entry<TypeIndexLocation, ATypeElement> ei
+ : extImpls.entrySet()) {
+ TypeIndexLocation idx = ei.getKey();
+ ATypeElement ty = ei.getValue();
+ // reading from a short into an integer does not preserve sign?
+ if (idx.typeIndex == -1 || idx.typeIndex == 65535) {
+ printTypeElementAndInnerTypes(indentation, "extends", ty);
+ } else {
+ printTypeElementAndInnerTypes(indentation, "implements " + idx.typeIndex, ty);
+ }
+ }
+ }
+
+ private void printASTInsertions(String indentation,
+ Map<ASTPath, ATypeElement>
+ insertAnnotations,
+ Map<ASTPath, ATypeElementWithType>
+ insertTypecasts) {
+ for (Map. Entry<ASTPath, ATypeElement> e :
+ insertAnnotations.entrySet()) {
+ ASTPath path = e.getKey();
+ ATypeElement el = e.getValue();
+ pw.print(indentation + "insert-annotation " + path + ":");
+ printAnnotations(el);
+ pw.println();
+ printInnerTypes(INDENT, el, path);
+ }
+ for (Map. Entry<ASTPath,
+ ATypeElementWithType> e :
+ insertTypecasts.entrySet()) {
+ ASTPath path = e.getKey();
+ ATypeElementWithType el = e.getValue();
+ pw.print(indentation + "insert-typecast " + path + ":");
+ printAnnotations(el);
+ pw.print(" ");
+ printType(el.getType());
+ pw.println();
+ printInnerTypes(INDENT, el, path);
+ }
+ }
+
+ private void printType(type.Type type) {
+ switch (type.getKind()) {
+ case ARRAY:
+ type.ArrayType a = (type.ArrayType) type;
+ printType(a.getComponentType());
+ pw.print("[]");
+ break;
+ case BOUNDED:
+ type.BoundedType b = (type.BoundedType) type;
+ printType(b.getName());
+ pw.print(" ");
+ pw.print(b.getBoundKind());
+ pw.print(" ");
+ printType(b.getBound());
+ break;
+ case DECLARED:
+ type.DeclaredType d = (type.DeclaredType) type;
+ pw.print(d.getName());
+ if (!d.isWildcard()) {
+ type.DeclaredType inner = d.getInnerType();
+ Iterator<type.Type> iter = d.getTypeParameters().iterator();
+ // for (String s : d.getAnnotations()) {
+ // pw.print(s + " ");
+ // }
+ if (iter.hasNext()) {
+ pw.print("<");
+ printType(iter.next());
+ while (iter.hasNext()) {
+ pw.print(", ");
+ printType(iter.next());
+ }
+ pw.print(">");
+ }
+ if (inner != null) {
+ pw.print(".");
+ printType(inner);
+ }
+ }
+ break;
+ }
+ }
+
+ private void write() throws DefException {
+ // First the annotation definitions...
+ OurDefCollector odc = new OurDefCollector();
+ odc.visit();
+
+ // Then any package annotations...
+ for (Map. Entry<String, AElement> pe
+ : scene.packages.entrySet()) {
+ AElement elem = pe.getValue();
+ if (elem != null && !elem.tlAnnotationsHere.isEmpty()) {
+ pw.print("package " + pe.getKey() + ":");
+ printAnnotations(elem);
+ pw.println();
+ }
+ }
+
+ // And then the annotated classes
+ final String indent2 = INDENT + INDENT;
+ final String indent3 = INDENT + indent2;
+ for (Map. Entry<String, AClass> ce
+ : scene.classes.entrySet()) {
+ String cname = ce.getKey();
+ AClass c = ce.getValue();
+ String pkg = annotations.io.IOUtils.packagePart(cname);
+ String basename = annotations.io.IOUtils.basenamePart(cname);
+ if ("package-info".equals(basename)) {
+ if (!c.tlAnnotationsHere.isEmpty()) {
+ pw.print("package " + pkg + ":");
+ printAnnotations(c);
+ pw.println();
+ }
+ continue;
+ } else {
+ pw.println("package " + pkg + ":");
+ pw.print("class " + basename + ":");
+ printAnnotations(c);
+ pw.println();
+ }
+
+ printBounds(INDENT, c.bounds);
+ printExtImpls(INDENT, c.extendsImplements);
+ printASTInsertions(INDENT, c.insertAnnotations, c.insertTypecasts);
+
+ for (Map. Entry<String, AField> fe
+ : c.fields.entrySet()) {
+ String fname = fe.getKey();
+ AField f = fe.getValue();
+ pw.println();
+ printElement(INDENT, "field " + fname, f);
+ printTypeElementAndInnerTypes(indent2, "type", f.type);
+ printASTInsertions(indent2,
+ f.insertAnnotations, f.insertTypecasts);
+ }
+ for (Map. Entry<String, AMethod> me
+ : c.methods.entrySet()) {
+ String mkey = me.getKey();
+ AMethod m = me.getValue();
+ pw.println();
+ printElement(INDENT, "method " + mkey, m);
+ printBounds(indent2, m.bounds);
+ printTypeElementAndInnerTypes(indent2, "return", m.returnType);
+ if (!m.receiver.type.tlAnnotationsHere.isEmpty()
+ || !m.receiver.type.innerTypes.isEmpty()) {
+ // Only output the receiver if there is something to
+ // say. This is a bit inconsistent with the return
+ // type, but so be it.
+ printElementAndInnerTypes(indent2, "receiver", m.receiver);
+ }
+ printNumberedAmbigiousElements(indent2,
+ "parameter", m.parameters);
+ for (Map. Entry<LocalLocation,
+ AField> le : m.body.locals.entrySet()) {
+ LocalLocation loc = le.getKey();
+ AElement l = le.getValue();
+ StringBuilder sb = new StringBuilder("local ");
+ sb.append(loc.varName == null
+ ? loc.index
+ + " #" + loc.scopeStart + "+" + loc.scopeLength
+ : loc.varName);
+ printElement(indent2, sb.toString(), l);
+ printTypeElementAndInnerTypes(indent3,
+ "type", l.type);
+ }
+ printRelativeElements(indent2, "typecast",
+ m.body.typecasts);
+ printRelativeElements(indent2, "instanceof",
+ m.body.instanceofs);
+ printRelativeElements(indent2, "new", m.body.news);
+ printRelativeElements(indent2, "reference", "typearg", m.body.refs);
+ printRelativeElements(indent2, "call", "typearg", m.body.calls);
+ for (Map. Entry<RelativeLocation,
+ AMethod> entry : m.body.funs.entrySet()) {
+ AMethod lambda = entry.getValue();
+ RelativeLocation loc = entry.getKey();
+ pw.print("lambda " + loc.getLocationString() + ":\n");
+ printBounds(indent3, lambda.bounds);
+ printTypeElementAndInnerTypes(indent3,
+ "return", lambda.returnType);
+ }
+ // throwsException field is not processed. Why?
+ printASTInsertions(indent2,
+ m.insertAnnotations, m.insertTypecasts);
+ }
+ pw.println();
+ }
+ }
+
+ private IndexFileWriter(AScene scene,
+ Writer out) throws DefException {
+ this.scene = scene;
+ pw = new PrintWriter(out);
+ write();
+ pw.flush();
+ }
+
+ /**
+ * Writes the annotations in <code>scene</code> and their definitions to
+ * <code>out</code> in index file format.
+ *
+ * <p>
+ * An {@link AScene} can contain several annotations of the same type but
+ * different definitions, while an index file can accommodate only a single
+ * definition for each annotation type. This has two consequences:
+ *
+ * <ul>
+ * <li>Before writing anything, this method uses a {@link DefCollector} to
+ * ensure that all definitions of each annotation type are identical
+ * (modulo unknown array types). If not, a {@link DefException} is thrown.
+ * <li>There is one case in which, even if a scene is written successfully,
+ * reading it back in produces a different scene. Consider a scene
+ * containing two annotations of type Foo, each with an array field bar.
+ * In one annotation, bar is empty and of unknown element type (see
+ * {@link annotations.AnnotationBuilder#addEmptyArrayField}); in the other, bar is
+ * of known element type. This method will
+ * {@linkplain AnnotationDef#unify unify} the two definitions of Foo by
+ * writing a single definition with known element type. When the index
+ * file is read into a new scene, the definitions of both annotations
+ * will have known element type, whereas in the original scene, one had
+ * unknown element type.
+ * </ul>
+ */
+ public static void write(
+ AScene scene,
+ Writer out) throws DefException {
+ new IndexFileWriter(scene, out);
+ }
+
+ /**
+ * Writes the annotations in <code>scene</code> and their definitions to
+ * the file <code>filename</code> in index file format; see
+ * {@link #write(AScene, Writer)}.
+ */
+ public static void write(
+ AScene scene,
+ String filename) throws IOException, DefException {
+ write(scene, new FileWriter(filename));
+ }
+}