1 /* 2 * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /** 25 * @test 26 * @bug 8072480 8277106 8331027 27 * @summary Unit test for CreateSymbols 28 * @modules java.compiler 29 * jdk.compiler/com.sun.tools.javac.api 30 * jdk.compiler/com.sun.tools.javac.jvm 31 * jdk.compiler/com.sun.tools.javac.main 32 * jdk.compiler/com.sun.tools.javac.util 33 * @clean * 34 * @run main/othervm CreateSymbolsTest 35 */ 36 37 import java.io.File; 38 import java.io.InputStream; 39 import java.io.Writer; 40 import java.lang.annotation.Retention; 41 import java.lang.annotation.RetentionPolicy; 42 import java.lang.classfile.ClassFile; 43 import java.lang.classfile.ClassModel; 44 import java.lang.classfile.attribute.ModuleAttribute; 45 import java.lang.classfile.attribute.ModulePackagesAttribute; 46 import java.lang.constant.PackageDesc; 47 import java.lang.reflect.Method; 48 import java.util.Arrays; 49 import java.util.ArrayList; 50 import java.util.HashMap; 51 import java.util.List; 52 import java.util.Map; 53 import java.io.IOException; 54 import java.io.OutputStream; 55 import java.nio.charset.StandardCharsets; 56 import java.nio.file.DirectoryStream; 57 import java.nio.file.FileVisitResult; 58 import java.nio.file.FileVisitor; 59 import java.nio.file.Files; 60 import java.nio.file.Path; 61 import java.nio.file.Paths; 62 import java.nio.file.attribute.BasicFileAttributes; 63 import java.util.Enumeration; 64 import java.util.HashSet; 65 import java.util.Set; 66 import java.util.jar.JarEntry; 67 import java.util.jar.JarFile; 68 import java.util.stream.Collectors; 69 import java.util.stream.Stream; 70 import toolbox.JavacTask; 71 import toolbox.Task; 72 import toolbox.Task.Expect; 73 import toolbox.ToolBox; 74 import build.tools.symbolgenerator.CreateSymbols; 75 import build.tools.symbolgenerator.CreateSymbols.ClassDescription; 76 import build.tools.symbolgenerator.CreateSymbols.ClassList; 77 import build.tools.symbolgenerator.CreateSymbols.ExcludeIncludeList; 78 import build.tools.symbolgenerator.CreateSymbols.VersionDescription; 79 import java.io.UncheckedIOException; 80 import java.lang.classfile.attribute.ModuleMainClassAttribute; 81 import java.lang.constant.ClassDesc; 82 import java.util.Objects; 83 import java.util.function.Consumer; 84 85 public class CreateSymbolsTestImpl { 86 87 static final String CREATE_SYMBOLS_NAME = "symbolgenerator.CreateSymbols"; 88 89 public static void main(String... args) throws Exception { 90 new CreateSymbolsTestImpl().doTest(); 91 } 92 93 void doTest() throws Exception { 94 boolean testRun = false; 95 for (Method m : CreateSymbolsTestImpl.class.getDeclaredMethods()) { 96 if (m.isAnnotationPresent(Test.class)) { 97 m.invoke(this); 98 testRun = true; 99 } 100 } 101 if (!testRun) { 102 throw new IllegalStateException("No tests found."); 103 } 104 } 105 106 @Test 107 void testMethodRemoved() throws Exception { 108 doTest("package t; public class T { public void m() { } }", 109 "package t; public class T { }", 110 "package t; public class Test { { T t = null; t.m(); } }", 111 Expect.SUCCESS, 112 Expect.FAIL); 113 doTest("package t; public class T { public void b() { } public void m() { } public void a() { } }", 114 "package t; public class T { public void b() { } public void a() { } }", 115 "package t; public class Test { { T t = null; t.b(); t.a(); } }", 116 Expect.SUCCESS, 117 Expect.SUCCESS); 118 //with additional attribute (need to properly skip the member): 119 doTest("package t; public class T { public void m() throws IllegalStateException { } public void a() { } }", 120 "package t; public class T { public void a() { } }", 121 "package t; public class Test { { T t = null; t.a(); } }", 122 Expect.SUCCESS, 123 Expect.SUCCESS); 124 } 125 126 @Test 127 void testMethodAdded() throws Exception { 128 doTest("package t; public class T { }", 129 "package t; public class T { public void m() { } }", 130 "package t; public class Test { { T t = null; t.m(); } }", 131 Expect.FAIL, 132 Expect.SUCCESS); 133 doTest("package t; public class T { public void b() { } public void a() { } }", 134 "package t; public class T { public void b() { } public void m() { } public void a() { } }", 135 "package t; public class Test { { T t = null; t.b(); t.a(); } }", 136 Expect.SUCCESS, 137 Expect.SUCCESS); 138 } 139 140 //verify fields added/modified/removed 141 142 @Test 143 void testClassAdded() throws Exception { 144 doTest("class Dummy {}", 145 "package t; public class T { }", 146 "package t; public class Test { { T t = new T(); } }", 147 Expect.FAIL, 148 Expect.SUCCESS); 149 } 150 151 @Test 152 void testClassModified() throws Exception { 153 doTest("package t; public class T { public void m() { } }", 154 "package t; public class T implements java.io.Serializable { public void m() { } }", 155 "package t; public class Test { { java.io.Serializable t = new T(); } }", 156 Expect.FAIL, 157 Expect.SUCCESS); 158 } 159 160 @Test 161 void testClassRemoved() throws Exception { 162 doTest("package t; public class T { }", 163 "class Dummy {}", 164 "package t; public class Test { { T t = new T(); } }", 165 Expect.SUCCESS, 166 Expect.FAIL); 167 } 168 169 @Test 170 void testInnerClassAttributes() throws Exception { 171 doTest("package t; public class T { public static class Inner { } }", 172 "package t; public class T { public static class Inner { } public void extra() {} }", 173 "package t; import t.T.Inner; public class Test { Inner i; }", 174 Expect.SUCCESS, 175 Expect.SUCCESS); 176 } 177 178 @Test 179 void testConstantAdded() throws Exception { 180 doTest("package t; public class T { }", 181 "package t; public class T { public static final int A = 0; }", 182 "package t; public class Test { void t(int i) { switch (i) { case T.A: break;} } }", 183 Expect.FAIL, 184 Expect.SUCCESS); 185 } 186 187 @Test 188 void testAnnotationAttributeDefaultvalue() throws Exception { 189 //TODO: this only verifies that there is *some* value, but we should also verify there is a specific value: 190 doTest("package t; public @interface T { }", 191 "package t;\n" + 192 "public @interface T {\n" + 193 " public boolean booleanValue() default true;\n" + 194 " public byte byteValue() default 1;\n" + 195 " public char charValue() default 2;\n" + 196 " public short shortValue() default 3;\n" + 197 " public int intValue() default 4;\n" + 198 " public long longValue() default 5;\n" + 199 " public float floatValue() default 6;\n" + 200 " public double doubleValue() default 7;\n" + 201 " public String stringValue() default \"8\";\n" + 202 " public java.lang.annotation.RetentionPolicy enumValue() default java.lang.annotation.RetentionPolicy.RUNTIME;\n" + 203 " public Class classValue() default Number.class;\n" + 204 " public int[] arrayValue() default {1, 2};\n" + 205 " public SuppressWarnings annotationValue() default @SuppressWarnings(\"cast\");\n" + 206 "}\n", 207 "package t; public @T class Test { }", 208 Expect.SUCCESS, 209 Expect.SUCCESS); 210 } 211 212 @Test 213 void testConstantTest() throws Exception { 214 //XXX: other constant types (String in particular) - see testStringConstant 215 doPrintElementTest("package t; public class T { public static final int A = 1; }", 216 "package t; public class T { public static final int A = 2; }", 217 "t.T", 218 "package t;\n\n" + 219 "public class T {\n" + 220 " public static final int A = 1;\n\n" + 221 " public T();\n" + 222 "}\n", 223 "t.T", 224 "package t;\n\n" + 225 "public class T {\n" + 226 " public static final int A = 2;\n\n" + 227 " public T();\n" + 228 "}\n"); 229 doPrintElementTest("package t; public class T { public static final boolean A = false; }", 230 "package t; public class T { public static final boolean A = true; }", 231 "t.T", 232 "package t;\n\n" + 233 "public class T {\n" + 234 " public static final boolean A = false;\n\n" + 235 " public T();\n" + 236 "}\n", 237 "t.T", 238 "package t;\n\n" + 239 "public class T {\n" + 240 " public static final boolean A = true;\n\n" + 241 " public T();\n" + 242 "}\n"); 243 } 244 245 @Test 246 void testAnnotations() throws Exception { 247 Set<String> extraAnnotations = Set.of("Ljava/lang/annotation/Retention;"); 248 CreateSymbols.HARDCODED_ANNOTATIONS.addAll(extraAnnotations); 249 try { 250 doPrintElementTest("package t;" + 251 "import java.lang.annotation.*;" + 252 "public @Visible @Invisible class T { public void extra() { } }" + 253 "@Retention(RetentionPolicy.RUNTIME) @interface Visible { }" + 254 "@Retention(RetentionPolicy.CLASS) @interface Invisible { }", 255 "package t;" + 256 "import java.lang.annotation.*;" + 257 "public @Visible @Invisible class T { }" + 258 "@Retention(RetentionPolicy.RUNTIME) @interface Visible { }" + 259 "@Retention(RetentionPolicy.CLASS) @interface Invisible { }", 260 "t.T", 261 "package t;\n\n" + 262 "@t.Invisible\n" + 263 "@t.Visible\n" + 264 "public class T {\n\n" + 265 " public T();\n\n" + 266 " public void extra();\n" + 267 "}\n", 268 "t.Visible", 269 "package t;\n\n" + 270 "@java.lang.annotation.Retention(RUNTIME)\n" + 271 "@interface Visible {\n" + 272 "}\n"); 273 doPrintElementTest("package t;" + 274 "import java.lang.annotation.*;" + 275 "import java.util.*;" + 276 "public class T {" + 277 " public void test(int h, @Invisible int i, @Visible List<String> j, int k) { }" + 278 "}" + 279 "@Retention(RetentionPolicy.RUNTIME) @interface Visible { }" + 280 "@Retention(RetentionPolicy.CLASS) @interface Invisible { }", 281 "package t;" + 282 "import java.lang.annotation.*;" + 283 "import java.util.*;" + 284 "public class T {" + 285 " public void test(int h, @Invisible int i, @Visible List<String> j, int k) { }" + 286 " public void extra() { }" + 287 "}" + 288 "@Retention(RetentionPolicy.RUNTIME) @interface Visible { }" + 289 "@Retention(RetentionPolicy.CLASS) @interface Invisible { }", 290 "t.T", 291 "package t;\n\n" + 292 "public class T {\n\n" + 293 " public T();\n\n" + 294 " public void test(int arg0,\n" + 295 " @t.Invisible int arg1,\n" + 296 " @t.Visible java.util.List<java.lang.String> arg2,\n" + 297 " int arg3);\n" + 298 "}\n", 299 "t.Visible", 300 "package t;\n\n" + 301 "@java.lang.annotation.Retention(RUNTIME)\n" + 302 "@interface Visible {\n" + 303 "}\n"); 304 doPrintElementTest("package t;" + 305 "import java.lang.annotation.*;" + 306 "public class T {" + 307 " public void test(@Ann(v=\"url\", dv=\"\\\"\\\"\") String str) { }" + 308 "}" + 309 "@Retention(RetentionPolicy.RUNTIME) @interface Ann {" + 310 " public String v();" + 311 " public String dv();" + 312 "}", 313 "package t;" + 314 "public class T { }", 315 "t.T", 316 "package t;\n\n" + 317 "public class T {\n\n" + 318 " public T();\n\n" + 319 " public void test(@t.Ann(dv=\"\\\"\\\"\", v=\"url\") java.lang.String arg0);\n" + 320 "}\n", 321 "t.T", 322 "package t;\n\n" + 323 "public class T {\n\n" + 324 " public T();\n" + 325 "}\n"); 326 } finally { 327 CreateSymbols.HARDCODED_ANNOTATIONS.removeAll(extraAnnotations); 328 } 329 } 330 331 @Test 332 void testStringConstant() throws Exception { 333 doTest("package t; public class T { public static final String C = \"\"; }", 334 "package t; public class T { public static final String C = \"\"; public void extra() { } }", 335 "package t; public class Test { { System.err.println(T.C); } }", 336 Expect.SUCCESS, 337 Expect.SUCCESS); 338 } 339 340 @Test 341 void testCopyProfileAnnotation() throws Exception { 342 String oldProfileAnnotation = CreateSymbols.PROFILE_ANNOTATION; 343 try { 344 CreateSymbols.PROFILE_ANNOTATION = "Lt/Ann;"; 345 doTestEquivalence("package t; public @Ann class T { public void t() {} } @interface Ann { }", 346 "package t; public class T { public void t() {} }", 347 "t.T"); 348 } finally { 349 CreateSymbols.PROFILE_ANNOTATION = oldProfileAnnotation; 350 } 351 } 352 353 @Test 354 void testParseAnnotation() throws Exception { 355 CreateSymbols.parseAnnotations("@Lsun/Proprietary+Annotation;@Ljdk/Profile+Annotation;(value=I1)", new int[1]); 356 CreateSymbols.parseAnnotations("@Ltest;(value={\"\"})", new int[1]); 357 CreateSymbols.parseAnnotations("@Ljava/beans/ConstructorProperties;(value={\"path\"})", new int[1]); 358 CreateSymbols.parseAnnotations("@Ljava/beans/ConstructorProperties;(value=I-2)", new int[1]); 359 } 360 361 @Test 362 void testStringCharLiterals() throws Exception { 363 doPrintElementTest("package t;" + 364 "public class T {" + 365 " public static final String STR = \"\\u0000\\u0001\\uffff\";" + 366 " public static final String EMPTY = \"\";" + 367 " public static final String AMP = \"&&<<>>''\";" + 368 "}", 369 "package t;" + 370 " public class T {" + 371 " public static final char c = '\\uffff';" + 372 "}", 373 "t.T", 374 "package t;\n\n" + 375 "public class T {\n" + 376 " public static final java.lang.String STR = \"\\u0000\\u0001\\uffff\";\n" + 377 " public static final java.lang.String EMPTY = \"\";\n" + 378 " public static final java.lang.String AMP = \"&&<<>>''\";\n\n" + 379 " public T();\n" + 380 "}\n", 381 "t.T", 382 "package t;\n\n" + 383 "public class T {\n" + 384 " public static final char c = '\\uffff';\n\n" + 385 " public T();\n" + 386 "}\n"); 387 } 388 389 @Test 390 void testGenerification() throws Exception { 391 doTest("package t; public class T { public class TT { public Object t() { return null; } } }", 392 "package t; public class T<E> { public class TT { public E t() { return null; } } }", 393 "package t; public class Test { { T.TT tt = null; tt.t(); } }", 394 Expect.SUCCESS, 395 Expect.SUCCESS); 396 } 397 398 @Test 399 void testClearMissingAnnotations() throws Exception { 400 doPrintElementTest(new String[] { 401 """ 402 package t; 403 import t.impl.HC; 404 import t.impl.HR; 405 @HC @HR public class T { 406 @HC @HR public static final int i = 0; 407 @HC @HR public void t() {} 408 } 409 """, 410 """ 411 package t.impl; 412 import java.lang.annotation.*; 413 @Retention(RetentionPolicy.CLASS) 414 public @interface HC { 415 } 416 """, 417 """ 418 package t.impl; 419 import java.lang.annotation.*; 420 @Retention(RetentionPolicy.RUNTIME) 421 public @interface HR { 422 } 423 """ 424 }, 425 new String[] { 426 """ 427 package t; 428 public class T { 429 public static final int i = 0; 430 } 431 """ 432 }, 433 "t.T", 434 """ 435 package t; 436 437 public class T { 438 public static final int i = 0; 439 440 public T(); 441 442 public void t(); 443 } 444 """, 445 "t.T", 446 """ 447 package t; 448 449 public class T { 450 public static final int i = 0; 451 452 public T(); 453 } 454 """); 455 } 456 457 int i = 0; 458 459 void doTest(String code7, String code8, String testCode, Expect result7, Expect result8) throws Exception { 460 ToolBox tb = new ToolBox(); 461 Path classes = prepareVersionedCTSym(new String[] {code7}, new String[] {code8}); 462 Path output = classes.getParent(); 463 Path scratch = output.resolve("scratch"); 464 465 Files.createDirectories(scratch); 466 467 new JavacTask(tb) 468 .sources(testCode) 469 .options("-d", scratch.toAbsolutePath().toString(), "-classpath", computeClassPath(classes, "7")) 470 .run(result7) 471 .writeAll(); 472 new JavacTask(tb) 473 .sources(testCode) 474 .options("-d", scratch.toAbsolutePath().toString(), "-classpath", computeClassPath(classes, "8")) 475 .run(result8) 476 .writeAll(); 477 } 478 479 private static String computeClassPath(Path classes, String version) throws IOException { 480 try (Stream<Path> elements = Files.list(classes)) { 481 return elements.filter(el -> el.getFileName().toString().contains(version)) 482 .map(el -> el.resolve("java.base")) 483 .map(el -> el.toAbsolutePath().toString()) 484 .collect(Collectors.joining(File.pathSeparator)); 485 } 486 } 487 488 void doPrintElementTest(String code7, String code8, String className7, String printed7, String className8, String printed8) throws Exception { 489 doPrintElementTest(new String[] {code7}, new String[] {code8}, className7, printed7, className8, printed8); 490 } 491 492 void doPrintElementTest(String[] code7, String[] code8, String className7, String printed7, String className8, String printed8) throws Exception { 493 ToolBox tb = new ToolBox(); 494 Path classes = prepareVersionedCTSym(code7, code8); 495 Path output = classes.getParent(); 496 Path scratch = output.resolve("scratch"); 497 498 Files.createDirectories(scratch); 499 500 String out; 501 out = new JavacTask(tb, Task.Mode.CMDLINE) 502 .options("-d", scratch.toAbsolutePath().toString(), "-classpath", computeClassPath(classes, "7"), "-Xprint", className7) 503 .run(Expect.SUCCESS) 504 .getOutput(Task.OutputKind.STDOUT) 505 .replaceAll("\\R", "\n"); 506 if (!out.equals(printed7)) { 507 throw new AssertionError("out=" + out + "; printed7=" + printed7); 508 } 509 out = new JavacTask(tb, Task.Mode.CMDLINE) 510 .options("-d", scratch.toAbsolutePath().toString(), "-classpath", computeClassPath(classes, "8"), "-Xprint", className8) 511 .run(Expect.SUCCESS) 512 .getOutput(Task.OutputKind.STDOUT) 513 .replaceAll("\\R", "\n"); 514 if (!out.equals(printed8)) { 515 throw new AssertionError("out=" + out + "; printed8=" + printed8); 516 } 517 } 518 519 void doTestEquivalence(String code7, String code8, String testClass) throws Exception { 520 Path classes = prepareVersionedCTSym(new String[] {code7}, new String[] {code8}); 521 Path classfile = classes.resolve("78").resolve("java.base").resolve(testClass.replace('.', '/') + ".class"); 522 523 if (!Files.isReadable(classfile)) { 524 throw new AssertionError("Cannot find expected class."); 525 } 526 } 527 528 @Test 529 void testIncluded() throws Exception { 530 doTestIncluded("package t;\n" + 531 "public class Test extends PP1<PP2> implements PP3<PP4>, PP5<PP6> {\n" + 532 " public PP7 m1(PP8 p) { return null;}\n" + 533 " public PP9<PPA> m2(PPB<PPC> p) { return null;}\n" + 534 " public PPD f1;\n" + 535 " public PPE<PPF> f2;\n" + 536 " public Test2 aux;\n" + 537 "}\n" + 538 "class Test2 extends PPG implements PPH, PPI {\n" + 539 "}\n" + 540 "class PP1<T> {}\n" + 541 "class PP2 {}\n" + 542 "interface PP3<T> {}\n" + 543 "class PP4 {}\n" + 544 "interface PP5<T> {}\n" + 545 "class PP6 {}\n" + 546 "class PP7 {}\n" + 547 "class PP8 {}\n" + 548 "class PP9<T> {}\n" + 549 "class PPA {}\n" + 550 "class PPB<T> {}\n" + 551 "class PPC {}\n" + 552 "class PPD {}\n" + 553 "class PPE<T> {}\n" + 554 "class PPF {}\n" + 555 "class PPG {}\n" + 556 "interface PPH {}\n" + 557 "interface PPI {}\n", 558 "t.Test", 559 "t.Test2", 560 "t.PP1", 561 "t.PP2", 562 "t.PP3", 563 "t.PP4", 564 "t.PP5", 565 "t.PP6", 566 "t.PP7", 567 "t.PP8", 568 "t.PP9", 569 "t.PPA", 570 "t.PPB", 571 "t.PPC", 572 "t.PPD", 573 "t.PPE", 574 "t.PPF", 575 "t.PPG", 576 "t.PPH", 577 "t.PPI"); 578 } 579 580 @Test 581 void testRecords() throws Exception { 582 doPrintElementTest("package t;" + 583 "public class T {" + 584 " public record R(int i, java.util.List<String> l) { }" + 585 "}", 586 "package t;" + 587 "public class T {" + 588 " public record R(@Ann int i, long j, java.util.List<String> l) { }" + 589 " public @interface Ann {} " + 590 "}", 591 "t.T$R", 592 """ 593 594 public static record R(int i, java.util.List<java.lang.String> l) { 595 596 public R(int i, 597 java.util.List<java.lang.String> l); 598 599 public final java.lang.String toString(); 600 601 public final int hashCode(); 602 603 public final boolean equals(java.lang.Object arg0); 604 605 public int i(); 606 607 public java.util.List<java.lang.String> l(); 608 } 609 """, 610 "t.T$R", 611 """ 612 613 public static record R(@t.T.Ann int i, long j, java.util.List<java.lang.String> l) { 614 615 public final java.lang.String toString(); 616 617 public final int hashCode(); 618 619 public final boolean equals(java.lang.Object arg0); 620 621 public java.util.List<java.lang.String> l(); 622 623 public R(@t.T.Ann int i, 624 long j, 625 java.util.List<java.lang.String> l); 626 627 @t.T.Ann 628 public int i(); 629 630 public long j(); 631 } 632 """); 633 doPrintElementTest("package t;" + 634 "public record R() {" + 635 "}", 636 "package t;" + 637 "public record R(int i) {" + 638 "}", 639 "t.R", 640 """ 641 package t; 642 \n\ 643 public record R() { 644 \n\ 645 public R(); 646 \n\ 647 public final java.lang.String toString(); 648 \n\ 649 public final int hashCode(); 650 \n\ 651 public final boolean equals(java.lang.Object arg0); 652 } 653 """, 654 "t.R", 655 """ 656 package t; 657 \n\ 658 public record R(int i) { 659 \n\ 660 public final java.lang.String toString(); 661 \n\ 662 public final int hashCode(); 663 \n\ 664 public final boolean equals(java.lang.Object arg0); 665 \n\ 666 public R(int i); 667 \n\ 668 public int i(); 669 } 670 """); 671 } 672 673 @Test 674 void testNonExportedSuperclass() throws Exception { 675 doTestComplex("api.Api", 676 """ 677 package api; 678 679 public class Api extends nonapi.Impl.Nested.Exp { 680 681 public Api(); 682 } 683 """, 684 """ 685 import api.Api; 686 public class Test { 687 private void t(Api api) { 688 api.run(); 689 } 690 } 691 """, 692 """ 693 import api.Api; 694 public class Test { 695 private void t(Api api) { 696 fail 697 } 698 } 699 """, 700 """ 701 module m { 702 exports api; 703 } 704 """, 705 """ 706 package api; 707 import nonapi.Impl; 708 public class Api extends Impl.Nested.Exp { 709 } 710 """, 711 """ 712 package api; 713 public @interface Ann { 714 } 715 """, 716 """ 717 package nonapi; 718 import api.Ann; 719 public class Impl { 720 public static final String C = ""; 721 public void test() {} 722 @Ann 723 public static class Nested { 724 public static class Exp extends Nested implements Runnable { 725 public void run() {} 726 public OtherNested get() { return null; } 727 } 728 } 729 public static class OtherNested {} 730 } 731 """); 732 } 733 734 void doTestComplex(String printClass, 735 String expected, 736 String depSuccess, 737 String depFailure, 738 String... code) throws Exception { 739 ToolBox tb = new ToolBox(); 740 String testClasses = System.getProperty("test.classes"); 741 Path output = Paths.get(testClasses, "test-data" + i++); 742 deleteRecursively(output); 743 Files.createDirectories(output); 744 Path ver9Jar = output.resolve("9.jar"); 745 compileAndPack(output, 746 ver9Jar, 747 code); 748 749 750 Path ctSym = output.resolve("ct.sym"); 751 752 deleteRecursively(ctSym); 753 754 CreateSymbols.ALLOW_NON_EXISTING_CLASSES = true; 755 CreateSymbols.EXTENSION = ".class"; 756 757 deleteRecursively(ctSym); 758 759 List<VersionDescription> versions = 760 Arrays.asList(new VersionDescription(ver9Jar.toAbsolutePath().toString(), "9", null)); 761 762 ExcludeIncludeList acceptAll = new ExcludeIncludeList(null, null) { 763 @Override public boolean accepts(String className, boolean includePrivateClasses) { 764 return true; 765 } 766 }; 767 new CreateSymbols().createBaseLine(versions, acceptAll, ctSym, new String[0]); 768 Path symbolsDesc = ctSym.resolve("symbols"); 769 Path modules = ctSym.resolve("modules"); 770 Path modulesList = ctSym.resolve("modules-list"); 771 772 Files.createDirectories(modules); 773 try (Writer w = Files.newBufferedWriter(modulesList)) {} 774 775 Path classesZip = output.resolve("classes.zip"); 776 Path classesDir = output.resolve("classes"); 777 778 new CreateSymbols().createSymbols(null, symbolsDesc.toAbsolutePath().toString(), classesZip.toAbsolutePath().toString(), 0, "9", "", modules.toString(), modulesList.toString()); 779 780 try (JarFile jf = new JarFile(classesZip.toFile())) { 781 Enumeration<JarEntry> en = jf.entries(); 782 783 while (en.hasMoreElements()) { 784 JarEntry je = en.nextElement(); 785 if (je.isDirectory()) continue; 786 Path target = classesDir.resolve(je.getName()); 787 Files.createDirectories(target.getParent()); 788 Files.copy(jf.getInputStream(je), target); 789 } 790 } 791 792 Path classes = classesDir; 793 Path scratch = output.resolve("scratch"); 794 795 Files.createDirectories(scratch); 796 797 String modulePath; 798 799 try (Stream<Path> elements = Files.list(classes)) { 800 modulePath = elements.filter(el -> el.getFileName().toString().contains("9")) 801 .map(el -> el.resolve("m")) 802 .map(el -> el.toAbsolutePath().toString()) 803 .collect(Collectors.joining(File.pathSeparator)); 804 } 805 806 { 807 String out = new JavacTask(tb, Task.Mode.CMDLINE) 808 .options("-d", scratch.toAbsolutePath().toString(), "--module-path", modulePath, 809 "--add-modules", "m", "-Xprint", "api.Api") 810 .run(Expect.SUCCESS) 811 .getOutput(Task.OutputKind.STDOUT) 812 .replaceAll("\\R", "\n"); 813 814 if (!out.equals(expected)) { 815 throw new AssertionError("out=" + out + "; expected=" + expected); 816 } 817 } 818 819 { 820 new JavacTask(tb) 821 .options("-d", scratch.toAbsolutePath().toString(), "--module-path", modulePath, 822 "--add-modules", "m") 823 .sources(depSuccess) 824 .run(Expect.SUCCESS) 825 .writeAll(); 826 } 827 828 { 829 String expectedFailure = new JavacTask(tb) 830 .options("-d", scratch.toAbsolutePath().toString(), "--module-path", output.resolve("temp").toString(), 831 "--add-modules", "m", "-XDrawDiagnostics") 832 .sources(depFailure) 833 .run(Expect.FAIL) 834 .getOutput(Task.OutputKind.DIRECT) 835 .replaceAll("\\R", "\n"); 836 837 String out = new JavacTask(tb) 838 .options("-d", scratch.toAbsolutePath().toString(), "--module-path", modulePath, 839 "--add-modules", "m", "-XDrawDiagnostics") 840 .sources(depFailure) 841 .run(Expect.FAIL) 842 .getOutput(Task.OutputKind.DIRECT) 843 .replaceAll("\\R", "\n"); 844 845 if (!out.equals(expectedFailure)) { 846 throw new AssertionError("out=" + out + "; expected=" + expectedFailure); 847 } 848 } 849 } 850 851 @Test 852 void testExtendsInternalData1() throws Exception { 853 doTestData(""" 854 module name m 855 header exports api extraModulePackages nonapi requires name\\u0020;java.base\\u0020;flags\\u0020;8000\\u0020;version\\u0020;0 flags 8000 856 857 class name api/Ann 858 header extends java/lang/Object implements java/lang/annotation/Annotation flags 2601 859 860 class name api/Api 861 header extends nonapi/Impl$Nested$Exp flags 21 862 innerclass innerClass nonapi/Impl$Nested outerClass nonapi/Impl innerClassName Nested flags 9 863 innerclass innerClass nonapi/Impl$Nested$Exp outerClass nonapi/Impl$Nested innerClassName Exp flags 9 864 method name <init> descriptor ()V flags 1 865 866 class name nonapi/Impl 867 header extends java/lang/Object nestMembers nonapi/Impl$Nested,nonapi/Impl$Nested$Exp flags 21 868 innerclass innerClass nonapi/Impl$Nested outerClass nonapi/Impl innerClassName Nested flags 9 869 innerclass innerClass nonapi/Impl$Nested$Exp outerClass nonapi/Impl$Nested innerClassName Exp flags 9 870 field name C descriptor Ljava/lang/String; constantValue flags 19 871 method name <init> descriptor ()V flags 1 872 method name test descriptor ()V flags 1 873 874 class name nonapi/Impl$Nested 875 header extends java/lang/Object nestHost nonapi/Impl flags 21 classAnnotations @Lapi/Ann; 876 innerclass innerClass nonapi/Impl$Nested outerClass nonapi/Impl innerClassName Nested flags 9 877 innerclass innerClass nonapi/Impl$Nested$Exp outerClass nonapi/Impl$Nested innerClassName Exp flags 9 878 method name <init> descriptor ()V flags 1 879 880 class name nonapi/Impl$Nested$Exp 881 header extends nonapi/Impl$Nested implements java/lang/Runnable nestHost nonapi/Impl flags 21 882 innerclass innerClass nonapi/Impl$Nested outerClass nonapi/Impl innerClassName Nested flags 9 883 innerclass innerClass nonapi/Impl$Nested$Exp outerClass nonapi/Impl$Nested innerClassName Exp flags 9 884 method name <init> descriptor ()V flags 1 885 method name run descriptor ()V flags 1 886 method name get descriptor ()Lnonapi/Impl$OtherNested; flags 1 887 888 """, 889 """ 890 module m { 891 exports api; 892 exports nonapi to java.base; 893 } 894 """, 895 """ 896 package api; 897 import nonapi.Impl; 898 public class Api extends Impl.Nested.Exp { 899 } 900 """, 901 """ 902 package api; 903 public @interface Ann { 904 } 905 """, 906 """ 907 package nonapi; 908 import api.Ann; 909 public class Impl { 910 public static final String C = ""; 911 public void test() {} 912 @Ann 913 public static class Nested { 914 public static class Exp extends Nested implements Runnable { 915 public void run() {} 916 public OtherNested get() { return null; } 917 } 918 } 919 public static class OtherNested {} 920 } 921 """); 922 } 923 924 @Test 925 void testTypeAnnotations() throws Exception { 926 doPrintElementTest(""" 927 package t; 928 public class T { 929 } 930 """, 931 """ 932 package t; 933 import java.lang.annotation.*; 934 import java.util.*; 935 public class T<@AnnInvisible @AnnVisible E extends @AnnInvisible @AnnVisible ArrayList<@AnnInvisible @AnnVisible ArrayList>> extends @AnnInvisible @AnnVisible ArrayList { 936 public @AnnInvisible @AnnVisible List<@AnnInvisible @AnnVisible E> field; 937 public <@AnnInvisible @AnnVisible M extends @AnnInvisible @AnnVisible ArrayList<@AnnInvisible @AnnVisible ArrayList>> @AnnInvisible @AnnVisible List<@AnnInvisible @AnnVisible M> convert(@AnnInvisible @AnnVisible T<E> this, @AnnInvisible @AnnVisible M e1, @AnnInvisible @AnnVisible List<@AnnInvisible @AnnVisible E> e2) throws @AnnInvisible @AnnVisible IllegalStateException, @AnnInvisible @AnnVisible IllegalArgumentException { 938 return null; 939 } 940 } 941 @Retention(RetentionPolicy.RUNTIME) 942 @Target(ElementType.TYPE_USE) 943 @interface AnnVisible { 944 } 945 @Retention(RetentionPolicy.CLASS) 946 @Target(ElementType.TYPE_USE) 947 @interface AnnInvisible { 948 } 949 """, 950 "t.T", 951 """ 952 package t; 953 954 public class T { 955 956 public T(); 957 } 958 """, 959 "t.T", 960 """ 961 package t; 962 963 public class T<@t.AnnInvisible @t.AnnVisible E extends java.util.@t.AnnInvisible @t.AnnVisible ArrayList<java.util.@t.AnnInvisible @t.AnnVisible ArrayList>> extends java.util.@t.AnnInvisible @t.AnnVisible ArrayList { 964 public java.util.@t.AnnInvisible @t.AnnVisible List<@t.AnnInvisible @t.AnnVisible E> field; 965 966 public T(); 967 968 public <@t.AnnInvisible @t.AnnVisible M extends java.util.@t.AnnInvisible @t.AnnVisible ArrayList<java.util.@t.AnnInvisible @t.AnnVisible ArrayList>> java.util.@t.AnnInvisible @t.AnnVisible List<@t.AnnInvisible @t.AnnVisible M> convert(@t.AnnInvisible @t.AnnVisible M arg0, 969 java.util.@t.AnnInvisible @t.AnnVisible List<@t.AnnInvisible @t.AnnVisible E> arg1) throws java.lang.@t.AnnInvisible @t.AnnVisible IllegalStateException,\s 970 java.lang.@t.AnnInvisible @t.AnnVisible IllegalArgumentException; 971 } 972 """); 973 } 974 975 @Test 976 void testParameterAnnotations() throws Exception { 977 doPrintElementTest(""" 978 package t; 979 public class T { 980 public void test(int p1, int p2) { 981 } 982 } 983 """, 984 """ 985 package t; 986 import java.lang.annotation.*; 987 import java.util.*; 988 public class T { 989 public void test(@AnnVisible int p1, @AnnInvisible int p2) { 990 } 991 } 992 @Retention(RetentionPolicy.RUNTIME) 993 @Target(ElementType.PARAMETER) 994 @interface AnnVisible { 995 } 996 @Retention(RetentionPolicy.CLASS) 997 @Target(ElementType.PARAMETER) 998 @interface AnnInvisible { 999 } 1000 """, 1001 "t.T", 1002 """ 1003 package t; 1004 1005 public class T { 1006 1007 public T(); 1008 1009 public void test(int arg0, 1010 int arg1); 1011 } 1012 """, 1013 "t.T", 1014 """ 1015 package t; 1016 1017 public class T { 1018 1019 public T(); 1020 1021 public void test(@t.AnnVisible int arg0, 1022 @t.AnnInvisible int arg1); 1023 } 1024 """); 1025 } 1026 1027 void doTestData(String data, 1028 String... code) throws Exception { 1029 String testClasses = System.getProperty("test.classes"); 1030 Path output = Paths.get(testClasses, "test-data" + i++); 1031 deleteRecursively(output); 1032 Files.createDirectories(output); 1033 Path ver9Jar = output.resolve("9.jar"); 1034 compileAndPack(output, 1035 ver9Jar, 1036 code); 1037 1038 Path ctSym = output.resolve("ct.sym"); 1039 1040 deleteRecursively(ctSym); 1041 1042 CreateSymbols.ALLOW_NON_EXISTING_CLASSES = true; 1043 CreateSymbols.DO_NOT_MODIFY = ""; 1044 CreateSymbols.EXTENSION = ".class"; 1045 CreateSymbols.INJECTED_VERSION = "0"; 1046 1047 deleteRecursively(ctSym); 1048 1049 List<VersionDescription> versions = 1050 Arrays.asList(new VersionDescription(ver9Jar.toAbsolutePath().toString(), "9", null)); 1051 1052 ExcludeIncludeList acceptAll = new ExcludeIncludeList(null, null) { 1053 @Override public boolean accepts(String className, boolean includePrivateClasses) { 1054 return true; 1055 } 1056 }; 1057 new CreateSymbols().createBaseLine(versions, acceptAll, ctSym, new String[0]); 1058 1059 Path symFile = null; 1060 1061 try (DirectoryStream<Path> ds = Files.newDirectoryStream(ctSym)) { 1062 for (Path p : ds) { 1063 if (p.toString().endsWith(".sym.txt")) { 1064 if (symFile != null) { 1065 throw new IllegalStateException("Multiple sym files!"); 1066 } else { 1067 symFile = p; 1068 } 1069 } 1070 } 1071 } 1072 String acutalContent = new String(Files.readAllBytes(symFile), StandardCharsets.UTF_8); 1073 if (!acutalContent.equals(data)) { 1074 throw new AssertionError("out=" + acutalContent + "; expected=" + data); 1075 } 1076 } 1077 1078 void doTestIncluded(String code, String... includedClasses) throws Exception { 1079 boolean oldIncludeAll = includeAll; 1080 try { 1081 includeAll = false; 1082 Path classes = prepareVersionedCTSym(new String[] {code}, new String[] {"package other; public class Other {}"}); 1083 Path root = classes.resolve("7").resolve("java.base"); 1084 try (Stream<Path> classFiles = Files.walk(root)) { 1085 Set<String> names = classFiles.map(p -> root.relativize(p)) 1086 .map(p -> p.toString()) 1087 .map(n -> {System.err.println("n= " + n); return n;}) 1088 .filter(n -> n.endsWith(".class")) 1089 .map(n -> n.substring(0, n.lastIndexOf('.'))) 1090 .map(n -> n.replace(File.separator, ".")) 1091 .collect(Collectors.toSet()); 1092 1093 if (!names.equals(new HashSet<>(Arrays.asList(includedClasses)))) 1094 throw new AssertionError("Expected classes not included: " + names); 1095 } 1096 } finally { 1097 includeAll = oldIncludeAll; 1098 } 1099 } 1100 1101 Path prepareVersionedCTSym(String[] code7, String[] code8) throws Exception { 1102 return prepareVersionedCTSym(code7, code8, _ -> {}); 1103 } 1104 1105 Path prepareVersionedCTSym(String[] code7, String[] code8, 1106 Consumer<Path> adjustClassFiles) throws Exception { 1107 String testClasses = System.getProperty("test.classes"); 1108 Path output = Paths.get(testClasses, "test-data" + i++); 1109 deleteRecursively(output); 1110 Files.createDirectories(output); 1111 Path ver7Jar = output.resolve("7.jar"); 1112 compileAndPack(output, ver7Jar, adjustClassFiles, code7); 1113 Path ver8Jar = output.resolve("8.jar"); 1114 compileAndPack(output, ver8Jar, adjustClassFiles, code8); 1115 1116 Path classes = output.resolve("classes.zip"); 1117 1118 Path ctSym = output.resolve("ct.sym"); 1119 1120 deleteRecursively(ctSym); 1121 1122 CreateSymbols.ALLOW_NON_EXISTING_CLASSES = true; 1123 CreateSymbols.EXTENSION = ".class"; 1124 1125 testGenerate(ver7Jar, ver8Jar, ctSym, "8", classes.toAbsolutePath().toString()); 1126 1127 Path classesDir = output.resolve("classes"); 1128 1129 try (JarFile jf = new JarFile(classes.toFile())) { 1130 Enumeration<JarEntry> en = jf.entries(); 1131 1132 while (en.hasMoreElements()) { 1133 JarEntry je = en.nextElement(); 1134 if (je.isDirectory()) continue; 1135 Path target = classesDir.resolve(je.getName()); 1136 Files.createDirectories(target.getParent()); 1137 Files.copy(jf.getInputStream(je), target); 1138 } 1139 } 1140 1141 return classesDir; 1142 } 1143 1144 boolean includeAll = true; 1145 1146 void testGenerate(Path jar7, Path jar8, Path descDest, String version, String classDest) throws IOException { 1147 deleteRecursively(descDest); 1148 1149 List<VersionDescription> versions = 1150 Arrays.asList(new VersionDescription(jar7.toAbsolutePath().toString(), "7", null), 1151 new VersionDescription(jar8.toAbsolutePath().toString(), "8", "7")); 1152 1153 ExcludeIncludeList acceptAll = new ExcludeIncludeList(null, null) { 1154 @Override public boolean accepts(String className, boolean includePrivateClasses) { 1155 return true; 1156 } 1157 }; 1158 new CreateSymbols() { 1159 @Override 1160 protected boolean includeEffectiveAccess(ClassList classes, ClassDescription clazz) { 1161 return includeAll ? true : super.includeEffectiveAccess(classes, clazz); 1162 } 1163 }.createBaseLine(versions, acceptAll, descDest, new String[0]); 1164 Path symbolsDesc = descDest.resolve("symbols"); 1165 Path modules = descDest.resolve("modules"); 1166 Path modulesList = descDest.resolve("modules-list"); 1167 1168 Files.createDirectories(modules); 1169 try (Writer w = Files.newBufferedWriter(modulesList)) {} 1170 1171 new CreateSymbols().createSymbols(null, symbolsDesc.toAbsolutePath().toString(), classDest, 0, "8", "", modules.toString(), modulesList.toString()); 1172 } 1173 1174 @Test 1175 void testModuleMainClass() throws Exception { 1176 ClassFile cf = ClassFile.of(); 1177 ToolBox tb = new ToolBox(); 1178 String testClasses = System.getProperty("test.classes"); 1179 Path output = Paths.get(testClasses, "test-data" + i++); 1180 deleteRecursively(output); 1181 Files.createDirectories(output); 1182 Path ver9Jar = output.resolve("9.jar"); 1183 compileAndPack(output, 1184 ver9Jar, 1185 classesDir -> { 1186 try { 1187 Path moduleInfo = classesDir.resolve("module-info.class"); 1188 byte[] newClassData = 1189 cf.transformClass(cf.parse(moduleInfo), 1190 (builder, element) -> { 1191 builder.with(element); 1192 if (element instanceof ModuleAttribute) { 1193 builder.with(ModuleMainClassAttribute.of(ClassDesc.of("main.Main"))); 1194 } 1195 }); 1196 try (OutputStream out = Files.newOutputStream(moduleInfo)) { 1197 out.write(newClassData); 1198 } 1199 } catch (IOException ex) { 1200 throw new UncheckedIOException(ex); 1201 } 1202 }, 1203 """ 1204 module m { 1205 } 1206 """, 1207 """ 1208 package main; 1209 public class Main {} 1210 """); 1211 1212 1213 Path ctSym = output.resolve("ct.sym"); 1214 1215 deleteRecursively(ctSym); 1216 1217 CreateSymbols.ALLOW_NON_EXISTING_CLASSES = true; 1218 CreateSymbols.EXTENSION = ".class"; 1219 1220 List<VersionDescription> versions = 1221 Arrays.asList(new VersionDescription(ver9Jar.toAbsolutePath().toString(), "9", null)); 1222 1223 ExcludeIncludeList acceptAll = new ExcludeIncludeList(null, null) { 1224 @Override public boolean accepts(String className, boolean includePrivateClasses) { 1225 return true; 1226 } 1227 }; 1228 new CreateSymbols().createBaseLine(versions, acceptAll, ctSym, new String[0]); 1229 Path symbolsDesc = ctSym.resolve("symbols"); 1230 Path modules = ctSym.resolve("modules"); 1231 Path modulesList = ctSym.resolve("modules-list"); 1232 1233 Files.createDirectories(modules); 1234 try (Writer w = Files.newBufferedWriter(modulesList)) {} 1235 1236 Path classesZip = output.resolve("classes.zip"); 1237 Path classesDir = output.resolve("classes"); 1238 1239 new CreateSymbols().createSymbols(null, symbolsDesc.toAbsolutePath().toString(), classesZip.toAbsolutePath().toString(), 0, "9", "", modules.toString(), modulesList.toString()); 1240 1241 try (JarFile jf = new JarFile(classesZip.toFile())) { 1242 Enumeration<JarEntry> en = jf.entries(); 1243 1244 while (en.hasMoreElements()) { 1245 JarEntry je = en.nextElement(); 1246 if (je.isDirectory()) continue; 1247 Path target = classesDir.resolve(je.getName()); 1248 Files.createDirectories(target.getParent()); 1249 Files.copy(jf.getInputStream(je), target); 1250 } 1251 } 1252 1253 Path moduleInfo = classesDir.resolve("9") 1254 .resolve("m") 1255 .resolve("module-info.class"); 1256 1257 cf.parse(moduleInfo) 1258 .attributes() 1259 .stream() 1260 .filter(attr -> attr instanceof ModuleMainClassAttribute) 1261 .forEach(attr -> { 1262 String expectedMain = "Lmain/Main;"; 1263 String mainClass = 1264 ((ModuleMainClassAttribute) attr).mainClass() 1265 .asSymbol() 1266 .descriptorString(); 1267 if (!Objects.equals(expectedMain, mainClass)) { 1268 throw new AssertionError("Expected " + expectedMain + " as a main class, " + 1269 "but got: " + mainClass); 1270 } 1271 }); 1272 } 1273 1274 void compileAndPack(Path output, Path outputFile, String... code) throws Exception { 1275 compileAndPack(output, outputFile, _ -> {}, code); 1276 } 1277 1278 void compileAndPack(Path output, Path outputFile, 1279 Consumer<Path> adjustClassFiles, String... code) throws Exception { 1280 ToolBox tb = new ToolBox(); 1281 Path scratch = output.resolve("temp"); 1282 deleteRecursively(scratch); 1283 Files.createDirectories(scratch); 1284 System.err.println(Arrays.asList(code)); 1285 new JavacTask(tb).sources(code).options("-d", scratch.toAbsolutePath().toString()).run(Expect.SUCCESS); 1286 List<String> classFiles = collectClassFile(scratch); 1287 Path moduleInfo = scratch.resolve("module-info.class"); 1288 if (Files.exists(moduleInfo)) { 1289 Set<String> packages = new HashSet<>(); 1290 for (String cf : classFiles) { 1291 int sep = cf.lastIndexOf(scratch.getFileSystem().getSeparator()); 1292 if (sep != (-1)) { 1293 packages.add(cf.substring(0, sep)); 1294 } 1295 } 1296 ClassFile cf = ClassFile.of(); 1297 ClassModel cm = cf.parse(moduleInfo); 1298 byte[] newData = cf.transformClass(cm, (builder, element) -> { 1299 builder.with(element); 1300 if (element instanceof ModuleAttribute) { 1301 builder.with(ModulePackagesAttribute.ofNames(packages.stream() 1302 .map(pack -> PackageDesc.of(pack)) 1303 .toList())); 1304 } 1305 }); 1306 try (OutputStream out = Files.newOutputStream(moduleInfo)) { 1307 out.write(newData); 1308 } 1309 } 1310 adjustClassFiles.accept(scratch); 1311 try (Writer out = Files.newBufferedWriter(outputFile)) { 1312 for (String classFile : classFiles) { 1313 try (InputStream in = Files.newInputStream(scratch.resolve(classFile))) { 1314 int read; 1315 1316 while ((read = in.read()) != (-1)) { 1317 out.write(String.format("%02x", read)); 1318 } 1319 1320 out.write("\n"); 1321 } 1322 } 1323 } 1324 } 1325 1326 List<String> collectClassFile(Path root) throws IOException { 1327 try (Stream<Path> files = Files.walk(root)) { 1328 return files.filter(p -> Files.isRegularFile(p)) 1329 .filter(p -> p.getFileName().toString().endsWith(".class")) 1330 .map(p -> root.relativize(p).toString()) 1331 .filter(p -> !p.contains("impl")) 1332 .collect(Collectors.toList()); 1333 } 1334 } 1335 1336 void deleteRecursively(Path dir) throws IOException { 1337 Files.walkFileTree(dir, new FileVisitor<Path>() { 1338 @Override 1339 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { 1340 return FileVisitResult.CONTINUE; 1341 } 1342 @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 1343 Files.delete(file); 1344 return FileVisitResult.CONTINUE; 1345 } 1346 @Override public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { 1347 return FileVisitResult.CONTINUE; 1348 } 1349 @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { 1350 Files.delete(dir); 1351 return FileVisitResult.CONTINUE; 1352 } 1353 }); 1354 } 1355 1356 @Retention(RetentionPolicy.RUNTIME) 1357 @interface Test { 1358 } 1359 }