1 /* 2 * Copyright (c) 2015, 2020, 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 import java.io.File; 25 import java.io.InputStream; 26 import java.io.Writer; 27 import java.lang.annotation.Retention; 28 import java.lang.annotation.RetentionPolicy; 29 import java.lang.reflect.Method; 30 import java.util.Arrays; 31 import java.util.List; 32 import com.sun.tools.javac.file.ZipFileIndexCache; 33 import java.io.IOException; 34 import java.nio.file.FileVisitResult; 35 import java.nio.file.FileVisitor; 36 import java.nio.file.Files; 37 import java.nio.file.Path; 38 import java.nio.file.Paths; 39 import java.nio.file.attribute.BasicFileAttributes; 40 import java.util.HashSet; 41 import java.util.Set; 42 import java.util.stream.Collectors; 43 import java.util.stream.Stream; 44 import org.openjdk.buildtools.symbolgenerator.CreateSymbols; 45 import org.openjdk.buildtools.symbolgenerator.CreateSymbols.ClassDescription; 46 import org.openjdk.buildtools.symbolgenerator.CreateSymbols.ClassList; 47 import org.openjdk.buildtools.symbolgenerator.CreateSymbols.CtSymKind; 48 import org.openjdk.buildtools.symbolgenerator.CreateSymbols.ExcludeIncludeList; 49 import org.openjdk.buildtools.symbolgenerator.CreateSymbols.VersionDescription; 50 51 public class CreateSymbolsTestImpl { 52 53 static final String CREATE_SYMBOLS_NAME = "symbolgenerator.CreateSymbols"; 54 55 public static void main(String... args) throws Exception { 56 new CreateSymbolsTestImpl().doTest(); 57 } 58 59 void doTest() throws Exception { 60 boolean testRun = false; 61 for (Method m : CreateSymbolsTestImpl.class.getDeclaredMethods()) { 62 if (!"testIncluded".equals(m.getName())) 63 continue; 64 if (m.isAnnotationPresent(Test.class)) { 65 m.invoke(this); 66 testRun = true; 67 } 68 } 69 if (!testRun) { 70 throw new IllegalStateException("No tests found."); 71 } 72 } 73 74 @Test 75 void testMethodRemoved() throws Exception { 76 doTest("package t; public class T { public void m() { } }", 77 "package t; public class T { }", 78 "package t; public class Test { { T t = null; t.m(); } }", 79 ToolBox.Expect.SUCCESS, 80 ToolBox.Expect.FAIL); 81 doTest("package t; public class T { public void b() { } public void m() { } public void a() { } }", 82 "package t; public class T { public void b() { } public void a() { } }", 83 "package t; public class Test { { T t = null; t.b(); t.a(); } }", 84 ToolBox.Expect.SUCCESS, 85 ToolBox.Expect.SUCCESS); 86 //with additional attribute (need to properly skip the member): 87 doTest("package t; public class T { public void m() throws IllegalStateException { } public void a() { } }", 88 "package t; public class T { public void a() { } }", 89 "package t; public class Test { { T t = null; t.a(); } }", 90 ToolBox.Expect.SUCCESS, 91 ToolBox.Expect.SUCCESS); 92 } 93 94 @Test 95 void testMethodAdded() throws Exception { 96 doTest("package t; public class T { }", 97 "package t; public class T { public void m() { } }", 98 "package t; public class Test { { T t = null; t.m(); } }", 99 ToolBox.Expect.FAIL, 100 ToolBox.Expect.SUCCESS); 101 doTest("package t; public class T { public void b() { } public void a() { } }", 102 "package t; public class T { public void b() { } public void m() { } public void a() { } }", 103 "package t; public class Test { { T t = null; t.b(); t.a(); } }", 104 ToolBox.Expect.SUCCESS, 105 ToolBox.Expect.SUCCESS); 106 } 107 108 //verify fields added/modified/removed 109 110 @Test 111 void testClassAdded() throws Exception { 112 doTest("class Dummy {}", 113 "package t; public class T { }", 114 "package t; public class Test { { T t = new T(); } }", 115 ToolBox.Expect.FAIL, 116 ToolBox.Expect.SUCCESS); 117 } 118 119 @Test 120 void testClassModified() throws Exception { 121 doTest("package t; public class T { public void m() { } }", 122 "package t; public class T implements java.io.Serializable { public void m() { } }", 123 "package t; public class Test { { java.io.Serializable t = new T(); } }", 124 ToolBox.Expect.FAIL, 125 ToolBox.Expect.SUCCESS); 126 } 127 128 @Test 129 void testClassRemoved() throws Exception { 130 doTest("package t; public class T { }", 131 "class Dummy {}", 132 "package t; public class Test { { T t = new T(); } }", 133 ToolBox.Expect.SUCCESS, 134 ToolBox.Expect.FAIL); 135 } 136 137 @Test 138 void testInnerClassAttributes() throws Exception { 139 doTest("package t; public class T { public static class Inner { } }", 140 "package t; public class T { public static class Inner { } }", 141 "package t; import t.T.Inner; public class Test { Inner i; }", 142 ToolBox.Expect.SUCCESS, 143 ToolBox.Expect.SUCCESS); 144 } 145 146 @Test 147 void testConstantAdded() throws Exception { 148 doTest("package t; public class T { }", 149 "package t; public class T { public static final int A = 0; }", 150 "package t; public class Test { void t(int i) { switch (i) { case T.A: break;} } }", 151 ToolBox.Expect.FAIL, 152 ToolBox.Expect.SUCCESS); 153 } 154 155 @Test 156 void testAnnotationAttributeDefaultvalue() throws Exception { 157 //TODO: this only verifies that there is *some* value, but we should also verify there is a specific value: 158 doTest("package t; public @interface T { }", 159 "package t;\n" + 160 "public @interface T {\n" + 161 " public boolean booleanValue() default true;\n" + 162 " public byte byteValue() default 1;\n" + 163 " public char charValue() default 2;\n" + 164 " public short shortValue() default 3;\n" + 165 " public int intValue() default 4;\n" + 166 " public long longValue() default 5;\n" + 167 " public float floatValue() default 6;\n" + 168 " public double doubleValue() default 7;\n" + 169 " public String stringValue() default \"8\";\n" + 170 " public java.lang.annotation.RetentionPolicy enumValue() default java.lang.annotation.RetentionPolicy.RUNTIME;\n" + 171 " public Class classValue() default Number.class;\n" + 172 " public int[] arrayValue() default {1, 2};\n" + 173 " public SuppressWarnings annotationValue() default @SuppressWarnings(\"cast\");\n" + 174 "}\n", 175 "package t; public @T class Test { }", 176 ToolBox.Expect.SUCCESS, 177 ToolBox.Expect.SUCCESS); 178 } 179 180 @Test 181 void testConstantTest() throws Exception { 182 //XXX: other constant types (String in particular) - see testStringConstant 183 doPrintElementTest("package t; public class T { public static final int A = 1; }", 184 "package t; public class T { public static final int A = 2; }", 185 "t.T", 186 "package t;\n\n" + 187 "public class T {\n" + 188 " public static final int A = 1;\n\n" + 189 " public T();\n" + 190 "}\n", 191 "t.T", 192 "package t;\n\n" + 193 "public class T {\n" + 194 " public static final int A = 2;\n\n" + 195 " public T();\n" + 196 "}\n"); 197 doPrintElementTest("package t; public class T { public static final boolean A = false; }", 198 "package t; public class T { public static final boolean A = true; }", 199 "t.T", 200 "package t;\n\n" + 201 "public class T {\n" + 202 " public static final boolean A = false;\n\n" + 203 " public T();\n" + 204 "}\n", 205 "t.T", 206 "package t;\n\n" + 207 "public class T {\n" + 208 " public static final boolean A = true;\n\n" + 209 " public T();\n" + 210 "}\n"); 211 } 212 213 @Test 214 void testAnnotations() throws Exception { 215 doPrintElementTest("package t;" + 216 "import java.lang.annotation.*;" + 217 "public @Visible @Invisible class T { }" + 218 "@Retention(RetentionPolicy.RUNTIME) @interface Visible { }" + 219 "@Retention(RetentionPolicy.CLASS) @interface Invisible { }", 220 "package t;" + 221 "import java.lang.annotation.*;" + 222 "public @Visible @Invisible class T { }" + 223 "@Retention(RetentionPolicy.RUNTIME) @interface Visible { }" + 224 "@Retention(RetentionPolicy.CLASS) @interface Invisible { }", 225 "t.T", 226 "package t;\n\n" + 227 "@t.Invisible\n" + 228 "@t.Visible\n" + 229 "public class T {\n\n" + 230 " public T();\n" + 231 "}\n", 232 "t.Visible", 233 "package t;\n\n" + 234 "@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)\n" + 235 "@interface Visible {\n" + 236 "}\n"); 237 doPrintElementTest("package t;" + 238 "import java.lang.annotation.*;" + 239 "import java.util.*;" + 240 "public class T {" + 241 " public void test(int h, @Invisible int i, @Visible List<String> j, int k) { }" + 242 "}" + 243 "@Retention(RetentionPolicy.RUNTIME) @interface Visible { }" + 244 "@Retention(RetentionPolicy.CLASS) @interface Invisible { }", 245 "package t;" + 246 "import java.lang.annotation.*;" + 247 "import java.util.*;" + 248 "public class T {" + 249 " public void test(int h, @Invisible int i, @Visible List<String> j, int k) { }" + 250 "}" + 251 "@Retention(RetentionPolicy.RUNTIME) @interface Visible { }" + 252 "@Retention(RetentionPolicy.CLASS) @interface Invisible { }", 253 "t.T", 254 "package t;\n\n" + 255 "public class T {\n\n" + 256 " public T();\n\n" + 257 " public void test(int arg0,\n" + 258 " @t.Invisible int arg1,\n" + 259 " @t.Visible java.util.List<java.lang.String> arg2,\n" + 260 " int arg3);\n" + 261 "}\n", 262 "t.Visible", 263 "package t;\n\n" + 264 "@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)\n" + 265 "@interface Visible {\n" + 266 "}\n"); 267 doPrintElementTest("package t;" + 268 "import java.lang.annotation.*;" + 269 "public class T {" + 270 " public void test(@Ann(v=\"url\", dv=\"\\\"\\\"\") String str) { }" + 271 "}" + 272 "@Retention(RetentionPolicy.RUNTIME) @interface Ann {" + 273 " public String v();" + 274 " public String dv();" + 275 "}", 276 "package t;" + 277 "public class T { }", 278 "t.T", 279 "package t;\n\n" + 280 "public class T {\n\n" + 281 " public T();\n\n" + 282 " public void test(@t.Ann(dv=\"\\\"\\\"\", v=\"url\") java.lang.String arg0);\n" + 283 "}\n", 284 "t.T", 285 "package t;\n\n" + 286 "public class T {\n\n" + 287 " public T();\n" + 288 "}\n"); 289 } 290 291 @Test 292 void testStringConstant() throws Exception { 293 doTest("package t; public class T { public static final String C = \"\"; }", 294 "package t; public class T { public static final String C = \"\"; }", 295 "package t; public class Test { { System.err.println(T.C); } }", 296 ToolBox.Expect.SUCCESS, 297 ToolBox.Expect.SUCCESS); 298 } 299 300 @Test 301 void testCopyProfileAnnotation() throws Exception { 302 String oldProfileAnnotation = CreateSymbols.PROFILE_ANNOTATION; 303 try { 304 CreateSymbols.PROFILE_ANNOTATION = "Lt/Ann;"; 305 doTestEquivalence("package t; public class T { public void t() {} } @interface Ann { }", 306 "package t; public @Ann class T { public void t() {} } @interface Ann { }", 307 "t.T"); 308 } finally { 309 CreateSymbols.PROFILE_ANNOTATION = oldProfileAnnotation; 310 } 311 } 312 313 @Test 314 void testParseAnnotation() throws Exception { 315 CreateSymbols.parseAnnotations("@Lsun/Proprietary+Annotation;@Ljdk/Profile+Annotation;(value=I1)", new int[1]); 316 CreateSymbols.parseAnnotations("@Ltest;(value={\"\"})", new int[1]); 317 CreateSymbols.parseAnnotations("@Ljava/beans/ConstructorProperties;(value={\"path\"})", new int[1]); 318 CreateSymbols.parseAnnotations("@Ljava/beans/ConstructorProperties;(value=I-2)", new int[1]); 319 } 320 321 @Test 322 void testStringCharLiterals() throws Exception { 323 doPrintElementTest("package t;" + 324 "public class T {" + 325 " public static final String STR = \"\\u0000\\u0001\\uffff\";" + 326 " public static final String EMPTY = \"\";" + 327 " public static final String AMP = \"&&<<>>''\";" + 328 "}", 329 "package t;" + 330 " public class T {" + 331 " public static final char c = '\\uffff';" + 332 "}", 333 "t.T", 334 "package t;\n\n" + 335 "public class T {\n" + 336 " public static final java.lang.String STR = \"\\u0000\\u0001\\uffff\";\n" + 337 " public static final java.lang.String EMPTY = \"\";\n" + 338 " public static final java.lang.String AMP = \"&&<<>>'\\'\";\n\n" + 339 " public T();\n" + 340 "}\n", 341 "t.T", 342 "package t;\n\n" + 343 "public class T {\n" + 344 " public static final char c = '\\uffff';\n\n" + 345 " public T();\n" + 346 "}\n"); 347 } 348 349 @Test 350 void testGenerification() throws Exception { 351 doTest("package t; public class T { public class TT { public Object t() { return null; } } }", 352 "package t; public class T<E> { public class TT { public E t() { return null; } } }", 353 "package t; public class Test { { T.TT tt = null; tt.t(); } }", 354 ToolBox.Expect.SUCCESS, 355 ToolBox.Expect.SUCCESS); 356 } 357 358 int i = 0; 359 360 void doTest(String code7, String code8, String testCode, ToolBox.Expect result7, ToolBox.Expect result8) throws Exception { 361 ToolBox tb = new ToolBox(); 362 Path classes = prepareVersionedCTSym(code7, code8); 363 Path output = classes.getParent(); 364 Path scratch = output.resolve("scratch"); 365 366 Files.createDirectories(scratch); 367 368 tb.new JavacTask() 369 .sources(testCode) 370 .options("-d", scratch.toAbsolutePath().toString(), "-classpath", computeClassPath(classes, "7"), "-XDuseOptimizedZip=false") 371 .run(result7) 372 .writeAll(); 373 tb.new JavacTask() 374 .sources(testCode) 375 .options("-d", scratch.toAbsolutePath().toString(), "-classpath", computeClassPath(classes, "8"), "-XDuseOptimizedZip=false") 376 .run(result8) 377 .writeAll(); 378 } 379 380 private static String computeClassPath(Path classes, String version) throws IOException { 381 try (Stream<Path> elements = Files.list(classes)) { 382 return elements.map(el -> el.toAbsolutePath().toString()) 383 .collect(Collectors.joining(File.pathSeparator)); 384 } 385 } 386 387 void doPrintElementTest(String code7, String code8, String className7, String printed7, String className8, String printed8) throws Exception { 388 ToolBox tb = new ToolBox(); 389 Path classes = prepareVersionedCTSym(code7, code8); 390 Path output = classes.getParent(); 391 Path scratch = output.resolve("scratch"); 392 393 Files.createDirectories(scratch); 394 395 String out; 396 out = tb.new JavacTask(ToolBox.Mode.CMDLINE) 397 .options("-d", scratch.toAbsolutePath().toString(), "-classpath", computeClassPath(classes, "7"), "-XDuseOptimizedZip=false", "-Xprint", className7) 398 .run(ToolBox.Expect.SUCCESS) 399 .getOutput(ToolBox.OutputKind.STDOUT); 400 if (!out.equals(printed7)) { 401 throw new AssertionError("out=" + out + "; printed7=" + printed7); 402 } 403 out = tb.new JavacTask(ToolBox.Mode.CMDLINE) 404 .options("-d", scratch.toAbsolutePath().toString(), "-classpath", computeClassPath(classes, "8"), "-XDuseOptimizedZip=false", "-Xprint", className8) 405 .run(ToolBox.Expect.SUCCESS) 406 .getOutput(ToolBox.OutputKind.STDOUT); 407 if (!out.equals(printed8)) { 408 throw new AssertionError("out=" + out + "; printed8=" + printed8); 409 } 410 } 411 412 void doTestEquivalence(String code7, String code8, String testClass) throws Exception { 413 Path classes = prepareVersionedCTSym(code7, code8); 414 Path classfile = classes.resolve("78").resolve(testClass.replace('.', '/') + ".class"); 415 416 if (!Files.isReadable(classfile)) { 417 throw new AssertionError("Cannot find expected class."); 418 } 419 } 420 421 @Test 422 void testIncluded() throws Exception { 423 doTestIncluded("package t;\n" + 424 "public class Test extends PP1<PP2> implements PP3<PP4>, PP5<PP6> {\n" + 425 " public PP7 m1(PP8 p) { return null;}\n" + 426 " public PP9<PPA> m2(PPB<PPC> p) { return null;}\n" + 427 " public PPD f1;\n" + 428 " public PPE<PPF> f2;\n" + 429 " public Test2 aux;\n" + 430 "}\n" + 431 "class Test2 extends PPG implements PPH, PPI {\n" + 432 "}\n" + 433 "class PP1<T> {}\n" + 434 "class PP2 {}\n" + 435 "interface PP3<T> {}\n" + 436 "class PP4 {}\n" + 437 "interface PP5<T> {}\n" + 438 "class PP6 {}\n" + 439 "class PP7 {}\n" + 440 "class PP8 {}\n" + 441 "class PP9<T> {}\n" + 442 "class PPA {}\n" + 443 "class PPB<T> {}\n" + 444 "class PPC {}\n" + 445 "class PPD {}\n" + 446 "class PPE<T> {}\n" + 447 "class PPF {}\n" + 448 "class PPG {}\n" + 449 "interface PPH {}\n" + 450 "interface PPI {}\n", 451 "t.Test", 452 "t.Test2", 453 "t.PP1", 454 "t.PP2", 455 "t.PP3", 456 "t.PP4", 457 "t.PP5", 458 "t.PP6", 459 "t.PP7", 460 "t.PP8", 461 "t.PP9", 462 "t.PPA", 463 "t.PPB", 464 "t.PPC", 465 "t.PPD", 466 "t.PPE", 467 "t.PPF", 468 "t.PPG", 469 "t.PPH", 470 "t.PPI"); 471 } 472 473 void doTestIncluded(String code, String... includedClasses) throws Exception { 474 boolean oldIncludeAll = includeAll; 475 try { 476 includeAll = false; 477 Path classes = prepareVersionedCTSym(code, "package other; public class Other {}"); 478 Path root = classes.resolve("7"); 479 try (Stream<Path> classFiles = Files.walk(root)) { 480 Set<String> names = classFiles.map(p -> root.relativize(p)) 481 .map(p -> p.toString()) 482 .map(n -> {System.err.println("n= " + n); return n;}) 483 .filter(n -> n.endsWith(".class")) 484 .map(n -> n.substring(0, n.lastIndexOf('.'))) 485 .map(n -> n.replace(File.separator, ".")) 486 .collect(Collectors.toSet()); 487 488 if (!names.equals(new HashSet<>(Arrays.asList(includedClasses)))) 489 throw new AssertionError("Expected classes not included: " + names); 490 } 491 } finally { 492 includeAll = oldIncludeAll; 493 } 494 } 495 496 Path prepareVersionedCTSym(String code7, String code8) throws Exception { 497 String testClasses = System.getProperty("test.classes"); 498 Path output = Paths.get(testClasses, "test-data" + i++); 499 deleteRecursively(output); 500 Files.createDirectories(output); 501 Path ver7Jar = output.resolve("7.jar"); 502 compileAndPack(output, ver7Jar, code7); 503 Path ver8Jar = output.resolve("8.jar"); 504 compileAndPack(output, ver8Jar, code8); 505 506 ZipFileIndexCache.getSharedInstance().clearCache(); 507 508 Path classes = output.resolve("classes"); 509 510 Files.createDirectories(classes); 511 512 Path ctSym = output.resolve("ct.sym"); 513 514 deleteRecursively(ctSym); 515 516 CreateSymbols.ALLOW_NON_EXISTING_CLASSES = true; 517 CreateSymbols.EXTENSION = ".class"; 518 519 testGenerate(ver7Jar, ver8Jar, ctSym, "8", classes.toAbsolutePath().toString()); 520 521 return classes; 522 } 523 524 boolean includeAll = true; 525 526 void testGenerate(Path jar7, Path jar8, Path descDest, String version, String classDest) throws IOException { 527 deleteRecursively(descDest); 528 529 List<VersionDescription> versions = 530 Arrays.asList(new VersionDescription(jar7.toAbsolutePath().toString(), "7", null), 531 new VersionDescription(jar8.toAbsolutePath().toString(), "8", "7")); 532 533 ExcludeIncludeList acceptAll = new ExcludeIncludeList(null, null) { 534 @Override public boolean accepts(String className) { 535 return true; 536 } 537 }; 538 new CreateSymbols() { 539 @Override 540 protected boolean includeEffectiveAccess(ClassList classes, ClassDescription clazz) { 541 return includeAll ? true : super.includeEffectiveAccess(classes, clazz); 542 } 543 }.createBaseLine(versions, acceptAll, descDest, null); 544 Path symbolsDesc = descDest.resolve("symbols"); 545 try (Writer symbolsFile = Files.newBufferedWriter(symbolsDesc)) { 546 symbolsFile.write("generate platforms 7:8"); 547 symbolsFile.write(System.lineSeparator()); 548 symbolsFile.write("platform version 7 files java.base-7.sym.txt"); 549 symbolsFile.write(System.lineSeparator()); 550 symbolsFile.write("platform version 8 base 7 files java.base-8.sym.txt"); 551 symbolsFile.write(System.lineSeparator()); 552 } 553 new CreateSymbols().createSymbols(symbolsDesc.toAbsolutePath().toString(), classDest, CtSymKind.JOINED_VERSIONS); 554 } 555 556 void compileAndPack(Path output, Path outputFile, String... code) throws Exception { 557 ToolBox tb = new ToolBox(); 558 Path scratch = output.resolve("temp"); 559 deleteRecursively(scratch); 560 Files.createDirectories(scratch); 561 System.err.println(Arrays.asList(code)); 562 tb.new JavacTask().sources(code).options("-d", scratch.toAbsolutePath().toString()).run(ToolBox.Expect.SUCCESS); 563 List<String> classFiles = collectClassFile(scratch); 564 try (Writer out = Files.newBufferedWriter(outputFile)) { 565 for (String classFile : classFiles) { 566 try (InputStream in = Files.newInputStream(scratch.resolve(classFile))) { 567 int read; 568 569 while ((read = in.read()) != (-1)) { 570 out.write(String.format("%02x", read)); 571 } 572 573 out.write("\n"); 574 } 575 } 576 } 577 } 578 579 List<String> collectClassFile(Path root) throws IOException { 580 try (Stream<Path> files = Files.walk(root)) { 581 return files.filter(p -> Files.isRegularFile(p)) 582 .filter(p -> p.getFileName().toString().endsWith(".class")) 583 .map(p -> root.relativize(p).toString()) 584 .collect(Collectors.toList()); 585 } 586 } 587 588 void deleteRecursively(Path dir) throws IOException { 589 Files.walkFileTree(dir, new FileVisitor<Path>() { 590 @Override 591 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { 592 return FileVisitResult.CONTINUE; 593 } 594 @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 595 Files.delete(file); 596 return FileVisitResult.CONTINUE; 597 } 598 @Override public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { 599 return FileVisitResult.CONTINUE; 600 } 601 @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { 602 Files.delete(dir); 603 return FileVisitResult.CONTINUE; 604 } 605 }); 606 } 607 608 @Retention(RetentionPolicy.RUNTIME) 609 @interface Test { 610 } 611 }