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 = \"&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 }