/*
 * 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 8003280 8004102 8006694 8129962
 * @summary Add lambda tests
 *  perform several automated checks in lambda conversion, esp. around accessibility
 *  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.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 FunctionalInterfaceConversionTest
 */

import java.io.IOException;

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


public class FunctionalInterfaceConversionTest extends ComboInstance<FunctionalInterfaceConversionTest> {

    enum PackageKind implements ComboParameter {
        NO_PKG(""),
        PKG_A("a");

        String pkg;

        PackageKind(String pkg) {
            this.pkg = pkg;
        }

        @Override
        public String expand(String optParameter) {
            return this == NO_PKG ?
                "" :
                "package " + pkg + ";";
        }

        String getImportStat() {
            return this == NO_PKG ?
                "" :
                "import " + pkg + ".*;";
        }
    }

    enum SamKind implements ComboParameter {
        CLASS("public class Sam {  }"),
        ABSTACT_CLASS("public abstract class Sam {  }"),
        ANNOTATION("public @interface Sam {  }"),
        ENUM("public enum Sam { }"),
        INTERFACE("public interface Sam { \n #{METH1}; \n }");

        String sam_str;

        SamKind(String sam_str) {
            this.sam_str = sam_str;
        }

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

    enum ModifierKind implements ComboParameter {
        PUBLIC("public"),
        PACKAGE("");

        String modifier_str;

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

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

    enum TypeKind implements ComboParameter {
        EXCEPTION("Exception"),
        PKG_CLASS("PackageClass");

        String typeStr;

        TypeKind(String typeStr) {
            this.typeStr = typeStr;
        }

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

    enum ExprKind implements ComboParameter {
        LAMBDA("x -> null"),
        MREF("this::m");

        String exprStr;

        ExprKind(String exprStr) {
            this.exprStr = exprStr;
        }

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

    enum MethodKind implements ComboParameter {
        NONE(""),
        NON_GENERIC("public abstract #{RET} m(#{ARG} s) throws #{THROWN};"),
        GENERIC("public abstract <X> #{RET} m(#{ARG} s) throws #{THROWN};");

        String methodTemplate;

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

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

    public static void main(String[] args) throws Exception {
        new ComboTestHelper<FunctionalInterfaceConversionTest>()
                .withDimension("PKG", (x, pkg) -> x.samPkg = pkg, PackageKind.values())
                .withDimension("MOD", (x, mod) -> x.modKind = mod, ModifierKind.values())
                .withDimension("CLAZZ", (x, sam) -> x.samKind = sam, SamKind.values())
                .withDimension("METH1", (x, meth) -> x.samMeth = meth, MethodKind.values())
                .withDimension("METH2", (x, meth) -> x.clientMeth = meth, MethodKind.values())
                .withDimension("RET", (x, ret) -> x.retType = ret, TypeKind.values())
                .withDimension("ARG", (x, arg) -> x.argType = arg, TypeKind.values())
                .withDimension("THROWN", (x, thrown) -> x.thrownType = thrown, TypeKind.values())
                .withDimension("EXPR", (x, expr) -> x.exprKind = expr, ExprKind.values())
                .run(FunctionalInterfaceConversionTest::new);
    }

    PackageKind samPkg;
    ModifierKind modKind;
    SamKind samKind;
    MethodKind samMeth;
    MethodKind clientMeth;
    TypeKind retType;
    TypeKind argType;
    TypeKind thrownType;
    ExprKind exprKind;

    String samSource = "#{PKG} \n #{CLAZZ}";
    String pkgClassSource = "#{PKG}\n #{MOD} class PackageClass extends Exception { }";
    String clientSource = "#{IMP}\n abstract class Client { \n" +
                           "  Sam s = #{EXPR};\n" +
                           "  #{METH2} \n }";

    @Override
    public void doWork() throws IOException {
        newCompilationTask()
                .withSourceFromTemplate("Sam", samSource)
                .withSourceFromTemplate("PackageClass", pkgClassSource)
                .withSourceFromTemplate("Client", clientSource, this::importStmt)
                .analyze(this::check);
    }

    ComboParameter importStmt(String name) {
        switch (name) {
            case "IMP": return new ComboParameter.Constant<>(samPkg.getImportStat());
            default: return null;
        }
    }

    void check(Result<?> res) {
        if (res.hasErrors() == checkSamConversion()) {
            fail("Unexpected compilation result; " + res.compilationInfo());
        }
    }

    boolean checkSamConversion() {
        if (samKind != SamKind.INTERFACE) {
            //sam type must be an interface
            return false;
        } else if (samMeth == MethodKind.NONE) {
            //interface must have at least a method
            return false;
        } else if (exprKind == ExprKind.LAMBDA &&
                samMeth != MethodKind.NON_GENERIC) {
            //target method for lambda must be non-generic
            return false;
        } else if (exprKind == ExprKind.MREF &&
                clientMeth == MethodKind.NONE) {
            return false;
        } else if (samPkg != PackageKind.NO_PKG &&
                modKind != ModifierKind.PUBLIC &&
                (retType == TypeKind.PKG_CLASS ||
                argType == TypeKind.PKG_CLASS ||
                thrownType == TypeKind.PKG_CLASS)) {
            //target must not contain inaccessible types
            return false;
        } else {
            return true;
        }
    }
}
