/*
 * Copyright (c) 2010, 2022, 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     6993978 7097436 8006694 7196160
 * @summary Project Coin: Annotation to reduce varargs warnings
 *  temporarily workaround combo tests are causing time out in several platforms
 * @library /tools/javac/lib
 * @modules jdk.compiler/com.sun.tools.javac.api
 *          jdk.compiler/com.sun.tools.javac.file
 *          jdk.compiler/com.sun.tools.javac.util
 * @build combo.ComboTestHelper
 * @run main/othervm Warn5
 */

import java.io.IOException;
import java.util.EnumSet;
import javax.tools.Diagnostic;
import javax.tools.Diagnostic.Kind;
import javax.tools.JavaFileObject;

import combo.ComboInstance;
import combo.ComboParameter;
import combo.ComboTask.Result;
import combo.ComboTestHelper;


public class Warn5 extends ComboInstance<Warn5> {

    enum XlintOption {
        NONE("none"),
        ALL("all");

        String opt;

        XlintOption(String opt) {
            this.opt = opt;
        }

        String getXlintOption() {
            return "-Xlint:" + opt;
        }
    }

    enum TrustMe implements ComboParameter {
        DONT_TRUST(""),
        TRUST("@java.lang.SafeVarargs");

        String anno;

        TrustMe(String anno) {
            this.anno = anno;
        }

        @Override
        public String expand(String optParameter) {
            return anno;
        }
    }

    enum SuppressLevel implements ComboParameter {
        NONE,
        VARARGS;

        @Override
        public String expand(String optParameter) {
            return this == VARARGS ?
                "@SuppressWarnings(\"varargs\")" :
                "";
        }
    }

    enum ModifierKind implements ComboParameter {
        NONE(""),
        FINAL("final"),
        STATIC("static"),
        PRIVATE("private");

        String mod;

        ModifierKind(String mod) {
            this.mod = mod;
        }

        @Override
        public String expand(String optParameter) {
            return mod;
        }
    }

    enum MethodKind implements ComboParameter {
        METHOD("void m"),
        CONSTRUCTOR("Test");

        String name;

        MethodKind(String name) {
            this.name = name;
        }

        @Override
        public String expand(String optParameter) {
            return name;
        }
    }

    // Handling of varargs warnings changed in JDK 9 compared to JDK 8
    // and then remained consistent; test 8 and then current release.
    enum SourceLevel {
        JDK_8("8"),
        LATEST(Integer.toString(javax.lang.model.SourceVersion.latest().runtimeVersion().feature()));

        String sourceKey;

        SourceLevel(String sourceKey) {
            this.sourceKey = sourceKey;
        }
    }

    enum SignatureKind implements ComboParameter {
        VARARGS_X("<X>#{NAME}(X... x)", false, true),
        VARARGS_STRING("#{NAME}(String... x)", true, true),
        ARRAY_X("<X>#{NAME}(X[] x)", false, false),
        ARRAY_STRING("#{NAME}(String[] x)", true, false);

        String stub;
        boolean isReifiableArg;
        boolean isVarargs;

        SignatureKind(String stub, boolean isReifiableArg, boolean isVarargs) {
            this.stub = stub;
            this.isReifiableArg = isReifiableArg;
            this.isVarargs = isVarargs;
        }

        @Override
        public String expand(String optParameter) {
            return stub;
        }
    }

    // javac does not currently perform analysis of the method body
    // with respect to the validity of the @SafeVargs annotation. If
    // that changes, the body tests should be expanded.
    enum BodyKind implements ComboParameter {
        // ASSIGN("Object o = x;", true),
        // CAST("Object o = (Object)x;", true),
        METH("test(x);", true),
        PRINT("System.out.println(x.toString());", false),
        // ARRAY_ASSIGN("Object[] o = x;", true),
        ARRAY_CAST("Object[] o = (Object[])x;", true),
        ARRAY_METH("testArr(x);", true);

        String body;
        boolean hasAliasing;

        BodyKind(String body, boolean hasAliasing) {
            this.body = body;
            this.hasAliasing = hasAliasing;
        }

        @Override
        public String expand(String optParameter) {
            return body;
        }
    }

    enum WarningKind {
        UNSAFE_BODY("compiler.warn.varargs.unsafe.use.varargs.param"),
        UNSAFE_DECL("compiler.warn.unchecked.varargs.non.reifiable.type"),
        MALFORMED_SAFEVARARGS("compiler.err.varargs.invalid.trustme.anno"),
        REDUNDANT_SAFEVARARGS("compiler.warn.varargs.redundant.trustme.anno");

        String code;

        WarningKind(String code) {
            this.code = code;
        }
    }

