/*
 * Copyright (c) 2013, 2018, 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.
 */

package vm.runtime.defmeth;

import nsk.share.test.TestBase;
import vm.runtime.defmeth.shared.DefMethTest;
import vm.runtime.defmeth.shared.annotation.KnownFailure;
import vm.runtime.defmeth.shared.data.*;
import vm.runtime.defmeth.shared.data.method.param.NewInstanceParam;
import vm.runtime.defmeth.shared.builder.TestBuilder;
import static vm.runtime.defmeth.shared.ExecutionMode.*;
import static vm.runtime.defmeth.shared.data.method.body.CallMethod.Invoke.*;
import static vm.runtime.defmeth.shared.data.method.body.CallMethod.IndexbyteOp.*;

/**
 * Tests on interaction of default methods with abstract methods
 *
 * The rule: "the superclass always wins."
 *
 * In searching the superclass hierarchy, a declaration in a superclass is
 * preferred to a default in an interface. This preference includes abstract
 * methods in superclasses as well; the defaults are only considered when
 * the entire implementation hierarchy is silent on the status of the method
 * in question.
 */
public class DefaultVsAbstractTest extends DefMethTest {

    public static void main(String[] args) {
        TestBase.runTest(new DefaultVsAbstractTest(), args);
    }

    /*
     * interface I          { public int m() default { return 1; } }
     * class C implements I { public abstract int m(); }
     *
     * TEST: new C() throws InstantiationError
     */
    public void test0() {
        TestBuilder b = factory.getBuilder();

        Interface I = b.intf("I")
                .defaultMethod("m", "()I").returns(1).build()
            .build();

        ConcreteClass C = b.clazz("C").implement(I)
                .abstractMethod("m", "()I").build()
            .build();

        b.test()
            .callSite(I, C, "m", "()I")
            .throws_(InstantiationError.class)
            .done()
        .run();
    }

    /*
     * interface I          {
     *     public int m() default { return 1; }
     * }
     * class C implements I {
     *     public abstract int m();
     * }
     * class D extends C {}
     *
     * TEST: I i = new D(); i.m() ==> AME
     * TEST: C c = new D(); c.m() ==> AME
     * TEST: D d = new D(); d.m() ==> AME
     */
    @KnownFailure(modes = {INVOKE_EXACT, INVOKE_GENERIC, INVOKE_WITH_ARGS, INDY }) // Test1_I_D_m: NPE instead of AME
                                                                                   // Test3_D_D_m: AME => IAE => ICCE instead of AME
    public void test1() {
        TestBuilder b = factory.getBuilder();

        Interface I = b.intf("I")
                .defaultMethod("m", "()I").returns(1).build()
            .build();

        ConcreteClass C = b.clazz("C").implement(I)
                .abstractMethod("m", "()I").build()
            .build();

        ConcreteClass D = b.clazz("D").extend(C).build();

        b.test()
            .callSite(I, D, "m",  "()I")
            .throws_(AbstractMethodError.class)
            .done()
        .test()
            .callSite(C, D, "m", "()I")
            .throws_(AbstractMethodError.class)
            .done()
        .test()
            .callSite(D, D, "m", "()I")
            .throws_(AbstractMethodError.class)
            .done()
        .run();
    }

    /*
     * interface I {
     *     default  public int m() { return 1; }
     * }
     * class C     {
     *     abstract public int m();
     * }
     * class D extends C implements I {}
     *
     * TEST: I o = new D(); o.m()I throws AME
     * TEST: C o = new D(); o.m()I throws AME
     * TEST: D o = new D(); o.m()I throws AME
     */
    @KnownFailure(modes = { INVOKE_EXACT, INVOKE_GENERIC, INVOKE_WITH_ARGS, INDY }) // Test1_I_D_m: NPE instead of AME
                                                                                    // Test3_D_D_m: AME => IAE => ICCE instead of AME
    public void test2() {
        TestBuilder b = factory.getBuilder();

        Interface I = b.intf("I")
                .defaultMethod("m", "()I").returns(1).build()
            .build();

        ConcreteClass C = b.clazz("C")
                .abstractMethod("m", "()I").build()
            .build();

        ConcreteClass D = b.clazz("D").extend(C).implement(I).build();

        b.test()
            .callSite(I, D, "m", "()I")
            .throws_(AbstractMethodError.class)
            .done()
        .test()
            .callSite(C, D, "m", "()I")
            .throws_(AbstractMethodError.class)
            .done()
        .test()
            .callSite(D, D, "m", "()I")
            .throws_(AbstractMethodError.class)
            .done()
        .run();
    }

