/**
 * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

/**
 * @test
 * @bug 8174962
 * @key closed-security
 * @run main/othervm -Xbatch PrivateInterfaceCall
 * @run main/othervm -Xbatch -XX:-TieredCompilation PrivateInterfaceCall
 */

import java.lang.invoke.*;

public class PrivateInterfaceCall {
    interface I1            { private void m(){}; }
    interface I2 extends I1 { private void m(){}; }

    static class C1 implements I2 {  }
    static class C2 implements I2 {  }
    static class C3 implements I2 {  }

    static class D1 implements I1 {  }
    static class E1               { private void m() {} }

    static void invokeDirect(I2 i) {
        i.m();
    }

    static final MethodHandle MH;
    static {
        try {
//            MH = MethodHandles.lookup().findVirtual(I2.class, "m", MethodType.methodType(void.class));
            MH = MethodHandles.lookup().findSpecial(I2.class, "m", MethodType.methodType(void.class), I2.class);
        } catch (Throwable e) {
            throw new Error(e);
        }
    }
    static void invokeMH(I2 i) throws Throwable {
        MH.invokeExact(i);
    }

    static void print(String s) {
        System.out.print(s);
    }

    static void runPositiveTests() {
        shouldNotThrow(() -> invokeDirect(new C1()));
        shouldNotThrow(() -> invokeDirect(new C2()));
        shouldNotThrow(() -> invokeDirect(new C3()));

        shouldNotThrow(() -> invokeMH(new C1()));
        shouldNotThrow(() -> invokeMH(new C2()));
        shouldNotThrow(() -> invokeMH(new C3()));
    }

    static void runNegativeTests() {
        shouldThrowICCE(() -> invokeDirect(unsafeCast(new D1())));
        shouldThrowICCE(() -> invokeDirect(unsafeCast(new E1())));

        shouldThrowICCE(() -> invokeMH(unsafeCast(new D1())));
        shouldThrowICCE(() -> invokeMH(unsafeCast(new E1())));
    }

    static void warmup() {
        for (int i = 0; i < 20_000; i++) {
            runPositiveTests();
        }
    }

    public static void main(String[] args) throws Throwable {
        System.out.println("UNRESOLVED:");
        runNegativeTests();
        runPositiveTests();

        System.out.println("RESOLVED:");
        runNegativeTests();

        warmup();

        System.out.println("COMPILED:");
        runNegativeTests();
        runPositiveTests();
    }

    static interface Test {
        void run() throws Throwable;
    }

    static void shouldThrowICCE(Test t) { shouldThrow(IncompatibleClassChangeError.class, t); }

    static void shouldThrow(Class<?> expectedError, Test t) {
        try {
            t.run();
            throw new AssertionError("No exception thrown");
        } catch(Throwable e) {
            if (expectedError.isInstance(e)) {
                // passed
                System.out.println("Thrown: " + e.getClass().getName() + ": " + e.getMessage());
            } else {
                String msg = String.format("Wrong exception thrown: expected=%s; thrown=%s",
                                           expectedError.getName(), e.getClass().getName());
                throw new AssertionError(msg);
            }

        }
    }

    static void shouldNotThrow(Test t) {
        try {
            t.run();
            // passed
        } catch(Throwable e) {
            throw new AssertionError("Exception is thrown", e);
        }
    }

    static I2 unsafeCast(Object obj) {
        try {
            MethodHandle mh = MethodHandles.identity(Object.class);
            mh = MethodHandles.explicitCastArguments(mh, mh.type().changeReturnType(I2.class));
            return (I2)mh.invokeExact((Object) obj);
        } catch (Throwable e) {
            throw new Error(e);
        }
    }
}
