1/*
2 * Copyright (c) 2015, 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
24import java.io.File;
25import java.io.InputStream;
26import java.io.Writer;
27import java.lang.annotation.Retention;
28import java.lang.annotation.RetentionPolicy;
29import java.lang.reflect.Method;
30import java.util.Arrays;
31import java.util.List;
32import com.sun.tools.javac.file.ZipFileIndexCache;
33import java.io.IOException;
34import java.nio.file.FileVisitResult;
35import java.nio.file.FileVisitor;
36import java.nio.file.Files;
37import java.nio.file.Path;
38import java.nio.file.Paths;
39import java.nio.file.attribute.BasicFileAttributes;
40import java.util.HashSet;
41import java.util.Set;
42import java.util.stream.Collectors;
43import java.util.stream.Stream;
44import build.tools.symbolgenerator.CreateSymbols;
45import build.tools.symbolgenerator.CreateSymbols.ClassDescription;
46import build.tools.symbolgenerator.CreateSymbols.ClassList;
47import build.tools.symbolgenerator.CreateSymbols.CtSymKind;
48import build.tools.symbolgenerator.CreateSymbols.ExcludeIncludeList;
49import build.tools.symbolgenerator.CreateSymbols.VersionDescription;
50
51public 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 = \"&amp;&&lt;<&gt;>&apos;'\";" +
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 = \"&amp;&&lt;<&gt;>&apos;\\'\";\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}
612