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

/**
 * @test
 * @bug 8193879 8193801 8129348
 * @summary Invokes static and instance methods when debugger trace
 * mode is on.
 * @library /lib/testlibrary
 * @run build TestScaffold VMConnection TargetListener TargetAdapter
 * @run compile -g MethodInvokeWithTraceOnTest.java
 * @run driver MethodInvokeWithTraceOnTest
 */

import com.sun.jdi.*;
import com.sun.jdi.event.*;
import com.sun.jdi.request.*;

import java.util.*;

import jdk.testlibrary.Utils;

/********** target program **********/
class MethodInvokeWithTraceOnTestTarg {
    public static void main(String[] args) {
        new MethodInvokeWithTraceOnTestTarg().test();
    }

    private void test() {
        Thread thread = Thread.currentThread();
        print(thread); // @1 breakpoint
        String str = "test";
        printStatic(str); // @2 breakpoint

    }

    public void print(Object obj) {
        System.out.println(obj);
    }

    public static void printStatic(Object obj) {
        System.out.println(obj);
    }

}


/********** test program **********/

public class MethodInvokeWithTraceOnTest extends TestScaffold {

    MethodInvokeWithTraceOnTest(String args[]) {
        super(args);
    }

    public static void main(String[] args)
            throws Exception {
        new MethodInvokeWithTraceOnTest(args).startTests();
    }

    /********** test core **********/

    protected void runTests() throws Exception {
        init();

        // Test with suspend policy set to SUSPEND_EVENT_THREAD
        BreakpointEvent be = resumeToBreakpoint(true, 1);
        System.out.println("Breakpoint 1 is hit, suspendPolicy:" + be.request().suspendPolicy());
        testMethods(be);

        // Test with suspend policy set to SUSPEND_ALL
        be = resumeToBreakpoint(false, 2);
        System.out.println("Breakpoint 2 is hit, suspendPolicy:" + be.request().suspendPolicy());
        testMethods(be);

        listenUntilVMDisconnect();
    }

    private void init() throws Exception {
        startToMain("MethodInvokeWithTraceOnTestTarg");
        vm().setDebugTraceMode(VirtualMachine.TRACE_ALL);
    }

    private BreakpointEvent resumeToBreakpoint(boolean suspendThread, int breakpointId) throws Exception {
        int bkpLine = Utils.parseBreakpoints(Utils.getTestSourcePath("MethodInvokeWithTraceOnTest.java"), breakpointId).get(0);
        System.out.println("Running to line: " + bkpLine);
        return resumeTo("MethodInvokeWithTraceOnTestTarg", bkpLine, suspendThread);
    }

    private void testMethods(BreakpointEvent be) throws Exception {
        System.out.println("Testing  methods...");
        ThreadReference thread = be.thread();
        StackFrame frame = thread.frame(0);
        ObjectReference thisObj = frame.thisObject();
        LocalVariable threadVar = frame.visibleVariableByName("thread");
        ThreadReference threadObj = (ThreadReference) frame.getValue(threadVar);
        StringReference stringObj = vm().mirrorOf("test string");
        int invokeOptions = getMethodInvokeOptions(be);

        testInstanceMethod1(thread, thisObj, stringObj, threadObj, invokeOptions);
        testStaticMethod1(thread, thisObj, stringObj, threadObj, invokeOptions);
        testStaticMethod2(thread, invokeOptions);
    }

    private void testInstanceMethod1(ThreadReference thread, ObjectReference thisObj, StringReference stringObj,
                                     ThreadReference threadObj, int invokeOptions) throws Exception {
        ClassType classType = (ClassType) thisObj.referenceType();
        Method printMethod = classType.methodsByName("print",
                "(Ljava/lang/Object;)V").get(0);

        System.out.println("Passing StringReference to instance method...");
        thisObj.invokeMethod(thread, printMethod, Collections.singletonList(stringObj), invokeOptions);

        System.out.println("Passing ThreadReference to instance method...");
        thisObj.invokeMethod(thread, printMethod, Collections.singletonList(threadObj), invokeOptions);
    }

    private void testStaticMethod1(ThreadReference thread, ObjectReference thisObj, StringReference stringObj,
                                   ThreadReference threadObj, int invokeOptions) throws Exception {
        ClassType classType = (ClassType) thisObj.referenceType();
        Method printMethod = classType.methodsByName("printStatic",
                "(Ljava/lang/Object;)V").get(0);

        System.out.println("Passing StringReference to static method...");
        classType.invokeMethod(thread, printMethod, Collections.singletonList(stringObj), invokeOptions);

        System.out.println("Passing ThreadReference to static method...");
        classType.invokeMethod(thread, printMethod, Collections.singletonList(threadObj), invokeOptions);
    }

    private void testStaticMethod2(ThreadReference thread, int invokeOptions) throws Exception {
        ClassType classType = getClassType("java.lang.Class");
        Method forNameMethod = classType.methodsByName("forName",
                "(Ljava/lang/String;)Ljava/lang/Class;").get(0);
        StringReference classNameParam = vm().mirrorOf("java.lang.String");
        classType.invokeMethod(thread, forNameMethod, Collections.singletonList(classNameParam), invokeOptions);
    }

    private ClassType getClassType(String className) {
        List classes = vm().classesByName(className);
        return (ClassType) classes.get(0);
    }

    private int getMethodInvokeOptions(BreakpointEvent be) {
        return be.request().suspendPolicy() == EventRequest.SUSPEND_EVENT_THREAD ?
                ObjectReference.INVOKE_SINGLE_THREADED : 0;
    }
}
