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