/*
 * Copyright (c) 2016, 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 8164086
 * @summary regression tests for 8164086, verify correct warning from checked JNI
 * @library /test/lib
 * @modules java.management
 * @run main/native TestCheckedJniExceptionCheck launch
 */

import java.util.List;
import jdk.test.lib.process.ProcessTools;
import jdk.test.lib.process.OutputAnalyzer;

public class TestCheckedJniExceptionCheck {

    static {
        System.loadLibrary("TestCheckedJniExceptionCheck");
    }

    int callableMethodInvokeCount = 0;

    static final String TEST_START           = "TEST STARTED";
    static final String EXPECT_WARNING_START = "EXPECT_WARNING_START";
    static final String EXPECT_WARNING_END   = "EXPECT_WARNING_END";

    static final String JNI_CHECK_EXCEPTION = "WARNING in native method: JNI call made without checking exceptions when required to from";

    static void printExpectWarningStart(int count) {
        System.out.println(EXPECT_WARNING_START + " " + count);
    }

    static void printExpectWarningEnd() {
        System.out.println(EXPECT_WARNING_END);
    }

    public TestCheckedJniExceptionCheck() {
        initMethodIds("callableMethod", "()V",
                      "callableNestedMethod", "(IZ)V");
        System.out.println(TEST_START);
    }

    public void test() {
        testSingleCallNoCheck();
        testSingleCallCheck();
        testSingleCallNoCheckMultipleTimes();

        testMultipleCallsNoCheck();
        testMultipleCallsCheck();

        testNestedSingleCallsNoCheck();
        testNestedSingleCallsCheck();
        testNestedMultipleCallsNoCheck();
        testNestedMultipleCallsCheck();
    }

    public void testSingleCallNoCheck() {
        System.out.println("testSingleCallNoCheck start");
        callJavaFromNative(1, false);
        System.out.println("testSingleCallNoCheck end");
    }

    public void testSingleCallCheck() {
        System.out.println("testSingleCallCheck start");
        callJavaFromNative(1, true);
        System.out.println("testSingleCallCheck end");
    }

    public void testSingleCallNoCheckMultipleTimes() {
        System.out.println("testSingleCallNoCheckMultipleTimes start");
        callJavaFromNative(1, false);
        callJavaFromNative(1, false);
        System.out.println("testSingleCallNoCheckMultipleTimes end");
    }

    public void testMultipleCallsNoCheck() {
        System.out.println("testMultipleCallsNoCheck start");
        printExpectWarningStart(1);
        callJavaFromNative(2, false);
        printExpectWarningEnd();
        System.out.println("testMultipleCallsNoCheck end");
    }

    public void testMultipleCallsCheck() {
        System.out.println("testMultipleCallsCheck start");
        callJavaFromNative(2, true);
        System.out.println("testMultipleCallsCheck end");
    }

    public void testNestedSingleCallsNoCheck() {
        System.out.println("testNestedSingleCallsNoCheck start");
        callNestedJavaFromNative(1, false);
        System.out.println("testNestedSingleCallsNoCheck end");
    }

    public void testNestedSingleCallsCheck() {
        System.out.println("testNestedSingleCallsCheck start");
        callNestedJavaFromNative(1, true);
        System.out.println("testNestedSingleCallsCheck end");
    }

    public void testNestedMultipleCallsNoCheck() {
        System.out.println("testNestedMultipleCallsNoCheck start");
        printExpectWarningStart(3);
        callNestedJavaFromNative(2, false);
        printExpectWarningEnd();
        System.out.println("testNestedMultipleCallsNoCheck end");
    }

    public void testNestedMultipleCallsCheck() {
        System.out.println("testNestedMultipleCallsCheck start");
        callNestedJavaFromNative(2, true);
        System.out.println("testNestedMultipleCallsCheck end");
    }

    public void callableMethod() {
        callableMethodInvokeCount++;
    }

    public void callableNestedMethod(int nofCalls, boolean withExceptionChecks) {
        callJavaFromNative(nofCalls, withExceptionChecks);
    }

    public native void callJavaFromNative(int nofCalls, boolean withExceptionChecks);

    public native void callNestedJavaFromNative(int nofCalls, boolean withExceptionChecks);

    private native void initMethodIds(String callableMethodName,
                                      String callableMethodSig,
                                      String callableNestedMethodName,
                                      String callableNestedMethodSig);


    // Check warnings appear where they should, with start/end statements in output...
    static void checkOuputForCorrectWarnings(OutputAnalyzer oa) throws RuntimeException {
        List<String> lines = oa.asLines();
        int expectedWarnings = 0;
        int warningCount = 0;
        int lineNo = 0;
        boolean testStartLine = false;
        for (String line : lines) {
            lineNo++;
            if (!testStartLine) { // Skip any warning before the actual test itself
                testStartLine = line.startsWith(TEST_START);
                continue;
            }
            if (line.startsWith(JNI_CHECK_EXCEPTION)) {
                if (expectedWarnings == 0) {
                    oa.reportDiagnosticSummary();
                    throw new RuntimeException("Unexpected warning at line " + lineNo);
                }
                warningCount++;
                if (warningCount > expectedWarnings) {
                    oa.reportDiagnosticSummary();
                    throw new RuntimeException("Unexpected warning at line " + lineNo);
                }
            }
            else if (line.startsWith(EXPECT_WARNING_START)) {
                String countStr = line.substring(EXPECT_WARNING_START.length() + 1);
                expectedWarnings = Integer.parseInt(countStr);
            }
            else if (line.startsWith(EXPECT_WARNING_END)) {
                if (warningCount != expectedWarnings) {
                    oa.reportDiagnosticSummary();
                    throw new RuntimeException("Missing warning at line " + lineNo);
                }
                warningCount = 0;
                expectedWarnings = 0;
            }
        }
        /*
        System.out.println("Output looks good...");
        oa.reportDiagnosticSummary();
        */
    }

    public static void main(String[] args) throws Throwable {
        if (args == null || args.length == 0) {
            new TestCheckedJniExceptionCheck().test();
            return;
        }

        // launch and check output
        checkOuputForCorrectWarnings(ProcessTools.executeTestJvm("-Xcheck:jni",
                                                                  "TestCheckedJniExceptionCheck"));
    }

}
