/*
 * Copyright (c) 2015, 2022, 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 utils;

import java.util.Map;
import java.util.Scanner;
import java.util.regex.MatchResult;

/**
 *
 * jstack default format 2008-03-05 18:36:26 Full thread dump Java HotSpot(TM)
 * Client VM (11.0-b11 mixed mode):
 *
 * "Thread-16" #10 daemon prio=3 os_prio=0 tid=0x0814d800 nid=0x1d runnable
 * [0xf394d000..0xf394d9f0] java.lang.Thread.State: RUNNABLE at
 * java.net.SocketInputStream.socketRead0(Native Method) at
 * java.net.SocketInputStream.read(SocketInputStream.java:129) at
 * java.net.SocketInputStream.read(SocketInputStream.java:182) at
 * java.io.ObjectInputStream$PeekInputStream.peek(ObjectInputStream.java:2249)
 * at
 * java.io.ObjectInputStream$BlockDataInputStream.peek(ObjectInputStream.java:2542)
 * at
 * java.io.ObjectInputStream$BlockDataInputStream.peekByte(ObjectInputStream.java:2552)
 * at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1297) at
 * java.io.ObjectInputStream.readObject(ObjectInputStream.java:351) at
 * tmtools.share.debuggee.DebuggeeProtocolHandler.run(DebuggeeProtocolHandler.java:32)
 *
 * Locked ownable synchronizers: - None ....
 *
 * Note that os_prio field is optional and will be printed only if JVM was able
 * to get native thread priority.
 */
public class DefaultFormat implements Format {

    protected String threadInfoPattern() {
        return "^\"(.*)\"\\s(#\\d+\\s|)\\[(\\d+)\\]\\s(daemon\\s|)prio=(.+)\\s(os_prio=(.+)\\s|)tid=(.+)\\snid=(.+)\\s("
                + Consts.UNKNOWN
                + "|runnable|sleeping|waiting\\son\\scondition|in\\sObject\\.wait\\(\\)|waiting\\sfor\\smonitor\\sentry)((.*))$";
    }

    protected String methodInfoPattern() {
        return "^\\s+at\\s(.+)\\((.*?)(\\:|\\))((.*?))\\)?$";
    }

    protected String extendedStatusPattern() {
        return "\\s+java\\.lang\\.Thread\\.State\\:\\s((.+))$";
    }

    protected String jniGlobalRefInfoPattern() {
        return "^JNI\\sglobal\\sreferences:\\s((.+))$";
    }

    // Sample string that matches the pattern:
    // waiting on <0x000000008f64e6d0> (a java.lang.Object)
    protected String monitorInfoPattern() {
        return "^\\s+\\-\\s(locked|waiting\\son|waiting\\sto\\slock)\\s\\<(.*)\\>\\s\\(((.*))\\)$";
    }

    // Sample string that matches the pattern:
    // waiting on <no object reference available>
    protected String monitorInfoNoObjectRefPattern() {
        return "^\\s+\\-\\s(locked|waiting\\son|waiting\\sto\\slock)\\s\\<(.*)\\>$";
    }

    protected String vmVersionInfoPattern() {
        return "Full\\sthread\\sdump\\s.*";
    }

    protected String ownableSynchronizersPattern() {
        return "^\\s+\\-\\s(\\<.*\\>\\s\\(((.*))\\)|None)$";
    }

    public JStack parse(String stack) {
        JStack result = new JStack();
        Scanner scanner = new Scanner(stack);

        // parsing thread stacks
        ThreadStack currentThreadStack = null;
        MethodInfo currentMethodInfo = null;

        try {
            while (scanner.hasNextLine()) {
                String line = scanner.nextLine();
                if (line.matches(threadInfoPattern())) {
                    currentThreadStack = parseThreadInfo(line);
                    result.addThreadStack(currentThreadStack.getThreadName(), currentThreadStack);
                } else if (line.matches(methodInfoPattern())) {
                    currentMethodInfo = parseMethodInfo(line);
                    currentThreadStack.addMethod(currentMethodInfo);
                } else if (line.matches(monitorInfoPattern())) {
                    MonitorInfo mi = parseMonitorInfo(line, monitorInfoPattern());
                    currentMethodInfo.getLocks().add(mi);
                } else if (line.matches(monitorInfoNoObjectRefPattern())) {
                    MonitorInfo mi = parseMonitorInfo(line, monitorInfoNoObjectRefPattern());
                    currentMethodInfo.getLocks().add(mi);
                } else if (line.matches(extendedStatusPattern())) {
                    currentThreadStack.setExtendedStatus(parseExtendedStatus(line));
                } else if (line.matches(vmVersionInfoPattern())) {
                    result.setVmVersion(line);
                } else if (line.matches(ownableSynchronizersPattern())) {
                    currentThreadStack.getLockOSList().add(parseLockInfo(line));
                } else if (line.matches(jniGlobalRefInfoPattern())) {
                    result.setJniGlobalReferences(parseJNIGlobalRefs(line));
                } else if (line.length() != 0) {
                    System.err.println("[Warning] Unknown string: " + line);
                }
            }

            scanner.close();

        } catch (NullPointerException e) {
            e.printStackTrace();
            throw new RuntimeException("Unexpected format in jstack output");
        }

        return result;
    }

