/*
 * Copyright (c) 2012, 2015, 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 8005166 8129962
 * @summary Add support for static interface methods
 *          Smoke test for static interface method hiding
 * @library /tools/javac/lib
 * @modules jdk.compiler/com.sun.tools.javac.api
 *          jdk.compiler/com.sun.tools.javac.code
 *          jdk.compiler/com.sun.tools.javac.comp
 *          jdk.compiler/com.sun.tools.javac.main
 *          jdk.compiler/com.sun.tools.javac.tree
 *          jdk.compiler/com.sun.tools.javac.util
 * @build combo.ComboTestHelper
 * @run main InterfaceMethodHidingTest
 */

import java.io.IOException;

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

public class InterfaceMethodHidingTest extends ComboInstance<InterfaceMethodHidingTest> {

    enum SignatureKind implements ComboParameter {
        VOID_INTEGER("void m(Integer s)", false),
        STRING_INTEGER("String m(Integer s)", true),
        VOID_STRING("void m(String s)", false),
        STRING_STRING("String m(String s)", true);

        String sigStr;
        boolean needsReturn;

        SignatureKind(String sigStr, boolean needsReturn) {
            this.sigStr = sigStr;
            this.needsReturn = needsReturn;
        }

        boolean overrideEquivalentWith(SignatureKind s2) {
            switch (this) {
                case VOID_INTEGER:
                case STRING_INTEGER:
                    return s2 == VOID_INTEGER || s2 == STRING_INTEGER;
                case VOID_STRING:
                case STRING_STRING:
                    return s2 == VOID_STRING || s2 == STRING_STRING;
                default:
                    throw new AssertionError("bad signature kind");
            }
        }

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

    enum MethodKind implements ComboParameter {
        VIRTUAL("#{SIG[#IDX]};"),
        STATIC("static #{SIG[#IDX]} { #{BODY[#IDX]}; #{RET.#IDX} }"),
        DEFAULT("default #{SIG[#IDX]} { #{BODY[#IDX]}; #{RET.#IDX} }");

        String methTemplate;

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

        boolean inherithed() {
            return this != STATIC;
        }

        static boolean overrides(MethodKind mk1, SignatureKind sk1, MethodKind mk2, SignatureKind sk2) {
            return sk1 == sk2 &&
                    mk2.inherithed() &&
                    mk1 != STATIC;
        }

        @Override
        public String expand(String optParameter) {
            return methTemplate.replaceAll("#IDX", optParameter);
        }
    }

    enum BodyExpr implements ComboParameter {
        NONE(""),
        THIS("Object o = this");

        String bodyExprStr;

        BodyExpr(String bodyExprStr) {
            this.bodyExprStr = bodyExprStr;
        }

        boolean allowed(MethodKind mk) {
            return this == NONE ||
                    mk != MethodKind.STATIC;
        }

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

    public static void main(String... args) throws Exception {
        new ComboTestHelper<InterfaceMethodHidingTest>()
                .withArrayDimension("SIG", (x, sig, idx) -> x.signatureKinds[idx] = sig, 3, SignatureKind.values())
                .withArrayDimension("BODY", (x, body, idx) -> x.bodyExprs[idx] = body, 3, BodyExpr.values())
                .withArrayDimension("MET", (x, meth, idx) -> x.methodKinds[idx] = meth, 3, MethodKind.values())
                .run(InterfaceMethodHidingTest::new);
    }

    MethodKind[] methodKinds = new MethodKind[3];
    SignatureKind[] signatureKinds = new SignatureKind[3];
    BodyExpr[] bodyExprs = new BodyExpr[3];

    String template = "interface Sup {\n" +
                          "   default void sup() { }\n" +
                          "}\n" +
                          "interface A extends Sup {\n" +
                          "   #{MET[0].0}\n" +
                          "}\n" +
                          "interface B extends A, Sup {\n" +
                          "   #{MET[1].1}\n" +
                          "}\n" +
                          "interface C extends B, Sup {\n" +
                          "   #{MET[2].2}\n" +
                          "}\n";

    @Override
    public void doWork() throws IOException {
        check(newCompilationTask()
                .withOption("-XDallowStaticInterfaceMethods")
                .withSourceFromTemplate(template, this::returnExpr)
                .analyze());
    }

    ComboParameter returnExpr(String name) {
        switch (name) {
            case "RET":
                return optParameter -> {
                    int idx = new Integer(optParameter);
                    return signatureKinds[idx].needsReturn ? "return null;" : "return;";
                };
            default:
                return null;
        }
    }

    void check(Result<?> res) {
        boolean errorExpected = !bodyExprs[0].allowed(methodKinds[0]) ||
                !bodyExprs[1].allowed(methodKinds[1]) ||
                !bodyExprs[2].allowed(methodKinds[2]);

        if (methodKinds[0].inherithed()) {
            errorExpected |= signatureKinds[1].overrideEquivalentWith(signatureKinds[0]) &&
                    !MethodKind.overrides(methodKinds[1], signatureKinds[1], methodKinds[0], signatureKinds[0]) ||
                    signatureKinds[2].overrideEquivalentWith(signatureKinds[0]) &&
                    !MethodKind.overrides(methodKinds[2], signatureKinds[2], methodKinds[0], signatureKinds[0]);
        }

        if (methodKinds[1].inherithed()) {
            errorExpected |= signatureKinds[2].overrideEquivalentWith(signatureKinds[1]) &&
                    !MethodKind.overrides(methodKinds[2], signatureKinds[2], methodKinds[1], signatureKinds[1]);
        }

        if (res.hasErrors() != errorExpected) {
            fail("Problem when compiling source:\n" + res.compilationInfo() +
                    "\nfound error: " + res.hasErrors());
        }
    }
}