    public static void main(String[] args) {
        new ComboTestHelper<Warn5>()
                .withFilter(Warn5::badTestFilter)
                .withDimension("SOURCE", (x, level) -> x.sourceLevel = level, SourceLevel.values())
                .withDimension("LINT", (x, lint) -> x.xlint = lint, XlintOption.values())
                .withDimension("TRUSTME", (x, trustme) -> x.trustMe = trustme, TrustMe.values())
                .withDimension("SUPPRESS", (x, suppress) -> x.suppressLevel = suppress, SuppressLevel.values())
                .withDimension("MOD", (x, mod) -> x.modKind = mod, ModifierKind.values())
                .withDimension("NAME", (x, name) -> x.methKind = name, MethodKind.values())
                .withDimension("SIG", (x, sig) -> x.sig = sig, SignatureKind.values())
                .withDimension("BODY", (x, body) -> x.body = body, BodyKind.values())
                .run(Warn5::new);
    }

    SourceLevel sourceLevel;
    XlintOption xlint;
    TrustMe trustMe;
    SuppressLevel suppressLevel;
    ModifierKind modKind;
    MethodKind methKind;
    SignatureKind sig;
    BodyKind body;

    boolean badTestFilter() {
        return (methKind != MethodKind.CONSTRUCTOR || modKind == ModifierKind.NONE);
    }

    String template = """
        import com.sun.tols.javac.api.*;
        import java.util.List;
        class Test {
           static void test(Object o) {}
           static void testArr(Object[] o) {}
           #{TRUSTME} #{SUPPRESS} #{MOD} #{SIG} { #{BODY} }
        }
        """;

    @Override
    public void doWork() throws IOException {
        newCompilationTask()
                .withOption(xlint.getXlintOption())
                .withOption("--release")
                .withOption(sourceLevel.sourceKey)
                .withSourceFromTemplate(template)
                .analyze(this::check);
    }

    void check(Result<?> res) {

        EnumSet<WarningKind> foundWarnings = EnumSet.noneOf(WarningKind.class);
        for (Diagnostic.Kind kind : new Kind[] { Kind.ERROR, Kind.MANDATORY_WARNING, Kind.WARNING}) {
            for (Diagnostic<? extends JavaFileObject> diag : res.diagnosticsForKind(kind)) {
                for (WarningKind wk : WarningKind.values()) {
                    if (wk.code.equals(diag.getCode())) {
                        foundWarnings.add(wk);
                    }
                }
            }
        }

        EnumSet<WarningKind> expectedWarnings =
                EnumSet.noneOf(WarningKind.class);

        if (trustMe == TrustMe.TRUST &&
            suppressLevel != SuppressLevel.VARARGS &&
            xlint != XlintOption.NONE &&
            sig.isVarargs &&
            !sig.isReifiableArg &&
            body.hasAliasing &&
            (methKind == MethodKind.CONSTRUCTOR ||
             (methKind == MethodKind.METHOD &&
              modKind == ModifierKind.FINAL || modKind == ModifierKind.STATIC ||
              (modKind == ModifierKind.PRIVATE && sourceLevel.compareTo(SourceLevel.JDK_8) > 0)))) {
            expectedWarnings.add(WarningKind.UNSAFE_BODY);
        }

        if (trustMe == TrustMe.DONT_TRUST &&
            sig.isVarargs &&
            !sig.isReifiableArg &&
            xlint == XlintOption.ALL) {
            expectedWarnings.add(WarningKind.UNSAFE_DECL);
        }

        if (trustMe == TrustMe.TRUST &&
            (!sig.isVarargs ||
             ((modKind == ModifierKind.NONE ||
               modKind == ModifierKind.PRIVATE && sourceLevel.compareTo(SourceLevel.JDK_8) <= 0 ) &&
              methKind == MethodKind.METHOD))) {
            expectedWarnings.add(WarningKind.MALFORMED_SAFEVARARGS);
        }

        if (trustMe == TrustMe.TRUST &&
            xlint != XlintOption.NONE &&
            suppressLevel != SuppressLevel.VARARGS &&
            (modKind == ModifierKind.FINAL || modKind == ModifierKind.STATIC ||
             (modKind == ModifierKind.PRIVATE && sourceLevel.compareTo(SourceLevel.JDK_8) > 0) ||
             methKind == MethodKind.CONSTRUCTOR) &&
            sig.isVarargs &&
            sig.isReifiableArg) {
            expectedWarnings.add(WarningKind.REDUNDANT_SAFEVARARGS);
        }

        if (!expectedWarnings.containsAll(foundWarnings) ||
                !foundWarnings.containsAll(expectedWarnings)) {
            fail("invalid diagnostics for source:\n" +
                    res.compilationInfo() +
                    "\nOptions: " + xlint.getXlintOption() +
                    "\nSource Level: " + sourceLevel +
                    "\nExpected warnings: " + expectedWarnings +
                    "\nFound warnings: " + foundWarnings);
        }
    }
}