/*
 * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/*
 * @test 8247352 8293348 8349512
 * @summary test different configurations of sealed classes, same compilation unit, diff pkg or mdl, etc
 * @library /tools/lib
 * @modules jdk.compiler/com.sun.tools.javac.api
 *          jdk.compiler/com.sun.tools.javac.main
 *          jdk.compiler/com.sun.tools.javac.util
 *          jdk.compiler/com.sun.tools.javac.code
 * @build toolbox.ToolBox toolbox.JavacTask
 * @run main SealedDiffConfigurationsTest
 */

import java.util.*;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.IntStream;

import java.lang.classfile.*;
import java.lang.classfile.attribute.PermittedSubclassesAttribute;
import java.lang.classfile.constantpool.ClassEntry;
import java.lang.classfile.constantpool.ConstantPoolException;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.util.Assert;
import toolbox.TestRunner;
import toolbox.ToolBox;
import toolbox.JavacTask;
import toolbox.Task;
import toolbox.Task.OutputKind;

public class SealedDiffConfigurationsTest extends TestRunner {
    ToolBox tb;

    SealedDiffConfigurationsTest() {
        super(System.err);
        tb = new ToolBox();
    }

    protected void runTests() throws Exception {
        runTests(m -> new Object[] { Paths.get(m.getName()) });
    }

    public static void main(String... args) throws Exception {
        SealedDiffConfigurationsTest t = new SealedDiffConfigurationsTest();
        t.runTests();
    }

    Path[] findJavaFiles(Path... paths) throws IOException {
        return tb.findJavaFiles(paths);
    }

    @Test
    public void testSameCompilationUnitPos(Path base) throws Exception {
        Path src = base.resolve("src");
        Path test = src.resolve("Test");

        tb.writeJavaFiles(test,
                          "class Test {\n" +
                           "    sealed class Sealed permits Sub1, Sub2 {}\n" +
                           "    final class Sub1 extends Sealed {}\n" +
                           "    final class Sub2 extends Sealed {}\n" +
                           "}");

        Path out = base.resolve("out");

        Files.createDirectories(out);

        new JavacTask(tb)
                .outdir(out)
                .files(findJavaFiles(test))
                .run()
                .writeAll();

        checkSealedClassFile(out, "Test$Sealed.class", List.of("Test$Sub1", "Test$Sub2"));
        checkSubtypeClassFile(out, "Test$Sub1.class", "Test$Sealed", true);
        checkSubtypeClassFile(out, "Test$Sub2.class", "Test$Sealed", true);
    }

    @Test
    public void testSameCompilationUnitPos2(Path base) throws Exception {
        Path src = base.resolve("src");
        Path test = src.resolve("Test");

        tb.writeJavaFiles(test,
                "class Test {\n" +
                        "    sealed class Sealed {}\n" +
                        "    final class Sub1 extends Sealed {}\n" +
                        "    final class Sub2 extends Sealed {}\n" +
                        "}");

        Path out = base.resolve("out");

        Files.createDirectories(out);

        new JavacTask(tb)
                .outdir(out)
                .files(findJavaFiles(test))
                .run()
                .writeAll();

        checkSealedClassFile(out, "Test$Sealed.class", List.of("Test$Sub1", "Test$Sub2"));
        checkSubtypeClassFile(out, "Test$Sub1.class", "Test$Sealed", true);
        checkSubtypeClassFile(out, "Test$Sub2.class", "Test$Sealed", true);
    }

    private void checkSealedClassFile(Path out, String cfName, List<String> expectedSubTypeNames) throws ConstantPoolException, Exception {
        ClassModel sealedCF = ClassFile.of().parse(out.resolve(cfName));
        Assert.check((sealedCF.flags().flagsMask() & ClassFile.ACC_FINAL) == 0, String.format("class at file %s must not be final", cfName));
        PermittedSubclassesAttribute permittedSubclasses = sealedCF.findAttribute(Attributes.permittedSubclasses()).orElseThrow();
        Assert.check(permittedSubclasses.permittedSubclasses().size() == expectedSubTypeNames.size(),
                String.format("%s != %s",
                        permittedSubclasses.permittedSubclasses(),
                        expectedSubTypeNames));
        List<String> subtypeNames = new ArrayList<>();
        permittedSubclasses.permittedSubclasses().forEach(i -> {
            try {
                subtypeNames.add(i.name().stringValue());
            } catch (ConstantPoolException ex) {
            }
        });
        for (int i = 0; i < expectedSubTypeNames.size(); i++) {
            Assert.check(expectedSubTypeNames.get(0).equals(subtypeNames.get(0)));
        }
    }

