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 = \"&amp;&&lt;<&gt;>&apos;'\";" +
 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 = \"&amp;&&lt;<&gt;>&apos;'\";\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 }