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