    private void checkSubtypeClassFile(Path out, String cfName, String superClassName, boolean shouldBeFinal) throws Exception {
        ClassModel subCF1 = ClassFile.of().parse(out.resolve(cfName));
        if (shouldBeFinal) {
            Assert.check((subCF1.flags().flagsMask() & ClassFile.ACC_FINAL) != 0, String.format("class at file %s must be final", cfName));
        }
        Assert.checkNull(subCF1.findAttribute(Attributes.permittedSubclasses()).orElse(null));
        Assert.check(subCF1.superclass().orElseThrow().name().equalsString(superClassName));
    }

    @Test
    public void testSamePackagePos(Path base) throws Exception {
        Path src = base.resolve("src");
        Path pkg = src.resolve("pkg");
        Path sealed = pkg.resolve("Sealed");
        Path sub1 = pkg.resolve("Sub1");
        Path sub2 = pkg.resolve("Sub2");

        tb.writeJavaFiles(sealed,
                          "package pkg;\n" +
                          "\n" +
                          "sealed class Sealed permits Sub1, Sub2 {\n" +
                          "}");
        tb.writeJavaFiles(sub1,
                          "package pkg;\n" +
                          "\n" +
                          "final class Sub1 extends Sealed {\n" +
                          "}");
        tb.writeJavaFiles(sub2,
                          "package pkg;\n" +
                          "\n" +
                          "final class Sub2 extends Sealed {\n" +
                          "}");

        Path out = base.resolve("out");

        Files.createDirectories(out);

        new JavacTask(tb)
                .outdir(out)
                .files(findJavaFiles(pkg))
                .run()
                .writeAll();

        checkSealedClassFile(out.resolve("pkg"), "Sealed.class", List.of("pkg/Sub1", "pkg/Sub1"));
        checkSubtypeClassFile(out.resolve("pkg"), "Sub1.class", "pkg/Sealed", true);
        checkSubtypeClassFile(out.resolve("pkg"), "Sub2.class", "pkg/Sealed", true);
    }

    @Test
    public void testSameCompilationUnitNeg(Path base) throws Exception {
        Path src = base.resolve("src");
        Path test = src.resolve("Test");

        tb.writeJavaFiles(test,
                          "class Test {\n" +
                           "    sealed class Sealed permits Sub1 {}\n" +
                           "    final class Sub1 extends Sealed {}\n" +
                           "    class Sub2 extends Sealed {}\n" +
                           "}");

        List<String> error = new JavacTask(tb)
                .options("-XDrawDiagnostics")
                .files(findJavaFiles(test))
                .run(Task.Expect.FAIL)
                .writeAll()
                .getOutputLines(OutputKind.DIRECT);

        List<String> expected = List.of(
                "Test.java:4:5: compiler.err.cant.inherit.from.sealed: Test.Sealed",
                "1 error");
        if (!error.containsAll(expected)) {
            throw new AssertionError("Expected output not found. Expected: " + expected);
        }
    }

    @Test
    public void testSameCompilationUnitNeg2(Path base) throws Exception {
        Path src = base.resolve("src");
        Path test = src.resolve("Test");

        tb.writeJavaFiles(test,
                "class Test {\n" +
                        "    sealed class Sealed permits Sub1 {}\n" +
                        "    class Sub1 extends Sealed {}\n" +
                        "}");

        List<String> error = new JavacTask(tb)
                .options("-XDrawDiagnostics")
                .files(findJavaFiles(test))
                .run(Task.Expect.FAIL)
                .writeAll()
                .getOutputLines(OutputKind.DIRECT);

        List<String> expected = List.of(
                "Test.java:3:5: compiler.err.non.sealed.sealed.or.final.expected",
                "1 error");
        if (!error.containsAll(expected)) {
            throw new AssertionError("Expected output not found. Expected: " + expected);
        }
    }

