/*
 * Copyright (c) 2022, 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
# @bug 8296329
* @summary Tests for version validator.
* @library /test/lib
* @modules java.base/jdk.internal.misc
*          jdk.compiler
*          jdk.jartool
* @build jdk.test.lib.Utils
*        jdk.test.lib.Asserts
*        jdk.test.lib.JDKToolFinder
*        jdk.test.lib.JDKToolLauncher
*        jdk.test.lib.Platform
*        jdk.test.lib.process.*
*        MRTestBase
* @run testng/timeout=1200 VersionValidatorTest
*/

import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;

public class VersionValidatorTest extends MRTestBase {
    private Path root;

    @BeforeMethod
    void testInit(Method method) {
        root = Paths.get(method.getName());
    }

    @Test(dataProvider = "differentMajorVersions")
    public void onlyCompatibleVersionIsAllowedInMultiReleaseJar(String baseMajorVersion, String otherMajorVersion,
            boolean enablePreviewForBaseVersion, boolean enablePreviewForOtherVersion, boolean isAcceptable)
            throws Throwable {
        Path baseVersionClassesDir = compileLibClass(baseMajorVersion, enablePreviewForBaseVersion);
        Path otherVersionClassesDir = compileLibClass(otherMajorVersion, enablePreviewForOtherVersion);

        var result = jar("--create", "--file", "lib.jar", "-C", baseVersionClassesDir.toString(), "Lib.class",
                "--release", otherMajorVersion, "-C", otherVersionClassesDir.toString(), "Lib.class");

        if (isAcceptable) {
            result.shouldHaveExitValue(SUCCESS)
                    .shouldBeEmptyIgnoreVMWarnings();
        } else {
            result.shouldNotHaveExitValue(SUCCESS)
                    .shouldContain("has a class version incompatible with an earlier version");
        }
    }

    private Path compileLibClass(String majorVersion, boolean enablePreview) throws Throwable {
        String classTemplate = """
                public class Lib {
                    public static int version = $VERSION;
                }
                    """;

        Path sourceFile = Files.createDirectories(root.resolve("src").resolve(majorVersion)).resolve("Lib.java");
        Files.write(sourceFile, classTemplate.replace("$VERSION", majorVersion).getBytes());

        Path classesDir = root.resolve("classes").resolve(majorVersion);

        javac(Integer.parseInt(majorVersion), classesDir, sourceFile);
        if (enablePreview) {
            rewriteMinorVersionForEnablePreviewClass(classesDir.resolve("Lib.class"));
        }
        return classesDir;
    }

    private void rewriteMinorVersionForEnablePreviewClass(Path classFile) throws Throwable {
        byte[] classBytes = Files.readAllBytes(classFile);
        classBytes[4] = -1;
        classBytes[5] = -1;
        Files.write(classFile, classBytes);
    }

    @DataProvider
    Object[][] differentMajorVersions() {
        return new Object[][] {
                { "19", "20", false, true, true },
                { "19", "20", false, false, true },
                { "19", "20", true, true, true },
                { "19", "20", true, false, true },
                { "20", "19", false, true, false },
                { "20", "19", false, false, false },
                { "20", "19", true, true, false },
                { "20", "19", true, false, false },
        };
    }
}