    /*
     * interface I {
     *     default public int m() { return 1; }
     * }
     * class C {
     *     abstract public int m();
     * }
     * class D extends C implements I {
     *     public int m()  { return 2; }
     * }
     *
     * TEST: I o = new D(); o.m()I == 2
     * TEST: C o = new D(); o.m()I == 2
     * TEST: D o = new D(); o.m()I == 2
     */
    public void test3() {
        TestBuilder b = factory.getBuilder();

        Interface I = b.intf("I")
                .defaultMethod("m", "()I").returns(1).build()
            .build();

        ConcreteClass C = b.clazz("C")
                .abstractMethod("m", "()I").build()
            .build();

        ConcreteClass D = b.clazz("D").extend(C).implement(I)
                .concreteMethod("m", "()I").returns(2)
                .build()
            .build();

        b.test() // I i = new D(); ...
            .callSite(I, D, "m", "()I").returns(2)
            .done()
        .test()  // C c = new D(); ...
            .callSite(C, D, "m", "()I").returns(2)
            .done()
        .test()  // D d = new C(); ...
            .callSite(D, D, "m", "()I").returns(2)
            .done()
        .run();
    }

    /*
     * interface I {
     *     default public int m() { return 1; }
     * }
     * class E {
     *     abstract public int m();
     * }
     * class D extends E {}
     * class C extends D implements I {}
     *
     * TEST: I o = new C(); o.m()I throws AME
     * TEST: E o = new C(); o.m()I throws AME
     * TEST: D o = new C(); o.m()I throws AME
     * TEST: C o = new C(); o.m()I throws AME
     */
    @KnownFailure(modes = {
        INVOKE_EXACT, INVOKE_GENERIC, INVOKE_WITH_ARGS, INDY // Test1_I_C_m: NPE instead of AME
                                                             // Test3_D_C_m: AME => IAE => ICCE instead of AME
                                                             // Test4_C_C_m: AME => IAE => ICCE instead of AME
    })
    public void test4() {
        TestBuilder b = factory.getBuilder();

        Interface I = b.intf("I")
                .defaultMethod("m", "()I").returns(1).build()
                .build();

        ConcreteClass E = b.clazz("E")
                .abstractMethod("m", "()I").build()
                .build();

        ConcreteClass D = b.clazz("D").extend(E).build();

        ConcreteClass C = b.clazz("C").extend(D).implement(I).build();

        b.test() // I i = new C(); ...
            .callSite(I, C, "m", "()I")
            .throws_(AbstractMethodError.class)
            .done()
        .test() // E e = new C(); ...
            .callSite(E, C, "m", "()I")
            .throws_(AbstractMethodError.class)
            .done()
        .test() // D d = new C(); ...
            .callSite(D, C, "m", "()I")
            .throws_(AbstractMethodError.class)
            .done()
        .test() // C c = new C(); ...
            .callSite(C, C, "m", "()I")
            .throws_(AbstractMethodError.class)
            .done()
        .run();
    }

    /*
     * interface I {
     *     default public int m() { return 1; }
     * }
     * class E {
     *     abstract public int m();
     * }
     * class D extends E {
     *     public int m()  { return 2; }
     * }
     * class C extends D implements I {}
     *
     * TEST: I o = new C(); o.m()I == 2
     * TEST: I o = new C(); o.m()I == 2
     * TEST: I o = new C(); o.m()I == 2
     * TEST: I o = new C(); o.m()I == 2
     */
    public void test5() {
        TestBuilder b = factory.getBuilder();

        Interface I = b.intf("I")
                .defaultMethod("m", "()I").returns(1).build()
            .build();

        ConcreteClass E = b.clazz("E")
                .abstractMethod("m", "()I").build()
            .build();

        ConcreteClass D = b.clazz("D").extend(E)
                .concreteMethod("m", "()I").returns(2).build()
            .build();

        ConcreteClass C = b.clazz("C").extend(D).implement(I).build();

        b.test() // I i = new C(); ...
            .callSite(I, C, "m", "()I")
            .returns(2)
            .done()
        .test() // E e = new C(); ...
            .callSite(I, C, "m", "()I")
            .returns(2)
            .done()
        .test() // D d = new C(); ...
            .callSite(I, C, "m", "()I")
            .returns(2)
            .done()
        .test() // C c = new C(); ...
            .callSite(I, C, "m", "()I")
            .returns(2)
            .done()
        .run();
    }

    /*
     * interface I {
     *     default public int m() { return 1; }
     * }
     * interface J {
     *     default public int m() { return 2; }
     * }
     * class E {
     *     abstract public int m();
     * }
     * class D extends E {
     *     public int m()  { return 3; }
     * }
     * class C extends D implements I, J {}
     *
     * TEST: I o = new C(); o.m()I == 3
     * TEST: J o = new C(); o.m()I == 3
     * TEST: E o = new C(); o.m()I == 3
     * TEST: D o = new C(); o.m()I == 3
     * TEST: J o = new C(); o.m()I == 3
     */
    public void test6() {
        TestBuilder b = factory.getBuilder();

        Interface I = b.intf("I")
                .defaultMethod("m", "()I").returns(1).build()
            .build();

        Interface J = b.intf("J")
                .defaultMethod("m", "()I").returns(2).build()
            .build();

        ConcreteClass E = b.clazz("E")
                .abstractMethod("m", "()I").build()
            .build();

        ConcreteClass D = b.clazz("D").extend(E)
                .concreteMethod("m", "()I").returns(3).build()
            .build();

        ConcreteClass C = b.clazz("C").extend(D).implement(I, J).build();


        b.test() // I i = new C(); ...
            .callSite(I, C, "m", "()I").returns(3)
            .done()
        .test() // J j = new C(); ...
            .callSite(J, C, "m", "()I").returns(3)
            .done()
        .test()  // E e = new C(); ...
            .callSite(E, C, "m", "()I").returns(3)
            .done()
        .test()  // D d = new C(); ...
            .callSite(D, C, "m", "()I").returns(3)
            .done()
        .test() // C c = new C(); ...
            .callSite(J, C, "m", "()I").returns(3)
            .done()
        .run();
    }