    @Test
    public void testSamePackageNeg(Path base) throws Exception {
        Path src = base.resolve("src");
        Path pkg = src.resolve("pkg");
        Path sealed = pkg.resolve("Sealed");
        Path sub1 = pkg.resolve("Sub1");
        Path sub2 = pkg.resolve("Sub2");

        tb.writeJavaFiles(sealed,
                          "package pkg;\n" +
                          "\n" +
                          "sealed class Sealed permits Sub1 {\n" +
                          "}");
        tb.writeJavaFiles(sub1,
                          "package pkg;\n" +
                          "\n" +
                          "final class Sub1 extends Sealed {\n" +
                          "}");
        tb.writeJavaFiles(sub2,
                          "package pkg;\n" +
                          "\n" +
                          "class Sub2 extends Sealed {\n" +
                          "}");

        List<String> error = new JavacTask(tb)
                .options("-XDrawDiagnostics")
                .files(findJavaFiles(pkg))
                .run(Task.Expect.FAIL)
                .writeAll()
                .getOutputLines(OutputKind.DIRECT);

        List<String> expected = List.of(
                "Sub2.java:3:1: compiler.err.cant.inherit.from.sealed: pkg.Sealed",
                "1 error"
                );
        if (!error.containsAll(expected)) {
            throw new AssertionError("Expected output not found. Expected: " + expected);
        }
    }

    @Test
    public void testSamePackageNeg2(Path base) throws Exception {
        Path src = base.resolve("src");
        Path pkg = src.resolve("pkg");
        Path sealed = pkg.resolve("Sealed");
        Path sub1 = pkg.resolve("Sub1");

        tb.writeJavaFiles(sealed,
                "package pkg;\n" +
                        "\n" +
                        "final class Sealed {\n" +
                        "}");
        tb.writeJavaFiles(sub1,
                "package pkg;\n" +
                        "\n" +
                        "class Sub1 extends Sealed {\n" +
                        "}");

        List<String> error = new JavacTask(tb)
                .options("-XDrawDiagnostics")
                .files(findJavaFiles(pkg))
                .run(Task.Expect.FAIL)
                .writeAll()
                .getOutputLines(OutputKind.DIRECT);

        List<String> expected = List.of(
                "Sub1.java:3:20: compiler.err.cant.inherit.from.final: pkg.Sealed",
                "1 error");
        if (!error.containsAll(expected)) {
            throw new AssertionError("Expected output not found. Expected: " + expected);
        }
    }

    @Test
    public void testSamePackageNeg3(Path base) throws Exception {
        Path src = base.resolve("src");
        Path pkg = src.resolve("pkg");
        Path sealed = pkg.resolve("Sealed");
        Path sub1 = pkg.resolve("Sub1");

        tb.writeJavaFiles(sealed,
                "package pkg;\n" +
                        "\n" +
                        "sealed class Sealed permits Sub1{\n" +
                        "}");
        tb.writeJavaFiles(sub1,
                "package pkg;\n" +
                        "\n" +
                        "class Sub1 extends Sealed {\n" +
                        "}");

        List<String> error = new JavacTask(tb)
                .options("-XDrawDiagnostics")
                .files(findJavaFiles(pkg))
                .run(Task.Expect.FAIL)
                .writeAll()
                .getOutputLines(OutputKind.DIRECT);

        List<String> expected = List.of(
                "Sub1.java:3:1: compiler.err.non.sealed.sealed.or.final.expected",
                "1 error");
        if (!error.containsAll(expected)) {
            throw new AssertionError("Expected output not found. Expected: " + expected);
        }
    }

    @Test
    public void testDiffPackageNeg(Path base) throws Exception {
        Path src = base.resolve("src");
        Path pkg1 = src.resolve("pkg1");
        Path pkg2 = src.resolve("pkg2");
        Path sealed = pkg1.resolve("Sealed");
        Path sub1 = pkg2.resolve("Sub1");
        Path sub2 = pkg2.resolve("Sub2");

        tb.writeJavaFiles(sealed,
                "package pkg1;\n" +
                        "import pkg2.*;\n" +
                        "public sealed class Sealed permits pkg2.Sub1 {\n" +
                        "}");
        tb.writeJavaFiles(sub1,
                "package pkg2;\n" +
                        "import pkg1.*;\n" +
                        "public final class Sub1 extends pkg1.Sealed {\n" +
                        "}");

        List<String> error = new JavacTask(tb)
                .options("-XDrawDiagnostics")
                .files(findJavaFiles(pkg1, pkg2))
                .run(Task.Expect.FAIL)
                .writeAll()
                .getOutputLines(OutputKind.DIRECT);

        List<String> expected = List.of(
                "Sealed.java:3:40: compiler.err.class.in.unnamed.module.cant.extend.sealed.in.diff.package: pkg1.Sealed",
                "1 error");
        if (!error.containsAll(expected)) {
            throw new AssertionError("Expected output not found. Expected: " + expected);
        }
    }

