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 }