    /*
     * interface I {
     *     abstract public int m();
     * }
     *
     * interface J {
     *     default public int m() { return 1; }
     * }
     *
     * class A implements I;
     *
     * class B extends A implements J;
     *
     * TEST: A o = new B(); o.m()I
     *                returns 1 for REFLECTION and INVOKE_WITH_ARGS
     *                ICCE for other modes
     */
    public void testInvokeInterfaceClassDefaultMethod() {
        TestBuilder b = factory.getBuilder();

        Interface I = b.intf("I")
            .abstractMethod("m", "()I").build()
            .build();

        Interface J = b.intf("J")
            .extend(I)
            .defaultMethod("m", "()I").returns(1).build()
            .build();

        ConcreteClass A = b.clazz("A").implement(I).build();

        ConcreteClass B = b.clazz("B").extend(A).implement(J).build();

        String exeMode = factory.getExecutionMode();

        // the test passes in the reflection mode because there's no way to
        // express invokeinterface on a class using Reflection API
        // In the test generator, vm.runtime.defmeth.shared.executor.ReflectionTest,
        // the invokeinterface is switched to invokevirtual.
        //
        // the test passes in the INVOKE_WITH_ARGS mode due to the fix for
        // JDK-8032010 to conform with the removal of the following check
        // during method resolution in JVMS-5.4.3.3 Method Resolution
        // "If method lookup succeeds and the method is abstract, but C is not
        // abstract, method resolution throws an AbstractMethodError."
        if (exeMode.equals("REFLECTION") ||
            exeMode.equals("INVOKE_WITH_ARGS")) {
            b.test().interfaceCallSite(A, B, "m", "()I")
             .returns(1).done()
             .run();
        } else {
            // ICCE in other modes due to
            // JVMS-5.4.3.4. Interface Method Resolution
            //   When resolving an interface method reference:
            //     If C is not an interface, interface method resolution throws an IncompatibleClassChangeError.
            b.test().interfaceCallSite(A, B, "m", "()I")
             .throws_(IncompatibleClassChangeError.class).done()
             .run();
        }
    }

    /*
     * interface I {
     *     abstract public int m();
     * }
     *
     * interface J {
     *     abstract public int m();
     * }
     *
     * class A implements I;
     *
     * class B extends A implements J;
     *
     * TEST: A o = new B(); o.m()I
     *                ICCE for DIRECT mode
     *                AME for REFLECTION and INVOKE_WITH_ARGS modes
     *                IAE for other modes
     */
    public void testInvokeInterfaceClassAbstractMethod() {
        TestBuilder b = factory.getBuilder();

        Interface I = b.intf("I")
            .abstractMethod("m", "()I").build()
            .build();

        Interface J = b.intf("J")
            .abstractMethod("m", "()I").build()
            .build();

        ConcreteClass A = b.clazz("A").implement(I).build();

        ConcreteClass B = b.clazz("B").extend(A).implement(J).build();

        String exeMode = factory.getExecutionMode();

        // ICCE in direct mode due to
        // JVMS-5.4.3.4. Interface Method Resolution
        //   When resolving an interface method reference:
        //     If C is not an interface, interface method resolution throws an IncompatibleClassChangeError.
        Class expectedError = IncompatibleClassChangeError.class;;

        b.test().interfaceCallSite(A, B, "m", "()I")
         .throws_(expectedError).done()
         .run();

    }

    /*
     * interface I {
     *     public int m() default { return 1; }
     * }
     *
     * interface J {
     *     public int m() default { return 1; }
     * }
     *
     * class A implements I;
     *
     * class B extends A implements J;
     *
     * TEST: A o = new B(); o.m()I
     *                ICCE for all modes
     */
    public void testInvokeInterfaceMultipleDefinedClassDefaultMethod() {
        TestBuilder b = factory.getBuilder();

        Interface I = b.intf("I")
            .defaultMethod("m", "()I").returns(1).build()
            .build();

        Interface J = b.intf("J")
            .defaultMethod("m", "()I").returns(1).build()
            .build();

        ConcreteClass A = b.clazz("A").implement(I).build();

        ConcreteClass B = b.clazz("B").extend(A).implement(J).build();

        String exeMode = factory.getExecutionMode();

        // ICCE in direct mode due to
        // JVMS-5.4.3.4. Interface Method Resolution
        //   When resolving an interface method reference:
        //     If C is not an interface, interface method resolution throws an IncompatibleClassChangeError.
        Class expectedError = IncompatibleClassChangeError.class;

        b.test().interfaceCallSite(A, B, "m", "()I")
         .throws_(expectedError).done()
         .run();
    }

}
