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