    private MonitorInfo parseMonitorInfo(String line, String pattern) {
        Scanner s = new Scanner(line);
        s.findInLine(pattern);
        MonitorInfo mi = new MonitorInfo();
        MatchResult res = s.match();

        mi.setType(res.group(1));
        mi.setMonitorAddress(res.group(2));
        if (res.groupCount() > 2) {
            mi.setMonitorClass(res.group(3));
        }
        return mi;
    }

    protected String parseExtendedStatus(String line) {
        Scanner s = new Scanner(line);
        s.findInLine(extendedStatusPattern());
        String result = s.match().group(1);
        s.close();
        return result;
    }

    protected String parseJNIGlobalRefs(String line) {
        Scanner s = new Scanner(line);
        s.findInLine(jniGlobalRefInfoPattern());
        String result = s.match().group(1);
        s.close();
        return result;
    }

    protected ThreadStack parseThreadInfo(String threadInfo) {
        Scanner s = new Scanner(threadInfo);
        ThreadStack result = new ThreadStack();

        // parsing thread info
        s.findInLine(threadInfoPattern());
        MatchResult res = s.match();

        result.setThreadName(res.group(1));

        result.setType(res.group(4));

        result.setPriority(res.group(5));
        result.setTid(res.group(8));
        result.setNid(res.group(9));
        result.setStatus(res.group(10));

        s.close();
        return result;
    }

    protected MethodInfo parseMethodInfo(String line) {

        MethodInfo result = new MethodInfo();
        Scanner s = new Scanner(line);

        s.findInLine(methodInfoPattern());
        MatchResult rexp = s.match();
        if (rexp.group(4) != null && rexp.group(4).length() > 0) {
            // line "  at tmtools.jstack.share.utils.Utils.sleep(Utils.java:29)"
            result.setName(rexp.group(1));
            result.setCompilationUnit(rexp.group(2));
            result.setLine(rexp.group(4));

        } else {
            // line "  at java.lang.Thread.sleep(Native Method)"
            result.setName(rexp.group(1));
        }

        s.close();
        return result;
    }

    public String dumpStackTraces() {
        StringBuffer result = new StringBuffer();
        Map<Thread, StackTraceElement[]> stacks = Thread.getAllStackTraces();

        // adding data and vm version
        result.append(Consts.UNKNOWN + "\n");
        result.append(Consts.UNKNOWN + "\n\n");

        for (Thread t : stacks.keySet()) {

            result.append("\"" + t.getName() + "\"");
            result.append(Consts.SEPARATOR);

            // status
            if (t.isDaemon()) {
                result.append("daemon");
                result.append(Consts.SEPARATOR);
            }

            // priority
            result.append("prio=" + t.getPriority());
            result.append(Consts.SEPARATOR);

            // tid
            result.append("tid=" + Consts.UNKNOWN);
            result.append(Consts.SEPARATOR);

            // nid
            result.append("nid=" + Consts.UNKNOWN);
            result.append(Consts.SEPARATOR);

            // status
            result.append(Consts.UNKNOWN);
            result.append(Consts.SEPARATOR);

            result.append("\n");

            // extended status
            result.append("   java.lang.Thread.State: "
                    + String.valueOf(Thread.currentThread().getState()));
            result.append(Consts.SEPARATOR);
            result.append("\n");

            for (StackTraceElement st : stacks.get(t)) {
                result.append("  at " + st.toString() + "\n");
            }
            result.append("\n");
        }

        result.append(Consts.JNI_GLOBAL_REF + Consts.UNKNOWN + "\n");
        return result.toString();
    }

    protected LockInfo parseLockInfo(String line) {
        LockInfo res = new LockInfo();

        Scanner s = new Scanner(line);
        s.findInLine(ownableSynchronizersPattern());

        MatchResult matchRes = s.match();
        String lock = matchRes.group(1).equals("None") ? matchRes.group(1) : matchRes.group(2);
        res.setLock(lock);

        return res;
    }

}
