/*
 * Copyright (c) 2004, 2023, 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 6202891
 * @summary TTY: Add support for method exit event return values to jdb
 * @comment converted from test/jdk/com/sun/jdi/JdbMethodExitTest.sh
 *
 * @library /test/lib
 * @compile -g JdbMethodExitTest.java
 * @run main/othervm JdbMethodExitTest
 */

import jdk.test.lib.process.OutputAnalyzer;
import lib.jdb.JdbCommand;
import lib.jdb.JdbTest;

import java.util.*;
import java.net.URLClassLoader;
import java.net.URL;
import java.util.stream.Collectors;

/*
 * This tests the jdb trace command
 */

class JdbMethodExitTestTarg {
    // These are the values that will be returned by the methods
    static URL[] urls = new URL[1];
    public static byte      byteValue = 89;
    public static char      charValue = 'x';
    public static double    doubleValue = 2.2;
    public static float     floatValue = 3.3f;
    public static int       intValue = 1;
    public static short     shortValue = 8;
    public static boolean   booleanValue = false;

    public static Class       classValue = Object.class;
    public static ClassLoader classLoaderValue;
    {
        try {
            urls[0] = new URL("file:/foo");
        } catch (java.net.MalformedURLException ee) {
        }
        classLoaderValue = new URLClassLoader(urls);
    }

    public static Thread      threadValue;
    public static ThreadGroup threadGroupValue;
    public static String      stringValue = "abc";
    public static int[]       intArrayValue = new int[] {1, 2, 3};

    public static JdbMethodExitTestTarg  objectValue =
        new JdbMethodExitTestTarg();
    public String ivar = stringValue;

    // These are the instance methods
    public byte i_bytef()            { return byteValue; }
    public char i_charf()            { return charValue; }
    public double i_doublef()        { return doubleValue; }
    public float i_floatf()          { return floatValue; }
    public int i_intf()              { return intValue; }
    public short i_shortf()          { return shortValue; }
    public boolean i_booleanf()      { return booleanValue; }
    public String i_stringf()        { return stringValue; }
    public Class i_classf()          { return classValue; }
    public ClassLoader i_classLoaderf()
                                     { return classLoaderValue; }
    public Thread i_threadf()        { return threadValue = Thread.currentThread(); }
    public ThreadGroup i_threadGroupf()
                                     { return threadGroupValue = threadValue.getThreadGroup(); }
    public int[] i_intArrayf()       { return intArrayValue; }
    public Object i_nullObjectf()    { return null; }
    public Object i_objectf()        { return objectValue; }
    public void i_voidf()            {}

    static void doit(JdbMethodExitTestTarg xx) {

        xx.i_bytef();
        xx.i_charf();
        xx.i_doublef();
        xx.i_floatf();
        xx.i_intf();
        xx.i_shortf();
        xx.i_booleanf();
        xx.i_stringf();
        xx.i_intArrayf();
        xx.i_classf();
        xx.i_classLoaderf();
        xx.i_threadf();
        xx.i_threadGroupf();
        xx.i_nullObjectf();
        xx.i_objectf();
        xx.i_voidf();

        // Prove it works for native methods too
        StrictMath.sin(doubleValue);
        stringValue.intern();
    }

    public static void bkpt() {
       int i = 0;     //@1 breakpoint
    }

    public static String traceMethods() {
        return "traceMethods";
    }

    public static String traceMethods1() {
        return "traceMethods1";
    }

    public static String traceExits() {
        return "traceExits";
    }

    public static String traceExits1() {
        return "traceExits1";
    }

    public static String traceExit() {
        return "traceExit";
    }

    public static String traceExit1() {
        return "traceExit1";
    }

    public static void main(String[] args) {
        // The debugger will stop at the start of main,
        // enable method exit events, and then do
        // a resume.

        JdbMethodExitTestTarg xx = new JdbMethodExitTestTarg();
        bkpt();

        // test all possible return types
        doit(xx);
        bkpt();

       // test trace methods
       traceMethods();

       // test trace go methods
       traceMethods1();
       bkpt();

       // test trace method exits
       traceExits();

       // test trace method exits
       traceExits1();
       bkpt();

       // test trace method exit
       traceExit();

       // test trace method exit
       traceExit1();
       bkpt();

    }
}

public class JdbMethodExitTest extends JdbTest {
    public static void main(String argv[]) {
        new JdbMethodExitTest().run();
    }

    private JdbMethodExitTest() {
        super(DEBUGGEE_CLASS);
    }

    private static final String DEBUGGEE_CLASS = JdbMethodExitTestTarg.class.getName();

