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