Home | History | Annotate | Download | only in io
      1 package annotations.io;
      2 
      3 /*>>>
      4 import org.checkerframework.checker.nullness.qual.*;
      5 */
      6 
      7 import java.io.FileWriter;
      8 import java.io.IOException;
      9 import java.io.PrintWriter;
     10 import java.io.Writer;
     11 import java.lang.annotation.Retention;
     12 import java.lang.annotation.Target;
     13 import java.util.Collection;
     14 import java.util.HashSet;
     15 import java.util.Iterator;
     16 import java.util.List;
     17 import java.util.Map;
     18 import java.util.Set;
     19 
     20 import annotations.Annotation;
     21 import annotations.el.AClass;
     22 import annotations.el.AElement;
     23 import annotations.el.AField;
     24 import annotations.el.AMethod;
     25 import annotations.el.AScene;
     26 import annotations.el.ATypeElement;
     27 import annotations.el.ATypeElementWithType;
     28 import annotations.el.AnnotationDef;
     29 import annotations.el.BoundLocation;
     30 import annotations.el.DefCollector;
     31 import annotations.el.DefException;
     32 import annotations.el.InnerTypeLocation;
     33 import annotations.el.LocalLocation;
     34 import annotations.el.RelativeLocation;
     35 import annotations.el.TypeIndexLocation;
     36 import annotations.field.AnnotationAFT;
     37 import annotations.field.AnnotationFieldType;
     38 import annotations.field.ArrayAFT;
     39 import annotations.field.BasicAFT;
     40 import annotations.field.ClassTokenAFT;
     41 import annotations.util.Strings;
     42 
     43 import com.sun.tools.javac.code.TypeAnnotationPosition.TypePathEntry;
     44 
     45 /**
     46  * IndexFileWriter provides two static methods named <code>write</code>
     47  * that write a given {@link AScene} to a given {@link Writer} or filename,
     48  * in index file format.
     49  */
     50 public final class IndexFileWriter {
     51     final AScene scene;
     52 
     53     private static final String INDENT = "    ";
     54 
     55     void printAnnotationDefBody(AnnotationDef d) {
     56         for (Map. Entry<String, AnnotationFieldType> f : d.fieldTypes.entrySet()) {
     57             String fieldname = f.getKey();
     58             AnnotationFieldType fieldType = f.getValue();
     59             pw.println(INDENT + fieldType + " " + fieldname);
     60         }
     61         pw.println();
     62     }
     63 
     64     private class OurDefCollector extends DefCollector {
     65         OurDefCollector() throws DefException {
     66             super(IndexFileWriter.this.scene);
     67         }
     68 
     69         @Override
     70         protected void visitAnnotationDef(AnnotationDef d) {
     71           if (!d.name.contains("+")) {
     72             pw.println("package " + annotations.io.IOUtils.packagePart(d.name) + ":");
     73             pw.print("annotation @" + annotations.io.IOUtils.basenamePart(d.name) + ":");
     74             // TODO: We would only print Retention and Target annotations
     75             printAnnotations(requiredMetaannotations(d.tlAnnotationsHere));
     76             pw.println();
     77             printAnnotationDefBody(d);
     78           }
     79         }
     80 
     81         private Collection<Annotation> requiredMetaannotations(
     82                 Collection<Annotation> annos) {
     83             Set<Annotation> results = new HashSet<Annotation>();
     84             for (Annotation a : annos) {
     85                 String aName = a.def.name;
     86                 if (aName.equals(Retention.class.getCanonicalName())
     87                     || aName.equals(Target.class.getCanonicalName())) {
     88                     results.add(a);
     89                 }
     90             }
     91             return results;
     92         }
     93     }
     94 
     95     final PrintWriter pw;
     96 
     97     private void printValue(AnnotationFieldType aft, Object o) {
     98         if (aft instanceof AnnotationAFT) {
     99             printAnnotation((Annotation) o);
    100         } else if (aft instanceof ArrayAFT) {
    101             ArrayAFT aaft = (ArrayAFT) aft;
    102             pw.print('{');
    103             if (!(o instanceof List)) {
    104                 printValue(aaft.elementType, o);
    105             } else {
    106                 List<?> l =
    107                     (List<?>) o;
    108                 // watch out--could be an empty array of unknown type
    109                 // (see AnnotationBuilder#addEmptyArrayField)
    110                 if (aaft.elementType == null) {
    111                     if (l.size() != 0) {
    112                         throw new AssertionError();
    113                     }
    114                 } else {
    115                     boolean first = true;
    116                     for (Object o2 : l) {
    117                         if (!first) {
    118                             pw.print(',');
    119                         }
    120                         printValue(aaft.elementType, o2);
    121                         first = false;
    122                     }
    123                 }
    124             }
    125             pw.print('}');
    126         } else if (aft instanceof ClassTokenAFT) {
    127             pw.print(aft.format(o));
    128         } else if (aft instanceof BasicAFT && o instanceof String) {
    129             pw.print(Strings.escape((String) o));
    130         } else {
    131             pw.print(o.toString());
    132         }
    133     }
    134 
    135     private void printAnnotation(Annotation a) {
    136         pw.print("@" + a.def().name);
    137         if (!a.fieldValues.isEmpty()) {
    138             pw.print('(');
    139             boolean first = true;
    140             for (Map. Entry<String, Object> f
    141                     : a.fieldValues.entrySet()) {
    142                 if (!first) {
    143                     pw.print(',');
    144                 }
    145                 pw.print(f.getKey() + "=");
    146                 printValue(a.def().fieldTypes.get(f.getKey()), f.getValue());
    147                 first = false;
    148             }
    149             pw.print(')');
    150         }
    151     }
    152 
    153     private void printAnnotations(Collection<? extends Annotation> annos) {
    154         for (Annotation tla : annos) {
    155             pw.print(' ');
    156             printAnnotation(tla);
    157         }
    158     }
    159 
    160     private void printAnnotations(AElement e) {
    161         printAnnotations(e.tlAnnotationsHere);
    162     }
    163 
    164     private void printElement(String indentation,
    165             String desc,
    166             AElement e) {
    167         pw.print(indentation + desc + ":");
    168         printAnnotations(e);
    169         pw.println();
    170     }
    171 
    172     private void printElementAndInnerTypes(String indentation,
    173             String desc, AElement e) {
    174         if (e.type != null) {
    175             printElement(indentation, desc, e.type);
    176             if (!e.type.innerTypes.isEmpty()) {
    177                 printInnerTypes(indentation + INDENT, e.type);
    178             }
    179         }
    180     }
    181 
    182     private void printTypeElementAndInnerTypes(String indentation,
    183             String desc,
    184             ATypeElement e) {
    185         if (e.tlAnnotationsHere.isEmpty() && e.innerTypes.isEmpty() && desc.equals("type")) {
    186             return;
    187         }
    188         printElement(indentation, desc, e);
    189         printInnerTypes(indentation + INDENT, e);
    190     }
    191 
    192     private void printInnerTypes(String indentation, ATypeElement e) {
    193       for (Map. Entry<InnerTypeLocation,
    194               ATypeElement> ite : e.innerTypes.entrySet()) {
    195           InnerTypeLocation loc = ite.getKey();
    196           AElement it = ite.getValue();
    197           pw.print(indentation + "inner-type");
    198           char sep = ' ';
    199           for (TypePathEntry l : loc.location) {
    200               pw.print(sep);
    201               pw.print(typePathEntryToString(l));
    202               sep = ',';
    203           }
    204           pw.print(':');
    205           printAnnotations(it);
    206           pw.println();
    207       }
    208     }
    209 
    210     private void printInnerTypes(String indentation, ATypeElement e,
    211             ASTPath path) {
    212         for (Map. Entry<InnerTypeLocation,
    213                 ATypeElement> ite : e.innerTypes.entrySet()) {
    214             InnerTypeLocation loc = ite.getKey();
    215             AElement it = ite.getValue();
    216             pw.print(indentation + "inner-type");
    217             char sep = ' ';
    218             for (TypePathEntry l : loc.location) {
    219                 pw.print(sep);
    220                 pw.print(typePathEntryToString(l));
    221                 sep = ',';
    222             }
    223             pw.print(':');
    224             printAnnotations(it);
    225             pw.println();
    226         }
    227     }
    228 
    229     /**
    230      * Converts the given {@link TypePathEntry} to a string of the form
    231      * {@code tag, arg}, where tag and arg are both integers.
    232      */
    233     private String typePathEntryToString(TypePathEntry t) {
    234         return t.tag.tag + ", " + t.arg;
    235     }
    236 
    237     private void printNumberedAmbigiousElements(String indentation,
    238             String desc,
    239             Map<Integer, ? extends AElement> nels) {
    240         for (Map. Entry<Integer,
    241                 ? extends AElement> te : nels.entrySet()) {
    242             AElement t = te.getValue();
    243             printAmbElementAndInnerTypes(indentation,
    244                     desc + " #" + te.getKey(), t);
    245         }
    246     }
    247 
    248     private void printAmbElementAndInnerTypes(String indentation,
    249             String desc,
    250             AElement e) {
    251         printElement(indentation, desc, e);
    252         if (e.type.tlAnnotationsHere.isEmpty() && e.type.innerTypes.isEmpty()) {
    253             return;
    254         }
    255         printElement(indentation + INDENT, "type", e.type);
    256         for (Map. Entry<InnerTypeLocation, ATypeElement> ite
    257                 : e.type.innerTypes.entrySet()) {
    258             InnerTypeLocation loc = ite.getKey();
    259             AElement it = ite.getValue();
    260             pw.print(indentation + INDENT + INDENT + "inner-type");
    261             boolean first = true;
    262             for (TypePathEntry l : loc.location) {
    263                 if (first) {
    264                     pw.print(' ');
    265                 } else {
    266                     pw.print(',');
    267                 }
    268                 pw.print(typePathEntryToString(l));
    269                 first = false;
    270             }
    271             pw.print(':');
    272             printAnnotations(it);
    273             pw.println();
    274         }
    275     }
    276 
    277     private void printRelativeElements(String indentation,
    278             String desc,
    279             Map<RelativeLocation, ATypeElement> nels) {
    280         for (Map. Entry<RelativeLocation, ATypeElement> te
    281                 : nels.entrySet()) {
    282             ATypeElement t = te.getValue();
    283             printTypeElementAndInnerTypes(indentation,
    284                     desc + " " + te.getKey().getLocationString(), t);
    285         }
    286     }
    287 
    288     private void printRelativeElements(String indentation,
    289             String desc1, String desc2,
    290             Map<RelativeLocation, ATypeElement> nels) {
    291         RelativeLocation prev = null;
    292         for (Map. Entry<RelativeLocation, ATypeElement> te
    293                 : nels.entrySet()) {
    294             ATypeElement t = te.getValue();
    295             RelativeLocation loc = te.getKey();
    296             boolean isOffset = loc.index < 0;
    297             if (prev == null || loc.type_index < 0
    298                     || (isOffset ? loc.offset != prev.offset
    299                             : loc.index != prev.index)) {
    300                 pw.print(indentation + desc1 + " ");
    301                 pw.print(isOffset ? "#" + loc.offset : "*" + loc.index);
    302                 pw.print(":");
    303                 if (loc.type_index <= 0) { printAnnotations(t); }
    304                 pw.println();
    305                 printInnerTypes(indentation + INDENT, t);
    306             }
    307             if (loc.type_index > 0) {
    308                 printTypeElementAndInnerTypes(indentation + INDENT,
    309                         desc2 + " " + loc.type_index, t);
    310             }
    311             prev = loc;
    312         }
    313     }
    314 
    315     private void printBounds(String indentation,
    316             Map<BoundLocation, ATypeElement> bounds) {
    317         for (Map. Entry<BoundLocation, ATypeElement> be
    318                 : bounds.entrySet()) {
    319             BoundLocation bl = be.getKey();
    320             ATypeElement b = be.getValue();
    321             if (bl.boundIndex == -1) {
    322                 printTypeElementAndInnerTypes(indentation,
    323                                               "typeparam " + bl.paramIndex, b);
    324             } else {
    325                 printTypeElementAndInnerTypes(indentation,
    326                            "bound " + bl.paramIndex + " &" + bl.boundIndex, b);
    327             }
    328         }
    329     }
    330 
    331     private void printExtImpls(String indentation,
    332             Map<TypeIndexLocation, ATypeElement> extImpls) {
    333 
    334         for (Map. Entry<TypeIndexLocation, ATypeElement> ei
    335                 : extImpls.entrySet()) {
    336             TypeIndexLocation idx = ei.getKey();
    337             ATypeElement ty = ei.getValue();
    338             // reading from a short into an integer does not preserve sign?
    339             if (idx.typeIndex == -1 || idx.typeIndex == 65535) {
    340                 printTypeElementAndInnerTypes(indentation, "extends", ty);
    341             } else {
    342                 printTypeElementAndInnerTypes(indentation, "implements " + idx.typeIndex, ty);
    343             }
    344         }
    345     }
    346 
    347     private void printASTInsertions(String indentation,
    348             Map<ASTPath, ATypeElement>
    349             insertAnnotations,
    350             Map<ASTPath, ATypeElementWithType>
    351             insertTypecasts) {
    352         for (Map. Entry<ASTPath, ATypeElement> e :
    353                 insertAnnotations.entrySet()) {
    354             ASTPath path = e.getKey();
    355             ATypeElement el = e.getValue();
    356             pw.print(indentation + "insert-annotation " + path + ":");
    357             printAnnotations(el);
    358             pw.println();
    359             printInnerTypes(INDENT, el, path);
    360         }
    361         for (Map. Entry<ASTPath,
    362                     ATypeElementWithType> e :
    363                 insertTypecasts.entrySet()) {
    364             ASTPath path = e.getKey();
    365             ATypeElementWithType el = e.getValue();
    366             pw.print(indentation + "insert-typecast " + path + ":");
    367             printAnnotations(el);
    368             pw.print(" ");
    369             printType(el.getType());
    370             pw.println();
    371             printInnerTypes(INDENT, el, path);
    372         }
    373     }
    374 
    375     private void printType(type.Type type) {
    376         switch (type.getKind()) {
    377         case ARRAY:
    378             type.ArrayType a = (type.ArrayType) type;
    379             printType(a.getComponentType());
    380             pw.print("[]");
    381             break;
    382         case BOUNDED:
    383             type.BoundedType b = (type.BoundedType) type;
    384             printType(b.getName());
    385             pw.print(" ");
    386             pw.print(b.getBoundKind());
    387             pw.print(" ");
    388             printType(b.getBound());
    389             break;
    390         case DECLARED:
    391             type.DeclaredType d = (type.DeclaredType) type;
    392             pw.print(d.getName());
    393             if (!d.isWildcard()) {
    394                 type.DeclaredType inner = d.getInnerType();
    395                 Iterator<type.Type> iter = d.getTypeParameters().iterator();
    396                 // for (String s : d.getAnnotations()) {
    397                 //    pw.print(s + " ");
    398                 // }
    399                 if (iter.hasNext()) {
    400                     pw.print("<");
    401                     printType(iter.next());
    402                     while (iter.hasNext()) {
    403                         pw.print(", ");
    404                         printType(iter.next());
    405                     }
    406                     pw.print(">");
    407                 }
    408                 if (inner != null) {
    409                     pw.print(".");
    410                     printType(inner);
    411                 }
    412             }
    413             break;
    414         }
    415     }
    416 
    417     private void write() throws DefException {
    418         // First the annotation definitions...
    419         OurDefCollector odc = new OurDefCollector();
    420         odc.visit();
    421 
    422         // Then any package annotations...
    423         for (Map. Entry<String, AElement> pe
    424                 : scene.packages.entrySet()) {
    425             AElement elem = pe.getValue();
    426             if (elem != null && !elem.tlAnnotationsHere.isEmpty()) {
    427                 pw.print("package " + pe.getKey() + ":");
    428                 printAnnotations(elem);
    429                 pw.println();
    430             }
    431         }
    432 
    433         // And then the annotated classes
    434         final String indent2 = INDENT + INDENT;
    435         final String indent3 = INDENT + indent2;
    436         for (Map. Entry<String, AClass> ce
    437                 : scene.classes.entrySet()) {
    438             String cname = ce.getKey();
    439             AClass c = ce.getValue();
    440             String pkg = annotations.io.IOUtils.packagePart(cname);
    441             String basename = annotations.io.IOUtils.basenamePart(cname);
    442             if ("package-info".equals(basename)) {
    443               if (!c.tlAnnotationsHere.isEmpty()) {
    444                 pw.print("package " + pkg + ":");
    445                 printAnnotations(c);
    446                 pw.println();
    447               }
    448               continue;
    449             } else {
    450               pw.println("package " + pkg + ":");
    451               pw.print("class " + basename + ":");
    452               printAnnotations(c);
    453               pw.println();
    454             }
    455 
    456             printBounds(INDENT, c.bounds);
    457             printExtImpls(INDENT, c.extendsImplements);
    458             printASTInsertions(INDENT, c.insertAnnotations, c.insertTypecasts);
    459 
    460             for (Map. Entry<String, AField> fe
    461                     : c.fields.entrySet()) {
    462                 String fname = fe.getKey();
    463                 AField f = fe.getValue();
    464                 pw.println();
    465                 printElement(INDENT, "field " + fname, f);
    466                 printTypeElementAndInnerTypes(indent2, "type", f.type);
    467                 printASTInsertions(indent2,
    468                         f.insertAnnotations, f.insertTypecasts);
    469             }
    470             for (Map. Entry<String, AMethod> me
    471                     : c.methods.entrySet()) {
    472                 String mkey = me.getKey();
    473                 AMethod m = me.getValue();
    474                 pw.println();
    475                 printElement(INDENT, "method " + mkey, m);
    476                 printBounds(indent2, m.bounds);
    477                 printTypeElementAndInnerTypes(indent2, "return", m.returnType);
    478                 if (!m.receiver.type.tlAnnotationsHere.isEmpty()
    479                         || !m.receiver.type.innerTypes.isEmpty()) {
    480                     // Only output the receiver if there is something to
    481                     // say.  This is a bit inconsistent with the return
    482                     // type, but so be it.
    483                     printElementAndInnerTypes(indent2, "receiver", m.receiver);
    484                 }
    485                 printNumberedAmbigiousElements(indent2,
    486                         "parameter", m.parameters);
    487                 for (Map. Entry<LocalLocation,
    488                         AField> le : m.body.locals.entrySet()) {
    489                     LocalLocation loc = le.getKey();
    490                     AElement l = le.getValue();
    491                     StringBuilder sb = new StringBuilder("local ");
    492                     sb.append(loc.varName == null
    493                         ? loc.index
    494                             + " #" + loc.scopeStart + "+" + loc.scopeLength
    495                         : loc.varName);
    496                     printElement(indent2, sb.toString(), l);
    497                     printTypeElementAndInnerTypes(indent3,
    498                             "type", l.type);
    499                 }
    500                 printRelativeElements(indent2, "typecast",
    501                         m.body.typecasts);
    502                 printRelativeElements(indent2, "instanceof",
    503                         m.body.instanceofs);
    504                 printRelativeElements(indent2, "new", m.body.news);
    505                 printRelativeElements(indent2, "reference", "typearg", m.body.refs);
    506                 printRelativeElements(indent2, "call", "typearg", m.body.calls);
    507                 for (Map. Entry<RelativeLocation,
    508                         AMethod> entry : m.body.funs.entrySet()) {
    509                     AMethod lambda = entry.getValue();
    510                     RelativeLocation loc = entry.getKey();
    511                     pw.print("lambda " + loc.getLocationString() + ":\n");
    512                     printBounds(indent3, lambda.bounds);
    513                     printTypeElementAndInnerTypes(indent3,
    514                         "return", lambda.returnType);
    515                 }
    516                 // throwsException field is not processed.  Why?
    517                 printASTInsertions(indent2,
    518                         m.insertAnnotations, m.insertTypecasts);
    519             }
    520             pw.println();
    521         }
    522     }
    523 
    524     private IndexFileWriter(AScene scene,
    525             Writer out) throws DefException {
    526         this.scene = scene;
    527         pw = new PrintWriter(out);
    528         write();
    529         pw.flush();
    530     }
    531 
    532     /**
    533      * Writes the annotations in <code>scene</code> and their definitions to
    534      * <code>out</code> in index file format.
    535      *
    536      * <p>
    537      * An {@link AScene} can contain several annotations of the same type but
    538      * different definitions, while an index file can accommodate only a single
    539      * definition for each annotation type.  This has two consequences:
    540      *
    541      * <ul>
    542      * <li>Before writing anything, this method uses a {@link DefCollector} to
    543      * ensure that all definitions of each annotation type are identical
    544      * (modulo unknown array types).  If not, a {@link DefException} is thrown.
    545      * <li>There is one case in which, even if a scene is written successfully,
    546      * reading it back in produces a different scene.  Consider a scene
    547      * containing two annotations of type Foo, each with an array field bar.
    548      * In one annotation, bar is empty and of unknown element type (see
    549      * {@link annotations.AnnotationBuilder#addEmptyArrayField}); in the other, bar is
    550      * of known element type.  This method will
    551      * {@linkplain AnnotationDef#unify unify} the two definitions of Foo by
    552      * writing a single definition with known element type.  When the index
    553      * file is read into a new scene, the definitions of both annotations
    554      * will have known element type, whereas in the original scene, one had
    555      * unknown element type.
    556      * </ul>
    557      */
    558     public static void write(
    559             AScene scene,
    560             Writer out) throws DefException {
    561         new IndexFileWriter(scene, out);
    562     }
    563 
    564     /**
    565      * Writes the annotations in <code>scene</code> and their definitions to
    566      * the file <code>filename</code> in index file format; see
    567      * {@link #write(AScene, Writer)}.
    568      */
    569     public static void write(
    570             AScene scene,
    571             String filename) throws IOException, DefException {
    572         write(scene, new FileWriter(filename));
    573     }
    574 }
    575