    @Test
    public void testDiffPackageNeg2(Path base) throws Exception {
        // test that the compiler rejects a subtype that is not accessible to the sealed class
        Path src = base.resolve("src");
        Path pkg1 = src.resolve("pkg1");
        Path pkg2 = src.resolve("pkg2");
        Path sealed = pkg1.resolve("Sealed");
        Path sub1 = pkg2.resolve("Sub1");
        Path sub2 = pkg2.resolve("Sub2");

        tb.writeJavaFiles(sealed,
                "package pkg1;\n" +
                        "import pkg2.*;\n" +
                        "public sealed class Sealed permits pkg2.Sub1 {\n" +
                        "}");
        tb.writeJavaFiles(sub1,
                "package pkg2;\n" +
                        "import pkg1.*;\n" +
                        "final class Sub1 extends pkg1.Sealed {\n" +
                        "}");

        List<String> error = new JavacTask(tb)
                .options("-XDrawDiagnostics")
                .files(findJavaFiles(pkg1, pkg2))
                .run(Task.Expect.FAIL)
                .writeAll()
                .getOutputLines(OutputKind.DIRECT);

        List<String> expected = List.of(
                "Sealed.java:3:40: compiler.err.not.def.public.cant.access: pkg2.Sub1, pkg2",
                "Sub1.java:3:7: compiler.err.cant.inherit.from.sealed: pkg1.Sealed",
                "2 errors");
        if (!error.containsAll(expected)) {
            throw new AssertionError("Expected output not found. Expected: " + expected);
        }
    }

    @Test
    public void testDiffPackageNeg3(Path base) throws Exception {
        Path src = base.resolve("src");
        Path pkg1 = src.resolve("pkg1");
        Path pkg2 = src.resolve("pkg2");
        Path sealed = pkg1.resolve("Sealed");
        Path sub1 = pkg2.resolve("Sub1");
        Path sub2 = pkg2.resolve("Sub2");

        tb.writeJavaFiles(sealed,
                "package pkg1;\n" +
                        "import pkg2.*;\n" +
                        "public sealed class Sealed permits pkg2.Sub1 {\n" +
                        "}");
        tb.writeJavaFiles(sub1,
                "package pkg2;\n" +
                        "import pkg1.*;\n" +
                        "public final class Sub1 extends pkg1.Sealed {\n" +
                        "}");

        Path out = base.resolve("out");

        Files.createDirectories(out);

        List<String> error = new JavacTask(tb)
                .options("-XDrawDiagnostics")
                .files(findJavaFiles(pkg1, pkg2))
                .run(Task.Expect.FAIL)
                .writeAll()
                .getOutputLines(OutputKind.DIRECT);

        List<String> expected = List.of(
                "Sealed.java:3:40: compiler.err.class.in.unnamed.module.cant.extend.sealed.in.diff.package: pkg1.Sealed",
                "1 error");
        if (!error.containsAll(expected)) {
            throw new AssertionError("Expected output not found. Found: " + error);
        }
    }

    @Test
    public void testSameModuleSamePkgPos(Path base) throws Exception {
        Path src = base.resolve("src");
        Path src_m1 = src.resolve("mSealed");
        tb.writeJavaFiles(src_m1,
                "module mSealed {}",
                "package pkg; public sealed class Sealed permits pkg.Sub{}",
                "package pkg; public final class Sub extends pkg.Sealed{}");
        Path classes = base.resolve("classes");
        tb.createDirectories(classes);

        new JavacTask(tb)
                .options("--module-source-path", src.toString())
                .outdir(classes)
                .files(findJavaFiles(src))
                .run()
                .writeAll();
    }

