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 junit/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 
  71 import org.junit.jupiter.api.Test;
  72 import toolbox.JavacTask;
  73 import toolbox.Task;
  74 import toolbox.Task.Expect;
  75 import toolbox.ToolBox;
  76 import build.tools.symbolgenerator.CreateSymbols;
  77 import build.tools.symbolgenerator.CreateSymbols.ClassDescription;
  78 import build.tools.symbolgenerator.CreateSymbols.ClassList;
  79 import build.tools.symbolgenerator.CreateSymbols.ExcludeIncludeList;
  80 import build.tools.symbolgenerator.CreateSymbols.VersionDescription;
  81 import java.io.UncheckedIOException;
  82 import java.lang.classfile.attribute.ModuleMainClassAttribute;
  83 import java.lang.constant.ClassDesc;
  84 import java.util.Objects;
  85 import java.util.function.Consumer;
  86 
  87 public class CreateSymbolsTestImpl {
  88 
  89     static final String CREATE_SYMBOLS_NAME = "symbolgenerator.CreateSymbols";
  90 
  91     @Test
  92     void testMethodRemoved() throws Exception {
  93         doTest("package t; public class T { public void m() { } }",
  94                "package t; public class T { }",
  95                "package t; public class Test { { T t = null; t.m(); } }",
  96                Expect.SUCCESS,
  97                Expect.FAIL);
  98         doTest("package t; public class T { public void b() { } public void m() { } public void a() { } }",
  99                "package t; public class T { public void b() { }                     public void a() { } }",
 100                "package t; public class Test { { T t = null; t.b(); t.a(); } }",
 101                Expect.SUCCESS,
 102                Expect.SUCCESS);
 103         //with additional attribute (need to properly skip the member):
 104         doTest("package t; public class T { public void m() throws IllegalStateException { } public void a() { } }",
 105                "package t; public class T {                                                  public void a() { } }",
 106                "package t; public class Test { { T t = null; t.a(); } }",
 107                Expect.SUCCESS,
 108                Expect.SUCCESS);
 109     }
 110 
 111     @Test
 112     void testMethodAdded() throws Exception {
 113         doTest("package t; public class T { }",
 114                "package t; public class T { public void m() { } }",
 115                "package t; public class Test { { T t = null; t.m(); } }",
 116                Expect.FAIL,
 117                Expect.SUCCESS);
 118         doTest("package t; public class T { public void b() { }                     public void a() { } }",
 119                "package t; public class T { public void b() { } public void m() { } public void a() { } }",
 120                "package t; public class Test { { T t = null; t.b(); t.a(); } }",
 121                Expect.SUCCESS,
 122                Expect.SUCCESS);
 123     }
 124 
 125     //verify fields added/modified/removed
 126 
 127     @Test
 128     void testClassAdded() throws Exception {
 129         doTest("class Dummy {}",
 130                "package t; public class T { }",
 131                "package t; public class Test { { T t = new T(); } }",
 132                Expect.FAIL,
 133                Expect.SUCCESS);
 134     }
 135 
 136     @Test
 137     void testClassModified() throws Exception {
 138         doTest("package t; public class T { public void m() { } }",
 139                "package t; public class T implements java.io.Serializable { public void m() { } }",
 140                "package t; public class Test { { java.io.Serializable t = new T(); } }",
 141                Expect.FAIL,
 142                Expect.SUCCESS);
 143     }
 144 
 145     @Test
 146     void testClassRemoved() throws Exception {
 147         doTest("package t; public class T { }",
 148                "class Dummy {}",
 149                "package t; public class Test { { T t = new T(); } }",
 150                Expect.SUCCESS,
 151                Expect.FAIL);
 152     }
 153 
 154     @Test
 155     void testInnerClassAttributes() throws Exception {
 156         doTest("package t; public class T { public static class Inner { } }",
 157                "package t; public class T { public static class Inner { } public void extra() {} }",
 158                "package t; import t.T.Inner; public class Test { Inner i; }",
 159                Expect.SUCCESS,
 160                Expect.SUCCESS);
 161     }
 162 
 163     @Test
 164     void testConstantAdded() throws Exception {
 165         doTest("package t; public class T { }",
 166                "package t; public class T { public static final int A = 0; }",
 167                "package t; public class Test { void t(int i) { switch (i) { case T.A: break;} } }",
 168                Expect.FAIL,
 169                Expect.SUCCESS);
 170     }
 171 
 172     @Test
 173     void testAnnotationAttributeDefaultvalue() throws Exception {
 174         //TODO: this only verifies that there is *some* value, but we should also verify there is a specific value:
 175         doTest("package t; public @interface T { }",
 176                "package t;\n" +
 177                "public @interface T {\n" +
 178                "    public boolean booleanValue() default true;\n" +
 179                "    public byte byteValue() default 1;\n" +
 180                "    public char charValue() default 2;\n" +
 181                "    public short shortValue() default 3;\n" +
 182                "    public int intValue() default 4;\n" +
 183                "    public long longValue() default 5;\n" +
 184                "    public float floatValue() default 6;\n" +
 185                "    public double doubleValue() default 7;\n" +
 186                "    public String stringValue() default \"8\";\n" +
 187                "    public java.lang.annotation.RetentionPolicy enumValue() default java.lang.annotation.RetentionPolicy.RUNTIME;\n" +
 188                "    public Class classValue() default Number.class;\n" +
 189                "    public int[] arrayValue() default {1, 2};\n" +
 190                "    public SuppressWarnings annotationValue() default @SuppressWarnings(\"cast\");\n" +
 191                "}\n",
 192                "package t; public @T class Test { }",
 193                Expect.SUCCESS,
 194                Expect.SUCCESS);
 195     }
 196 
 197     @Test
 198     void testConstantTest() throws Exception {
 199         //XXX: other constant types (String in particular) - see testStringConstant
 200         doPrintElementTest("package t; public class T { public static final int A = 1; }",
 201                            "package t; public class T { public static final int A = 2; }",
 202                            "t.T",
 203                            "package t;\n\n" +
 204                            "public class T {\n" +
 205                            "  public static final int A = 1;\n\n" +
 206                            "  public T();\n" +
 207                            "}\n",
 208                            "t.T",
 209                            "package t;\n\n" +
 210                            "public class T {\n" +
 211                            "  public static final int A = 2;\n\n" +
 212                            "  public T();\n" +
 213                            "}\n");
 214         doPrintElementTest("package t; public class T { public static final boolean A = false; }",
 215                            "package t; public class T { public static final boolean A = true; }",
 216                            "t.T",
 217                            "package t;\n\n" +
 218                            "public class T {\n" +
 219                            "  public static final boolean A = false;\n\n" +
 220                            "  public T();\n" +
 221                            "}\n",
 222                            "t.T",
 223                            "package t;\n\n" +
 224                            "public class T {\n" +
 225                            "  public static final boolean A = true;\n\n" +
 226                            "  public T();\n" +
 227                            "}\n");
 228     }
 229 
 230     @Test
 231     void testAnnotations() throws Exception {
 232         Set<String> extraAnnotations = Set.of("Ljava/lang/annotation/Retention;");
 233         CreateSymbols.HARDCODED_ANNOTATIONS.addAll(extraAnnotations);
 234         try {
 235             doPrintElementTest("package t;" +
 236                                "import java.lang.annotation.*;" +
 237                                "public @Visible @Invisible class T { public void extra() { } }" +
 238                                "@Retention(RetentionPolicy.RUNTIME) @interface Visible { }" +
 239                                "@Retention(RetentionPolicy.CLASS) @interface Invisible { }",
 240                                "package t;" +
 241                                "import java.lang.annotation.*;" +
 242                                "public @Visible @Invisible class T { }" +
 243                                "@Retention(RetentionPolicy.RUNTIME) @interface Visible { }" +
 244                                "@Retention(RetentionPolicy.CLASS) @interface Invisible { }",
 245                                "t.T",
 246                                "package t;\n\n" +
 247                                "@t.Invisible\n" +
 248                                "@t.Visible\n" +
 249                                "public class T {\n\n" +
 250                                "  public T();\n\n" +
 251                                "  public void extra();\n" +
 252                                "}\n",
 253                                "t.Visible",
 254                                "package t;\n\n" +
 255                                "@java.lang.annotation.Retention(RUNTIME)\n" +
 256                                "@interface Visible {\n" +
 257                                "}\n");
 258             doPrintElementTest("package t;" +
 259                                "import java.lang.annotation.*;" +
 260                                "import java.util.*;" +
 261                                "public class T {" +
 262                                "    public void test(int h, @Invisible int i, @Visible List<String> j, int k) { }" +
 263                                "}" +
 264                                "@Retention(RetentionPolicy.RUNTIME) @interface Visible { }" +
 265                                "@Retention(RetentionPolicy.CLASS) @interface Invisible { }",
 266                                "package t;" +
 267                                "import java.lang.annotation.*;" +
 268                                "import java.util.*;" +
 269                                "public class T {" +
 270                                "    public void test(int h, @Invisible int i, @Visible List<String> j, int k) { }" +
 271                                "    public void extra() { }" +
 272                                "}" +
 273                                "@Retention(RetentionPolicy.RUNTIME) @interface Visible { }" +
 274                                "@Retention(RetentionPolicy.CLASS) @interface Invisible { }",
 275                                "t.T",
 276                                "package t;\n\n" +
 277                                "public class T {\n\n" +
 278                                "  public T();\n\n" +
 279                                "  public void test(int arg0,\n" +
 280                                "    @t.Invisible int arg1,\n" +
 281                                "    @t.Visible java.util.List<java.lang.String> arg2,\n" +
 282                                "    int arg3);\n" +
 283                                "}\n",
 284                                "t.Visible",
 285                                "package t;\n\n" +
 286                                "@java.lang.annotation.Retention(RUNTIME)\n" +
 287                                "@interface Visible {\n" +
 288                                "}\n");
 289             doPrintElementTest("package t;" +
 290                                "import java.lang.annotation.*;" +
 291                                "public class T {" +
 292                                "    public void test(@Ann(v=\"url\", dv=\"\\\"\\\"\") String str) { }" +
 293                                "}" +
 294                                "@Retention(RetentionPolicy.RUNTIME) @interface Ann {" +
 295                                "    public String v();" +
 296                                "    public String dv();" +
 297                                "}",
 298                                "package t;" +
 299                                "public class T { }",
 300                                "t.T",
 301                                "package t;\n\n" +
 302                                "public class T {\n\n" +
 303                                "  public T();\n\n" +
 304                                "  public void test(@t.Ann(dv=\"\\\"\\\"\", v=\"url\") java.lang.String arg0);\n" +
 305                                "}\n",
 306                                "t.T",
 307                                "package t;\n\n" +
 308                                "public class T {\n\n" +
 309                                "  public T();\n" +
 310                                "}\n");
 311         } finally {
 312             CreateSymbols.HARDCODED_ANNOTATIONS.removeAll(extraAnnotations);
 313         }
 314     }
 315 
 316     @Test
 317     void testStringConstant() throws Exception {
 318         doTest("package t; public class T { public static final String C = \"\"; }",
 319                "package t; public class T { public static final String C = \"\"; public void extra() { } }",
 320                "package t; public class Test { { System.err.println(T.C); } }",
 321                 Expect.SUCCESS,
 322                 Expect.SUCCESS);
 323     }
 324 
 325     @Test
 326     void testCopyProfileAnnotation() throws Exception {
 327         String oldProfileAnnotation = CreateSymbols.PROFILE_ANNOTATION;
 328         try {
 329             CreateSymbols.PROFILE_ANNOTATION = "Lt/Ann;";
 330             doTestEquivalence("package t; public @Ann class T { public void t() {} } @interface Ann { }",
 331                               "package t; public class T { public void t() {} }",
 332                               "t.T");
 333         } finally {
 334             CreateSymbols.PROFILE_ANNOTATION = oldProfileAnnotation;
 335         }
 336     }
 337 
 338     @Test
 339     void testParseAnnotation() throws Exception {
 340         CreateSymbols.parseAnnotations("@Lsun/Proprietary+Annotation;@Ljdk/Profile+Annotation;(value=I1)", new int[1]);
 341         CreateSymbols.parseAnnotations("@Ltest;(value={\"\"})", new int[1]);
 342         CreateSymbols.parseAnnotations("@Ljava/beans/ConstructorProperties;(value={\"path\"})", new int[1]);
 343         CreateSymbols.parseAnnotations("@Ljava/beans/ConstructorProperties;(value=I-2)", new int[1]);
 344     }
 345 
 346     @Test
 347     void testStringCharLiterals() throws Exception {
 348         doPrintElementTest("package t;" +
 349                            "public class T {" +
 350                            "    public static final String STR = \"\\u0000\\u0001\\uffff\";" +
 351                            "    public static final String EMPTY = \"\";" +
 352                            "    public static final String AMP = \"&amp;&&lt;<&gt;>&apos;'\";" +
 353                            "}",
 354                            "package t;" +
 355                            "    public class T {" +
 356                            "    public static final char c = '\\uffff';" +
 357                            "}",
 358                            "t.T",
 359                            "package t;\n\n" +
 360                            "public class T {\n" +
 361                            "  public static final java.lang.String STR = \"\\u0000\\u0001\\uffff\";\n" +
 362                            "  public static final java.lang.String EMPTY = \"\";\n" +
 363                            "  public static final java.lang.String AMP = \"&amp;&&lt;<&gt;>&apos;'\";\n\n" +
 364                            "  public T();\n" +
 365                            "}\n",
 366                            "t.T",
 367                            "package t;\n\n" +
 368                            "public class T {\n" +
 369                            "  public static final char c = '\\uffff';\n\n" +
 370                            "  public T();\n" +
 371                            "}\n");
 372     }
 373 
 374     @Test
 375     void testGenerification() throws Exception {
 376         doTest("package t; public class T { public class TT { public Object t() { return null; } } }",
 377                "package t; public class T<E> { public class TT { public E t() { return null; } } }",
 378                "package t; public class Test { { T.TT tt = null; tt.t(); } }",
 379                Expect.SUCCESS,
 380                Expect.SUCCESS);
 381     }
 382 
 383     @Test
 384     void testClearMissingAnnotations() throws Exception {
 385         doPrintElementTest(new String[] {
 386                                """
 387                                package t;
 388                                import t.impl.HC;
 389                                import t.impl.HR;
 390                                @HC @HR public class T {
 391                                    @HC @HR public static final int i = 0;
 392                                    @HC @HR public void t() {}
 393                                }
 394                                """,
 395                                """
 396                                package t.impl;
 397                                import java.lang.annotation.*;
 398                                @Retention(RetentionPolicy.CLASS)
 399                                public @interface HC {
 400                                }
 401                                """,
 402                                """
 403                                package t.impl;
 404                                import java.lang.annotation.*;
 405                                @Retention(RetentionPolicy.RUNTIME)
 406                                public @interface HR {
 407                                }
 408                                """
 409                            },
 410                            new String[] {
 411                                """
 412                                package t;
 413                                public class T {
 414                                    public static final int i = 0;
 415                                }
 416                                """
 417                            },
 418                            "t.T",
 419                            """
 420                            package t;
 421 
 422                            public class T {
 423                              public static final int i = 0;
 424 
 425                              public T();
 426 
 427                              public void t();
 428                            }
 429                            """,
 430                            "t.T",
 431                            """
 432                            package t;
 433 
 434                            public class T {
 435                              public static final int i = 0;
 436 
 437                              public T();
 438                            }
 439                            """);
 440     }
 441 
 442     int i = 0;
 443 
 444     void doTest(String code7, String code8, String testCode, Expect result7, Expect result8) throws Exception {
 445         ToolBox tb = new ToolBox();
 446         Path classes = prepareVersionedCTSym(new String[] {code7}, new String[] {code8});
 447         Path output = classes.getParent();
 448         Path scratch = output.resolve("scratch");
 449 
 450         Files.createDirectories(scratch);
 451 
 452         new JavacTask(tb)
 453           .sources(testCode)
 454           .options("-d", scratch.toAbsolutePath().toString(), "-classpath", computeClassPath(classes, "7"))
 455           .run(result7)
 456           .writeAll();
 457         new JavacTask(tb)
 458           .sources(testCode)
 459           .options("-d", scratch.toAbsolutePath().toString(), "-classpath", computeClassPath(classes, "8"))
 460           .run(result8)
 461           .writeAll();
 462     }
 463 
 464     private static String computeClassPath(Path classes, String version) throws IOException {
 465         try (Stream<Path> elements = Files.list(classes)) {
 466             return elements.filter(el -> el.getFileName().toString().contains(version))
 467                             .map(el -> el.resolve("java.base"))
 468                             .map(el -> el.toAbsolutePath().toString())
 469                             .collect(Collectors.joining(File.pathSeparator));
 470         }
 471     }
 472 
 473     void doPrintElementTest(String code7, String code8, String className7, String printed7, String className8, String printed8) throws Exception {
 474         doPrintElementTest(new String[] {code7}, new String[] {code8}, className7, printed7, className8, printed8);
 475     }
 476 
 477     void doPrintElementTest(String[] code7, String[] code8, String className7, String printed7, String className8, String printed8) throws Exception {
 478         ToolBox tb = new ToolBox();
 479         Path classes = prepareVersionedCTSym(code7, code8);
 480         Path output = classes.getParent();
 481         Path scratch = output.resolve("scratch");
 482 
 483         Files.createDirectories(scratch);
 484 
 485         String out;
 486         out = new JavacTask(tb, Task.Mode.CMDLINE)
 487                 .options("-d", scratch.toAbsolutePath().toString(), "-classpath", computeClassPath(classes, "7"), "-Xprint", className7)
 488                 .run(Expect.SUCCESS)
 489                 .getOutput(Task.OutputKind.STDOUT)
 490                 .replaceAll("\\R", "\n");
 491         if (!out.equals(printed7)) {
 492             throw new AssertionError("out=" + out + "; printed7=" + printed7);
 493         }
 494         out = new JavacTask(tb, Task.Mode.CMDLINE)
 495                 .options("-d", scratch.toAbsolutePath().toString(), "-classpath", computeClassPath(classes, "8"), "-Xprint", className8)
 496                 .run(Expect.SUCCESS)
 497                 .getOutput(Task.OutputKind.STDOUT)
 498                 .replaceAll("\\R", "\n");
 499         if (!out.equals(printed8)) {
 500             throw new AssertionError("out=" + out + "; printed8=" + printed8);
 501         }
 502     }
 503 
 504     void doTestEquivalence(String code7, String code8, String testClass) throws Exception {
 505         Path classes = prepareVersionedCTSym(new String[] {code7}, new String[] {code8});
 506         Path classfile = classes.resolve("78").resolve("java.base").resolve(testClass.replace('.', '/') + ".class");
 507 
 508         if (!Files.isReadable(classfile)) {
 509             throw new AssertionError("Cannot find expected class.");
 510         }
 511     }
 512 
 513     @Test
 514     void testIncluded() throws Exception {
 515         doTestIncluded("package t;\n" +
 516                        "public class Test extends PP1<PP2> implements PP3<PP4>, PP5<PP6> {\n" +
 517                        "     public PP7 m1(PP8 p) { return null;}\n" +
 518                        "     public PP9<PPA> m2(PPB<PPC> p) { return null;}\n" +
 519                        "     public PPD f1;\n" +
 520                        "     public PPE<PPF> f2;\n" +
 521                        "     public Test2 aux;\n" +
 522                        "}\n" +
 523                        "class Test2 extends PPG implements PPH, PPI {\n" +
 524                        "}\n" +
 525                        "class PP1<T> {}\n" +
 526                        "class PP2 {}\n" +
 527                        "interface PP3<T> {}\n" +
 528                        "class PP4 {}\n" +
 529                        "interface PP5<T> {}\n" +
 530                        "class PP6 {}\n" +
 531                        "class PP7 {}\n" +
 532                        "class PP8 {}\n" +
 533                        "class PP9<T> {}\n" +
 534                        "class PPA {}\n" +
 535                        "class PPB<T> {}\n" +
 536                        "class PPC {}\n" +
 537                        "class PPD {}\n" +
 538                        "class PPE<T> {}\n" +
 539                        "class PPF {}\n" +
 540                        "class PPG {}\n" +
 541                        "interface PPH {}\n" +
 542                        "interface PPI {}\n",
 543                        "t.Test",
 544                        "t.Test2",
 545                        "t.PP1",
 546                        "t.PP2",
 547                        "t.PP3",
 548                        "t.PP4",
 549                        "t.PP5",
 550                        "t.PP6",
 551                        "t.PP7",
 552                        "t.PP8",
 553                        "t.PP9",
 554                        "t.PPA",
 555                        "t.PPB",
 556                        "t.PPC",
 557                        "t.PPD",
 558                        "t.PPE",
 559                        "t.PPF",
 560                        "t.PPG",
 561                        "t.PPH",
 562                        "t.PPI");
 563     }
 564 
 565     @Test
 566     void testRecords() throws Exception {
 567         doPrintElementTest("package t;" +
 568                            "public class T {" +
 569                            "    public record R(int i, java.util.List<String> l) { }" +
 570                            "}",
 571                            "package t;" +
 572                            "public class T {" +
 573                            "    public record R(@Ann int i, long j, java.util.List<String> l) { }" +
 574                            "    public @interface Ann {} " +
 575                            "}",
 576                            "t.T$R",
 577                            """
 578 
 579                            public static record R(int i, java.util.List<java.lang.String> l) {
 580 
 581                              public R(int i,
 582                                java.util.List<java.lang.String> l);
 583 
 584                              public final java.lang.String toString();
 585 
 586                              public final int hashCode();
 587 
 588                              public final boolean equals(java.lang.Object arg0);
 589 
 590                              public int i();
 591 
 592                              public java.util.List<java.lang.String> l();
 593                            }
 594                            """,
 595                            "t.T$R",
 596                            """
 597 
 598                            public static record R(@t.T.Ann int i, long j, java.util.List<java.lang.String> l) {
 599 
 600                              public final java.lang.String toString();
 601 
 602                              public final int hashCode();
 603 
 604                              public final boolean equals(java.lang.Object arg0);
 605 
 606                              public java.util.List<java.lang.String> l();
 607 
 608                              public R(@t.T.Ann int i,
 609                                long j,
 610                                java.util.List<java.lang.String> l);
 611 
 612                              @t.T.Ann
 613                              public int i();
 614 
 615                              public long j();
 616                            }
 617                            """);
 618         doPrintElementTest("package t;" +
 619                            "public record R() {" +
 620                            "}",
 621                            "package t;" +
 622                            "public record R(int i) {" +
 623                            "}",
 624                            "t.R",
 625                            """
 626                            package t;
 627                            \n\
 628                            public record R() {
 629                            \n\
 630                              public R();
 631                            \n\
 632                              public final java.lang.String toString();
 633                            \n\
 634                              public final int hashCode();
 635                            \n\
 636                              public final boolean equals(java.lang.Object arg0);
 637                            }
 638                            """,
 639                            "t.R",
 640                            """
 641                            package t;
 642                            \n\
 643                            public record R(int i) {
 644                            \n\
 645                              public final java.lang.String toString();
 646                            \n\
 647                              public final int hashCode();
 648                            \n\
 649                              public final boolean equals(java.lang.Object arg0);
 650                            \n\
 651                              public R(int i);
 652                            \n\
 653                              public int i();
 654                            }
 655                            """);
 656     }
 657 
 658     @Test
 659     void testNonExportedSuperclass() throws Exception {
 660         doTestComplex("api.Api",
 661                       """
 662                       package api;
 663 
 664                       public class Api extends nonapi.Impl.Nested.Exp {
 665 
 666                         public Api();
 667                       }
 668                       """,
 669                       """
 670                       import api.Api;
 671                       public class Test {
 672                           private void t(Api api) {
 673                             api.run();
 674                           }
 675                       }
 676                       """,
 677                       """
 678                       import api.Api;
 679                       public class Test {
 680                           private void t(Api api) {
 681                               fail
 682                           }
 683                       }
 684                       """,
 685                       """
 686                       module m {
 687                           exports api;
 688                       }
 689                       """,
 690                       """
 691                       package api;
 692                       import nonapi.Impl;
 693                       public class Api extends Impl.Nested.Exp {
 694                       }
 695                       """,
 696                       """
 697                       package api;
 698                       public @interface Ann {
 699                       }
 700                       """,
 701                       """
 702                       package nonapi;
 703                       import api.Ann;
 704                       public class Impl {
 705                           public static final String C = "";
 706                           public void test() {}
 707                           @Ann
 708                           public static class Nested {
 709                               public static class Exp extends Nested implements Runnable {
 710                                   public void run() {}
 711                                   public OtherNested get() { return null; }
 712                               }
 713                           }
 714                           public static class OtherNested {}
 715                       }
 716                       """);
 717     }
 718 
 719     void doTestComplex(String printClass,
 720                       String expected,
 721                       String depSuccess,
 722                       String depFailure,
 723                       String... code) throws Exception {
 724         ToolBox tb = new ToolBox();
 725         String testClasses = System.getProperty("test.classes");
 726         Path output = Paths.get(testClasses, "test-data" + i++);
 727         deleteRecursively(output);
 728         Files.createDirectories(output);
 729         Path ver9Jar = output.resolve("9.jar");
 730         compileAndPack(output,
 731                        ver9Jar,
 732                        code);
 733 
 734 
 735         Path ctSym = output.resolve("ct.sym");
 736 
 737         deleteRecursively(ctSym);
 738 
 739         CreateSymbols.ALLOW_NON_EXISTING_CLASSES = true;
 740         CreateSymbols.EXTENSION = ".class";
 741 
 742         deleteRecursively(ctSym);
 743 
 744         List<VersionDescription> versions =
 745                 Arrays.asList(new VersionDescription(ver9Jar.toAbsolutePath().toString(), "9", null));
 746 
 747         ExcludeIncludeList acceptAll = new ExcludeIncludeList(null, null) {
 748             @Override public boolean accepts(String className, boolean includePrivateClasses) {
 749                 return true;
 750             }
 751         };
 752         new CreateSymbols().createBaseLine(versions, acceptAll, ctSym, new String[0]);
 753         Path symbolsDesc = ctSym.resolve("symbols");
 754         Path modules = ctSym.resolve("modules");
 755         Path modulesList = ctSym.resolve("modules-list");
 756 
 757         Files.createDirectories(modules);
 758         try (Writer w = Files.newBufferedWriter(modulesList)) {}
 759 
 760         Path classesZip = output.resolve("classes.zip");
 761         Path classesDir = output.resolve("classes");
 762 
 763         new CreateSymbols().createSymbols(null, symbolsDesc.toAbsolutePath().toString(), classesZip.toAbsolutePath().toString(), 0, "9", "", modules.toString(), modulesList.toString());
 764 
 765         try (JarFile jf = new JarFile(classesZip.toFile())) {
 766             Enumeration<JarEntry> en = jf.entries();
 767 
 768             while (en.hasMoreElements()) {
 769                 JarEntry je = en.nextElement();
 770                 if (je.isDirectory()) continue;
 771                 Path target = classesDir.resolve(je.getName());
 772                 Files.createDirectories(target.getParent());
 773                 Files.copy(jf.getInputStream(je), target);
 774             }
 775         }
 776 
 777         Path classes = classesDir;
 778         Path scratch = output.resolve("scratch");
 779 
 780         Files.createDirectories(scratch);
 781 
 782         String modulePath;
 783 
 784         try (Stream<Path> elements = Files.list(classes)) {
 785             modulePath = elements.filter(el -> el.getFileName().toString().contains("9"))
 786                             .map(el -> el.resolve("m"))
 787                             .map(el -> el.toAbsolutePath().toString())
 788                             .collect(Collectors.joining(File.pathSeparator));
 789         }
 790 
 791         {
 792             String out = new JavacTask(tb, Task.Mode.CMDLINE)
 793                     .options("-d", scratch.toAbsolutePath().toString(), "--module-path", modulePath,
 794                              "--add-modules", "m",  "-Xprint", "api.Api")
 795                     .run(Expect.SUCCESS)
 796                     .getOutput(Task.OutputKind.STDOUT)
 797                     .replaceAll("\\R", "\n");
 798 
 799             if (!out.equals(expected)) {
 800                 throw new AssertionError("out=" + out + "; expected=" + expected);
 801             }
 802         }
 803 
 804         {
 805             new JavacTask(tb)
 806                     .options("-d", scratch.toAbsolutePath().toString(), "--module-path", modulePath,
 807                              "--add-modules", "m")
 808                     .sources(depSuccess)
 809                     .run(Expect.SUCCESS)
 810                     .writeAll();
 811         }
 812 
 813         {
 814             String expectedFailure = new JavacTask(tb)
 815                     .options("-d", scratch.toAbsolutePath().toString(), "--module-path", output.resolve("temp").toString(),
 816                              "--add-modules", "m", "-XDrawDiagnostics")
 817                     .sources(depFailure)
 818                     .run(Expect.FAIL)
 819                     .getOutput(Task.OutputKind.DIRECT)
 820                     .replaceAll("\\R", "\n");
 821 
 822             String out = new JavacTask(tb)
 823                     .options("-d", scratch.toAbsolutePath().toString(), "--module-path", modulePath,
 824                              "--add-modules", "m", "-XDrawDiagnostics")
 825                     .sources(depFailure)
 826                     .run(Expect.FAIL)
 827                     .getOutput(Task.OutputKind.DIRECT)
 828                     .replaceAll("\\R", "\n");
 829 
 830             if (!out.equals(expectedFailure)) {
 831                 throw new AssertionError("out=" + out + "; expected=" + expectedFailure);
 832             }
 833         }
 834     }
 835 
 836     @Test
 837     void testExtendsInternalData1() throws Exception {
 838         doTestData("""
 839                    module name m
 840                    header exports api extraModulePackages nonapi requires name\\u0020;java.base\\u0020;flags\\u0020;8000\\u0020;version\\u0020;0 flags 8000
 841 
 842                    class name api/Ann
 843                    header extends java/lang/Object implements java/lang/annotation/Annotation flags 2601
 844 
 845                    class name api/Api
 846                    header extends nonapi/Impl$Nested$Exp flags 21
 847                    innerclass innerClass nonapi/Impl$Nested outerClass nonapi/Impl innerClassName Nested flags 29
 848                    innerclass innerClass nonapi/Impl$Nested$Exp outerClass nonapi/Impl$Nested innerClassName Exp flags 29
 849                    method name <init> descriptor ()V flags 1
 850 
 851                    class name nonapi/Impl
 852                    header extends java/lang/Object nestMembers nonapi/Impl$Nested,nonapi/Impl$Nested$Exp flags 21
 853                    innerclass innerClass nonapi/Impl$Nested outerClass nonapi/Impl innerClassName Nested flags 29
 854                    innerclass innerClass nonapi/Impl$Nested$Exp outerClass nonapi/Impl$Nested innerClassName Exp flags 29
 855                    field name C descriptor Ljava/lang/String; constantValue  flags 19
 856                    method name <init> descriptor ()V flags 1
 857                    method name test descriptor ()V flags 1
 858 
 859                    class name nonapi/Impl$Nested
 860                    header extends java/lang/Object nestHost nonapi/Impl flags 21 classAnnotations @Lapi/Ann;
 861                    innerclass innerClass nonapi/Impl$Nested outerClass nonapi/Impl innerClassName Nested flags 29
 862                    innerclass innerClass nonapi/Impl$Nested$Exp outerClass nonapi/Impl$Nested innerClassName Exp flags 29
 863                    method name <init> descriptor ()V flags 1
 864 
 865                    class name nonapi/Impl$Nested$Exp
 866                    header extends nonapi/Impl$Nested implements java/lang/Runnable nestHost nonapi/Impl flags 21
 867                    innerclass innerClass nonapi/Impl$Nested outerClass nonapi/Impl innerClassName Nested flags 29
 868                    innerclass innerClass nonapi/Impl$Nested$Exp outerClass nonapi/Impl$Nested innerClassName Exp flags 29
 869                    method name <init> descriptor ()V flags 1
 870                    method name run descriptor ()V flags 1
 871                    method name get descriptor ()Lnonapi/Impl$OtherNested; flags 1
 872 
 873                    """,
 874                    """
 875                    module m {
 876                        exports api;
 877                        exports nonapi to java.base;
 878                    }
 879                    """,
 880                    """
 881                    package api;
 882                    import nonapi.Impl;
 883                    public class Api extends Impl.Nested.Exp {
 884                    }
 885                    """,
 886                    """
 887                    package api;
 888                    public @interface Ann {
 889                    }
 890                    """,
 891                    """
 892                    package nonapi;
 893                    import api.Ann;
 894                    public class Impl {
 895                        public static final String C = "";
 896                        public void test() {}
 897                        @Ann
 898                        public static class Nested {
 899                            public static class Exp extends Nested implements Runnable {
 900                                public void run() {}
 901                                public OtherNested get() { return null; }
 902                            }
 903                        }
 904                        public static class OtherNested {}
 905                    }
 906                    """);
 907     }
 908 
 909     @Test
 910     void testTypeAnnotations() throws Exception {
 911         doPrintElementTest("""
 912                            package t;
 913                            public class T {
 914                            }
 915                            """,
 916                            """
 917                            package t;
 918                            import java.lang.annotation.*;
 919                            import java.util.*;
 920                            public class T<@AnnInvisible @AnnVisible E extends @AnnInvisible @AnnVisible ArrayList<@AnnInvisible @AnnVisible ArrayList>> extends @AnnInvisible @AnnVisible ArrayList {
 921                                public @AnnInvisible @AnnVisible List<@AnnInvisible @AnnVisible E> field;
 922                                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 {
 923                                    return null;
 924                                }
 925                            }
 926                            @Retention(RetentionPolicy.RUNTIME)
 927                            @Target(ElementType.TYPE_USE)
 928                            @interface AnnVisible {
 929                            }
 930                            @Retention(RetentionPolicy.CLASS)
 931                            @Target(ElementType.TYPE_USE)
 932                            @interface AnnInvisible {
 933                            }
 934                            """,
 935                            "t.T",
 936                            """
 937                            package t;
 938 
 939                            public class T {
 940 
 941                              public T();
 942                            }
 943                            """,
 944                            "t.T",
 945                            """
 946                            package t;
 947 
 948                            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 {
 949                              public java.util.@t.AnnInvisible @t.AnnVisible List<@t.AnnInvisible @t.AnnVisible E> field;
 950 
 951                              public T();
 952 
 953                              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,
 954                                java.util.@t.AnnInvisible @t.AnnVisible List<@t.AnnInvisible @t.AnnVisible E> arg1) throws java.lang.@t.AnnInvisible @t.AnnVisible IllegalStateException,\s
 955                                java.lang.@t.AnnInvisible @t.AnnVisible IllegalArgumentException;
 956                            }
 957                            """);
 958     }
 959 
 960     @Test
 961     void testParameterAnnotations() throws Exception {
 962         doPrintElementTest("""
 963                            package t;
 964                            public class T {
 965                                public void test(int p1, int p2) {
 966                                }
 967                            }
 968                            """,
 969                            """
 970                            package t;
 971                            import java.lang.annotation.*;
 972                            import java.util.*;
 973                            public class T {
 974                                public void test(@AnnVisible int p1, @AnnInvisible int p2) {
 975                                }
 976                            }
 977                            @Retention(RetentionPolicy.RUNTIME)
 978                            @Target(ElementType.PARAMETER)
 979                            @interface AnnVisible {
 980                            }
 981                            @Retention(RetentionPolicy.CLASS)
 982                            @Target(ElementType.PARAMETER)
 983                            @interface AnnInvisible {
 984                            }
 985                            """,
 986                            "t.T",
 987                            """
 988                            package t;
 989 
 990                            public class T {
 991 
 992                              public T();
 993 
 994                              public void test(int arg0,
 995                                int arg1);
 996                            }
 997                            """,
 998                            "t.T",
 999                            """
1000                            package t;
1001 
1002                            public class T {
1003 
1004                              public T();
1005 
1006                              public void test(@t.AnnVisible int arg0,
1007                                @t.AnnInvisible int arg1);
1008                            }
1009                            """);
1010     }
1011 
1012     void doTestData(String data,
1013                           String... code) throws Exception {
1014         String testClasses = System.getProperty("test.classes");
1015         Path output = Paths.get(testClasses, "test-data" + i++);
1016         deleteRecursively(output);
1017         Files.createDirectories(output);
1018         Path ver9Jar = output.resolve("9.jar");
1019         compileAndPack(output,
1020                        ver9Jar,
1021                        code);
1022 
1023         Path ctSym = output.resolve("ct.sym");
1024 
1025         deleteRecursively(ctSym);
1026 
1027         CreateSymbols.ALLOW_NON_EXISTING_CLASSES = true;
1028         CreateSymbols.DO_NOT_MODIFY = "";
1029         CreateSymbols.EXTENSION = ".class";
1030         CreateSymbols.INJECTED_VERSION = "0";
1031 
1032         deleteRecursively(ctSym);
1033 
1034         List<VersionDescription> versions =
1035                 Arrays.asList(new VersionDescription(ver9Jar.toAbsolutePath().toString(), "9", null));
1036 
1037         ExcludeIncludeList acceptAll = new ExcludeIncludeList(null, null) {
1038             @Override public boolean accepts(String className, boolean includePrivateClasses) {
1039                 return true;
1040             }
1041         };
1042         new CreateSymbols().createBaseLine(versions, acceptAll, ctSym, new String[0]);
1043 
1044         Path symFile = null;
1045 
1046         try (DirectoryStream<Path> ds = Files.newDirectoryStream(ctSym)) {
1047             for (Path p : ds) {
1048                 if (p.toString().endsWith(".sym.txt")) {
1049                     if (symFile != null) {
1050                         throw new IllegalStateException("Multiple sym files!");
1051                     } else {
1052                         symFile = p;
1053                     }
1054                 }
1055             }
1056         }
1057         String acutalContent = new String(Files.readAllBytes(symFile), StandardCharsets.UTF_8);
1058         if (!acutalContent.equals(data)) {
1059             throw new AssertionError("out=" + acutalContent + "; expected=" + data);
1060         }
1061     }
1062 
1063     void doTestIncluded(String code, String... includedClasses) throws Exception {
1064         boolean oldIncludeAll = includeAll;
1065         try {
1066             includeAll = false;
1067             Path classes = prepareVersionedCTSym(new String[] {code}, new String[] {"package other; public class Other {}"});
1068             Path root = classes.resolve("7").resolve("java.base");
1069             try (Stream<Path> classFiles = Files.walk(root)) {
1070                 Set<String> names = classFiles.map(p -> root.relativize(p))
1071                                               .map(p -> p.toString())
1072                                               .map(n -> {System.err.println("n= " + n); return n;})
1073                                               .filter(n -> n.endsWith(".class"))
1074                                               .map(n -> n.substring(0, n.lastIndexOf('.')))
1075                                               .map(n -> n.replace(File.separator, "."))
1076                                               .collect(Collectors.toSet());
1077 
1078                 if (!names.equals(new HashSet<>(Arrays.asList(includedClasses))))
1079                     throw new AssertionError("Expected classes not included: " + names);
1080             }
1081         } finally {
1082             includeAll = oldIncludeAll;
1083         }
1084     }
1085 
1086     Path prepareVersionedCTSym(String[] code7, String[] code8) throws Exception {
1087         return prepareVersionedCTSym(code7, code8, _ -> {});
1088     }
1089 
1090     Path prepareVersionedCTSym(String[] code7, String[] code8,
1091                                Consumer<Path> adjustClassFiles) throws Exception {
1092         String testClasses = System.getProperty("test.classes");
1093         Path output = Paths.get(testClasses, "test-data" + i++);
1094         deleteRecursively(output);
1095         Files.createDirectories(output);
1096         Path ver7Jar = output.resolve("7.jar");
1097         compileAndPack(output, ver7Jar, adjustClassFiles, code7);
1098         Path ver8Jar = output.resolve("8.jar");
1099         compileAndPack(output, ver8Jar, adjustClassFiles, code8);
1100 
1101         Path classes = output.resolve("classes.zip");
1102 
1103         Path ctSym = output.resolve("ct.sym");
1104 
1105         deleteRecursively(ctSym);
1106 
1107         CreateSymbols.ALLOW_NON_EXISTING_CLASSES = true;
1108         CreateSymbols.EXTENSION = ".class";
1109 
1110         testGenerate(ver7Jar, ver8Jar, ctSym, "8", classes.toAbsolutePath().toString());
1111 
1112         Path classesDir = output.resolve("classes");
1113 
1114         try (JarFile jf = new JarFile(classes.toFile())) {
1115             Enumeration<JarEntry> en = jf.entries();
1116 
1117             while (en.hasMoreElements()) {
1118                 JarEntry je = en.nextElement();
1119                 if (je.isDirectory()) continue;
1120                 Path target = classesDir.resolve(je.getName());
1121                 Files.createDirectories(target.getParent());
1122                 Files.copy(jf.getInputStream(je), target);
1123             }
1124         }
1125 
1126         return classesDir;
1127     }
1128 
1129     boolean includeAll = true;
1130 
1131     void testGenerate(Path jar7, Path jar8, Path descDest, String version, String classDest) throws IOException {
1132         deleteRecursively(descDest);
1133 
1134         List<VersionDescription> versions =
1135                 Arrays.asList(new VersionDescription(jar7.toAbsolutePath().toString(), "7", null),
1136                               new VersionDescription(jar8.toAbsolutePath().toString(), "8", "7"));
1137 
1138         ExcludeIncludeList acceptAll = new ExcludeIncludeList(null, null) {
1139             @Override public boolean accepts(String className, boolean includePrivateClasses) {
1140                 return true;
1141             }
1142         };
1143         new CreateSymbols() {
1144             @Override
1145             protected boolean includeEffectiveAccess(ClassList classes, ClassDescription clazz) {
1146                 return includeAll ? true : super.includeEffectiveAccess(classes, clazz);
1147             }
1148         }.createBaseLine(versions, acceptAll, descDest, new String[0]);
1149         Path symbolsDesc = descDest.resolve("symbols");
1150         Path modules = descDest.resolve("modules");
1151         Path modulesList = descDest.resolve("modules-list");
1152 
1153         Files.createDirectories(modules);
1154         try (Writer w = Files.newBufferedWriter(modulesList)) {}
1155 
1156         new CreateSymbols().createSymbols(null, symbolsDesc.toAbsolutePath().toString(), classDest, 0, "8", "", modules.toString(), modulesList.toString());
1157     }
1158 
1159     @Test
1160     void testModuleMainClass() throws Exception {
1161         ClassFile cf = ClassFile.of();
1162         ToolBox tb = new ToolBox();
1163         String testClasses = System.getProperty("test.classes");
1164         Path output = Paths.get(testClasses, "test-data" + i++);
1165         deleteRecursively(output);
1166         Files.createDirectories(output);
1167         Path ver9Jar = output.resolve("9.jar");
1168         compileAndPack(output,
1169                        ver9Jar,
1170                        classesDir -> {
1171                            try {
1172                                Path moduleInfo = classesDir.resolve("module-info.class");
1173                                byte[] newClassData =
1174                                        cf.transformClass(cf.parse(moduleInfo),
1175                                                     (builder, element) -> {
1176                                                         builder.with(element);
1177                                                         if (element instanceof ModuleAttribute) {
1178                                                             builder.with(ModuleMainClassAttribute.of(ClassDesc.of("main.Main")));
1179                                                         }
1180                                                     });
1181                                try (OutputStream out = Files.newOutputStream(moduleInfo)) {
1182                                    out.write(newClassData);
1183                                }
1184                            } catch (IOException ex) {
1185                                throw new UncheckedIOException(ex);
1186                            }
1187                        },
1188                        """
1189                        module m {
1190                        }
1191                        """,
1192                        """
1193                        package main;
1194                        public class Main {}
1195                        """);
1196 
1197 
1198         Path ctSym = output.resolve("ct.sym");
1199 
1200         deleteRecursively(ctSym);
1201 
1202         CreateSymbols.ALLOW_NON_EXISTING_CLASSES = true;
1203         CreateSymbols.EXTENSION = ".class";
1204 
1205         List<VersionDescription> versions =
1206                 Arrays.asList(new VersionDescription(ver9Jar.toAbsolutePath().toString(), "9", null));
1207 
1208         ExcludeIncludeList acceptAll = new ExcludeIncludeList(null, null) {
1209             @Override public boolean accepts(String className, boolean includePrivateClasses) {
1210                 return true;
1211             }
1212         };
1213         new CreateSymbols().createBaseLine(versions, acceptAll, ctSym, new String[0]);
1214         Path symbolsDesc = ctSym.resolve("symbols");
1215         Path modules = ctSym.resolve("modules");
1216         Path modulesList = ctSym.resolve("modules-list");
1217 
1218         Files.createDirectories(modules);
1219         try (Writer w = Files.newBufferedWriter(modulesList)) {}
1220 
1221         Path classesZip = output.resolve("classes.zip");
1222         Path classesDir = output.resolve("classes");
1223 
1224         new CreateSymbols().createSymbols(null, symbolsDesc.toAbsolutePath().toString(), classesZip.toAbsolutePath().toString(), 0, "9", "", modules.toString(), modulesList.toString());
1225 
1226         try (JarFile jf = new JarFile(classesZip.toFile())) {
1227             Enumeration<JarEntry> en = jf.entries();
1228 
1229             while (en.hasMoreElements()) {
1230                 JarEntry je = en.nextElement();
1231                 if (je.isDirectory()) continue;
1232                 Path target = classesDir.resolve(je.getName());
1233                 Files.createDirectories(target.getParent());
1234                 Files.copy(jf.getInputStream(je), target);
1235             }
1236         }
1237 
1238         Path moduleInfo = classesDir.resolve("9")
1239                                     .resolve("m")
1240                                     .resolve("module-info.class");
1241 
1242         cf.parse(moduleInfo)
1243           .attributes()
1244           .stream()
1245           .filter(attr -> attr instanceof ModuleMainClassAttribute)
1246           .forEach(attr -> {
1247               String expectedMain = "Lmain/Main;";
1248               String mainClass =
1249                       ((ModuleMainClassAttribute) attr).mainClass()
1250                                                        .asSymbol()
1251                                                        .descriptorString();
1252               if (!Objects.equals(expectedMain, mainClass)) {
1253                   throw new AssertionError("Expected " + expectedMain + " as a main class, " +
1254                                            "but got: " + mainClass);
1255               }
1256           });
1257     }
1258 
1259     void compileAndPack(Path output, Path outputFile, String... code) throws Exception {
1260         compileAndPack(output, outputFile, _ -> {}, code);
1261     }
1262 
1263     void compileAndPack(Path output, Path outputFile,
1264                         Consumer<Path> adjustClassFiles, String... code) throws Exception {
1265         ToolBox tb = new ToolBox();
1266         Path scratch = output.resolve("temp");
1267         deleteRecursively(scratch);
1268         Files.createDirectories(scratch);
1269         System.err.println(Arrays.asList(code));
1270         new JavacTask(tb).sources(code).options("-d", scratch.toAbsolutePath().toString()).run(Expect.SUCCESS);
1271         List<String> classFiles = collectClassFile(scratch);
1272         Path moduleInfo = scratch.resolve("module-info.class");
1273         if (Files.exists(moduleInfo)) {
1274             Set<String> packages = new HashSet<>();
1275             for (String cf : classFiles) {
1276                 int sep = cf.lastIndexOf(scratch.getFileSystem().getSeparator());
1277                 if (sep != (-1)) {
1278                     packages.add(cf.substring(0, sep));
1279                 }
1280             }
1281             ClassFile cf = ClassFile.of();
1282             ClassModel cm = cf.parse(moduleInfo);
1283             byte[] newData = cf.transformClass(cm, (builder, element) -> {
1284                 builder.with(element);
1285                 if (element instanceof ModuleAttribute) {
1286                     builder.with(ModulePackagesAttribute.ofNames(packages.stream()
1287                                                                          .map(pack -> PackageDesc.of(pack))
1288                                                                          .toList()));
1289                 }
1290             });
1291             try (OutputStream out = Files.newOutputStream(moduleInfo)) {
1292                 out.write(newData);
1293             }
1294         }
1295         adjustClassFiles.accept(scratch);
1296         try (Writer out = Files.newBufferedWriter(outputFile)) {
1297             for (String classFile : classFiles) {
1298                 try (InputStream in = Files.newInputStream(scratch.resolve(classFile))) {
1299                     int read;
1300 
1301                     while ((read = in.read()) != (-1)) {
1302                         out.write(String.format("%02x", read));
1303                     }
1304 
1305                     out.write("\n");
1306                 }
1307             }
1308         }
1309     }
1310 
1311     List<String> collectClassFile(Path root) throws IOException {
1312         try (Stream<Path> files = Files.walk(root)) {
1313             return files.filter(p -> Files.isRegularFile(p))
1314                         .filter(p -> p.getFileName().toString().endsWith(".class"))
1315                         .map(p -> root.relativize(p).toString())
1316                         .filter(p -> !p.contains("impl"))
1317                         .collect(Collectors.toList());
1318         }
1319     }
1320 
1321     void deleteRecursively(Path dir) throws IOException {
1322         Files.walkFileTree(dir, new FileVisitor<Path>() {
1323             @Override
1324             public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
1325                 return FileVisitResult.CONTINUE;
1326             }
1327             @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
1328                 Files.delete(file);
1329                 return FileVisitResult.CONTINUE;
1330             }
1331             @Override public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
1332                 return FileVisitResult.CONTINUE;
1333             }
1334             @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
1335                 Files.delete(dir);
1336                 return FileVisitResult.CONTINUE;
1337             }
1338         });
1339     }
1340 }