    @Override
    protected void runCases() {
        setBreakpointsFromTestSource("JdbMethodExitTest.java", 1);

        // test all possible return types
        execCommand(JdbCommand.run())
                .shouldContain("Breakpoint hit");

        // In order to find the main threadId, we need to parse a line from the "threads"
        // command output that looks something like:
        //    (java.lang.VirtualThread)694            main            running (at breakpoint)
        OutputAnalyzer o = execCommand(JdbCommand.threads());
        String match = o.firstMatch("^\\s*\\(.+\\)(\\d+)\\s+main\\s+running.+$", 1);
        Integer threadId = Integer.parseInt(match);

        jdb.command(JdbCommand.untrace());

        jdb.command(JdbCommand.traceMethods(false, null));
        execCommand(JdbCommand.trace())
                .shouldContain("trace methods in effect");

        jdb.command(JdbCommand.traceMethods(true, null));
        execCommand(JdbCommand.trace())
                .shouldContain("trace go methods in effect");

        jdb.command(JdbCommand.traceMethodExits(false, null));
        execCommand(JdbCommand.trace())
                .shouldContain("trace method exits in effect");

        jdb.command(JdbCommand.traceMethodExits(true, null));
        execCommand(JdbCommand.trace())
                .shouldContain("trace go method exits in effect");

        jdb.command(JdbCommand.traceMethodExit(false, null));
        execCommand(JdbCommand.trace())
                .shouldContain("trace method exit in effect for JdbMethodExitTestTarg.bkpt");

        jdb.command(JdbCommand.traceMethodExit(true, null));
        execCommand(JdbCommand.trace())
                .shouldContain("trace go method exit in effect for JdbMethodExitTestTarg.bkpt");


        // trace exit of methods with all the return values
        // (but just check a couple of them)
        jdb.command(JdbCommand.traceMethodExits(true, threadId));
        execCommand(JdbCommand.cont())
                .shouldContain("instance of JdbMethodExitTestTarg")
                .shouldContain("return value = 8");

        // Get out of bkpt back to the call to traceMethods
        jdb.command(JdbCommand.stepUp());


        jdb.command(JdbCommand.traceMethods(false, threadId));
        execCommand(JdbCommand.cont())
                .shouldContain("Method entered:");
        execCommand(JdbCommand.cont())
                .shouldContain("Method exited: return value = \"traceMethods\"");
        jdb.command(JdbCommand.stepUp());


        List<String> reply = new LinkedList<>();
        reply.addAll(jdb.command(JdbCommand.traceMethods(true, threadId)));
        reply.addAll(jdb.command(JdbCommand.cont()));
        reply.addAll(jdb.command(JdbCommand.cont()));
        reply.addAll(jdb.command(JdbCommand.cont()));
        new OutputAnalyzer(reply.stream().collect(Collectors.joining(lineSeparator)))
                .shouldContain("Method entered: \"thread=main\", JdbMethodExitTestTarg.traceMethods1")
                .shouldMatch("Method exited: .* JdbMethodExitTestTarg.traceMethods1");
        jdb.command(JdbCommand.untrace());
        jdb.command(JdbCommand.stepUp());


        reply.clear();
        reply.addAll(jdb.command(JdbCommand.traceMethodExits(false, threadId)));
        reply.addAll(jdb.command(JdbCommand.cont()));
        new OutputAnalyzer(reply.stream().collect(Collectors.joining(lineSeparator)))
                .shouldContain("Method exited: return value = \"traceExits\"");
        jdb.command(JdbCommand.untrace());
        jdb.command(JdbCommand.stepUp());


        reply.clear();
        reply.addAll(jdb.command(JdbCommand.traceMethodExits(true, threadId)));
        reply.addAll(jdb.command(JdbCommand.cont()));
        new OutputAnalyzer(reply.stream().collect(Collectors.joining(lineSeparator)))
                .shouldMatch("Method exited: .* JdbMethodExitTestTarg.traceExits1");
        jdb.command(JdbCommand.untrace());
        jdb.command(JdbCommand.stepUp());


        reply.clear();
        reply.addAll(jdb.command(JdbCommand.step()));   // step into traceExit()
        reply.addAll(jdb.command(JdbCommand.traceMethodExit(false, threadId)));
        reply.addAll(jdb.command(JdbCommand.cont()));
        new OutputAnalyzer(reply.stream().collect(Collectors.joining(lineSeparator)))
                .shouldContain("Method exited: return value = \"traceExit\"");
        jdb.command(JdbCommand.untrace());
        jdb.command(JdbCommand.stepUp());


        reply.clear();
        reply.addAll(jdb.command(JdbCommand.step()));
        reply.addAll(jdb.command(JdbCommand.step()));   // skip over setting return value in caller :-(
        reply.addAll(jdb.command(JdbCommand.traceMethodExit(true, threadId)));
        reply.addAll(jdb.command(JdbCommand.cont()));
        new OutputAnalyzer(reply.stream().collect(Collectors.joining(lineSeparator)))
                .shouldMatch("Method exited: .*JdbMethodExitTestTarg.traceExit1");

        new OutputAnalyzer(getJdbOutput())
                .shouldContain("Breakpoint hit");
    }
}