    @Test
    public void testSameModuleDiffPkgPos(Path base) throws Exception {
        Path src = base.resolve("src");
        Path src_m1 = src.resolve("mSealed");
        tb.writeJavaFiles(src_m1,
                "module mSealed {}",
                "package pkg1; public sealed class Sealed permits pkg2.Sub{}",
                "package pkg2; public final class Sub extends pkg1.Sealed{}");
        Path classes = base.resolve("classes");
        tb.createDirectories(classes);

        new JavacTask(tb)
                .options("--module-source-path", src.toString())
                .outdir(classes)
                .files(findJavaFiles(src))
                .run()
                .writeAll();
    }

    @Test
    public void testSameModuleSamePkgNeg1(Path base) throws Exception {
        Path src = base.resolve("src");
        Path src_m1 = src.resolve("mSealed");
        // subclass doesn't extend super class
        tb.writeJavaFiles(src_m1,
                "module mSealed {}",
                "package pkg; public sealed class Sealed permits pkg.Sub {}",
                "package pkg; public final class Sub {}");
        Path classes = base.resolve("classes");
        tb.createDirectories(classes);

        List<String> error = new JavacTask(tb)
                .options("-XDrawDiagnostics", "--module-source-path",
                        src.toString())
                .outdir(classes)
                .files(findJavaFiles(src))
                .run(Task.Expect.FAIL)
                .writeAll()
                .getOutputLines(OutputKind.DIRECT);

        List<String> expected = List.of(
                "Sealed.java:1:52: compiler.err.invalid.permits.clause: (compiler.misc.doesnt.extend.sealed: pkg.Sub)",
                "1 error");
        if (!error.containsAll(expected)) {
            throw new AssertionError("Expected output not found. Found: " + error);
        }
    }

    @Test
    public void testSameModuleSamePkgNeg2(Path base) throws Exception {
        Path src = base.resolve("src");
        Path src_m1 = src.resolve("mSealed");
        // subclass doesn't extend super class
        tb.writeJavaFiles(src_m1,
                "module mSealed {}",
                "package pkg; public sealed interface Sealed permits pkg.Sub1, pkg.Sub2 {}",
                "package pkg; public sealed class Sub1 implements Sealed permits Sub2 {}",
                "package pkg; public final class Sub2 extends Sub1 {}");
        Path classes = base.resolve("classes");
        tb.createDirectories(classes);

        List<String> error = new JavacTask(tb)
                .options("-XDrawDiagnostics", "--module-source-path",
                        src.toString())
                .outdir(classes)
                .files(findJavaFiles(src))
                .run(Task.Expect.FAIL)
                .writeAll()
                .getOutputLines(OutputKind.DIRECT);

        List<String> expected = List.of(
                "Sealed.java:1:66: compiler.err.invalid.permits.clause: (compiler.misc.doesnt.implement.sealed: kindname.class, pkg.Sub2)",
                "1 error");
        if (!error.containsAll(expected)) {
            throw new AssertionError("Expected output not found. Found: " + error);
        }
    }

    @Test
    public void testDifferentModuleNeg(Path base) throws Exception {
        // check that a subclass in one module can't extend a sealed class in another module
        Path src = base.resolve("src");
        Path src_m1 = src.resolve("mSealed");
        tb.writeJavaFiles(src_m1,
                "module mSealed { exports a; }",
                "package a; public sealed class Base permits b.Impl {}"
        );

        Path src_m2 = src.resolve("mSub");
        tb.writeJavaFiles(src_m2,
                "module mSub { exports b; requires mSealed; }",
                "package b; public final class Impl extends a.Base {}"
        );

        Path classes = base.resolve("classes");
        tb.createDirectories(classes);


        List<String> error =
            new JavacTask(tb)
                .options("-XDrawDiagnostics",
                        "--module-source-path", src.toString(),
                        "--add-reads", "mSealed=mSub")
                .outdir(classes)
                .files(findJavaFiles(src))
                .run(Task.Expect.FAIL)
                .writeAll()
                .getOutputLines(OutputKind.DIRECT);

        List<String> expected = List.of(
            "Base.java:1:46: compiler.err.class.in.module.cant.extend.sealed.in.diff.module: a.Base, mSealed",
            "1 error"
        );
        if (!error.containsAll(expected)) {
            throw new AssertionError("Expected output not found. Found: " + error);
        }
    }

    @Test
    public void testSeparateCompilation(Path base) throws Exception {
        Path src = base.resolve("src");
        Path src_m = src.resolve("m");
        tb.writeJavaFiles(src_m,
                "module m {}",
                "package pkg.a; public sealed interface Sealed permits pkg.b.Sub {}",
                "package pkg.b; public final class Sub implements pkg.a.Sealed {}");
        Path classes = base.resolve("classes");
        tb.createDirectories(classes);

        new JavacTask(tb)
                .options("-XDrawDiagnostics", "--module-source-path",
                        src.toString())
                .outdir(classes)
                .files(findJavaFiles(src_m))
                .run()
                .writeAll()
                .getOutputLines(OutputKind.DIRECT);

        new JavacTask(tb)
                .options("-XDrawDiagnostics", "--module-source-path",
                        src.toString(), "-doe")
                .outdir(classes)
                .files(findJavaFiles(src_m.resolve("pkg").resolve("a")))
                .run()
                .writeAll()
                .getOutputLines(OutputKind.DIRECT);

        new JavacTask(tb)
                .options("-XDrawDiagnostics", "--module-source-path",
                        src.toString(), "-doe")
                .outdir(classes)
                .files(findJavaFiles(src_m.resolve("pkg").resolve("b")))
                .run()
                .writeAll()
                .getOutputLines(OutputKind.DIRECT);

        tb.cleanDirectory(classes);

        //implicit compilations:
        new JavacTask(tb)
                .options("-XDrawDiagnostics", "--module-source-path",
                        src.toString(), "-doe")
                .outdir(classes)
                .files(findJavaFiles(src_m.resolve("pkg").resolve("a")))
                .run()
                .writeAll()
                .getOutputLines(OutputKind.DIRECT);

        tb.cleanDirectory(classes);

        new JavacTask(tb)
                .options("-XDrawDiagnostics", "--module-source-path",
                        src.toString(), "-doe")
                .outdir(classes)
                .files(findJavaFiles(src_m.resolve("pkg").resolve("b")))
                .run()
                .writeAll()
                .getOutputLines(OutputKind.DIRECT);
    }

    @Test //JDK-8293348
    public void testSupertypePermitsLoop(Path base) throws Exception {
        Path src = base.resolve("src");

        tb.writeJavaFiles(src,
                          "class Main implements T2 {}",
                          "non-sealed interface T2 extends T {}",
                          "sealed interface T permits T2 {}");

        Path out = base.resolve("out");

        Files.createDirectories(out);

        new JavacTask(tb)
                .outdir(out)
                .files(findJavaFiles(src))
                .run()
                .writeAll();

        Files.delete(out.resolve("Main.class"));
        Files.delete(out.resolve("T.class"));

        new JavacTask(tb)
                .outdir(out)
                .options("-cp", out.toString(),
                         "-sourcepath", src.toString())
                .files(src.resolve("Main.java"))
                .run()
                .writeAll();
    }

    @Test
    public void testClientSwapsPermittedSubclassesOrder(Path base) throws Exception {
        Path src = base.resolve("src");
        Path foo = src.resolve("Foo.java");
        Path fooUser = src.resolve("FooUser.java");

        tb.writeFile(foo,
                """
                public sealed interface Foo {
                    record R1() implements Foo {}
                    record R2() implements Foo {}
                }
                """);

        tb.writeFile(fooUser,
                """
                public class FooUser {
                    // see that the order of arguments differ from the order of subclasses of Foo in the source above
                    // we need to check that the order of permitted subclasses of Foo in the class file corresponds to the
                    // original order in the source code
                    public void blah(Foo.R2 a, Foo.R1 b) {}
                }
                """);

        Path out = base.resolve("out");
        Files.createDirectories(out);

        new JavacTask(tb)
                .outdir(out)
                .files(fooUser, foo)
                .run();
        checkSealedClassFile(out, "Foo.class", List.of("Foo$R1", "Foo$R2"));
    }

    @Test
    public void testDuplicatePermittedSubclassesDoclint(Path base) throws Exception {
        Path src = base.resolve("src");
        Path foo = src.resolve("Foo.java");

        tb.writeFile(foo,
                """
                public class Foo {
                  private enum E {
                    INSTANCE {
                      /** foo {@link E} */
                      void f() {}
                    };
                    void f() {}
                  }
                }
                """);

        Path out = base.resolve("out");
        Files.createDirectories(out);

        new JavacTask(tb)
                .options("-Xdoclint:html,syntax")
                .outdir(out)
                .files(foo)
                .run();
        checkSealedClassFile(out, "Foo$E.class", List.of("Foo$E$1"));
    }
}
