/*
 * Copyright (c) 1998, 2001, 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.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * 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 com.sun.tools.example.debug.gui;

import java.io.*;
import java.util.*;

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

import com.sun.tools.example.debug.bdi.*;

public class CommandInterpreter {

    boolean echo;

    Environment env;

    private ContextManager context;
    private ExecutionManager runtime;
    private ClassManager classManager;
    private SourceManager sourceManager;

    private OutputSink out; //### Hack!  Should be local in each method used.
    private String lastCommand = "help";

    public CommandInterpreter(Environment env) {
        this(env, true);
    }

    public CommandInterpreter(Environment env, boolean echo) {
        this.env = env;
        this.echo = echo;
        this.runtime = env.getExecutionManager();
        this.context = env.getContextManager();
        this.classManager = env.getClassManager();
        this.sourceManager = env.getSourceManager();
    }

    private ThreadReference[] threads = null;

    /*
     * The numbering of threads is relative to the current set of threads,
     * and may be affected by the creation and termination of new threads.
     * Commands issued using such thread ids will only give reliable behavior
     * relative to what was shown earlier in 'list' commands if the VM is interrupted.
     * We need a better scheme.
     */

    private ThreadReference[] threads() throws NoSessionException {
        if (threads == null) {
            ThreadIterator ti = new ThreadIterator(getDefaultThreadGroup());
            List<ThreadReference> tlist = new ArrayList<ThreadReference>();
            while (ti.hasNext()) {
                tlist.add(ti.nextThread());
            }
            threads = tlist.toArray(new ThreadReference[tlist.size()]);
        }
        return threads;
    }

    private ThreadReference findThread(String idToken) throws NoSessionException {
        String id;
        ThreadReference thread = null;
        if (idToken.startsWith("t@")) {
            id = idToken.substring(2);
        } else {
            id = idToken;
        }
        try {
            ThreadReference[] threads = threads();
            long threadID = Long.parseLong(id, 16);
            for (int i = 0; i < threads.length; i++) {
                if (threads[i].uniqueID() == threadID) {
                    thread = threads[i];
                    break;
                }
            }
            if (thread == null) {
                //env.failure("No thread for id \"" + idToken + "\"");
                env.failure("\"" + idToken + "\" is not a valid thread id.");
            }
        } catch (NumberFormatException e) {
            env.error("Thread id \"" + idToken + "\" is ill-formed.");
            thread = null;
        }
        return thread;
    }

    private ThreadIterator allThreads() throws NoSessionException {
        threads = null;
        //### Why not use runtime.allThreads().iterator() ?
        return new ThreadIterator(runtime.topLevelThreadGroups());
    }

    private ThreadIterator currentThreadGroupThreads() throws NoSessionException {
        threads = null;
        return new ThreadIterator(getDefaultThreadGroup());
    }

    private ThreadGroupIterator allThreadGroups() throws NoSessionException {
        threads = null;
        return new ThreadGroupIterator(runtime.topLevelThreadGroups());
    }

    private ThreadGroupReference defaultThreadGroup;

    private ThreadGroupReference getDefaultThreadGroup() throws NoSessionException {
        if (defaultThreadGroup == null) {
            defaultThreadGroup = runtime.systemThreadGroup();
        }
        return defaultThreadGroup;
    }

    private void setDefaultThreadGroup(ThreadGroupReference tg) {
        defaultThreadGroup = tg;
    }

    /*
     * Command handlers.
     */

    // Command: classes

    private void commandClasses() throws NoSessionException {
        OutputSink out = env.getOutputSink();
        //out.println("** classes list **");
        for (ReferenceType refType : runtime.allClasses()) {
            out.println(refType.name());
        }
        out.show();
    }


    // Command: methods

    private void commandMethods(StringTokenizer t) throws NoSessionException {
        if (!t.hasMoreTokens()) {
            env.error("No class specified.");
            return;
        }
        String idClass = t.nextToken();
        ReferenceType cls = findClass(idClass);
        if (cls != null) {
            List<Method> methods = cls.allMethods();
            OutputSink out = env.getOutputSink();
            for (int i = 0; i < methods.size(); i++) {
                Method method = methods.get(i);
                out.print(method.declaringType().name() + " " +
                            method.name() + "(");
                Iterator<String> it = method.argumentTypeNames().iterator();
                if (it.hasNext()) {
                    while (true) {
                        out.print(it.next());
                        if (!it.hasNext()) {
                            break;
                        }
                        out.print(", ");
                    }
                }
                out.println(")");
            }
            out.show();
        } else {
            //### Should validate class name syntax.
            env.failure("\"" + idClass + "\" is not a valid id or class name.");
        }
    }

    private ReferenceType findClass(String pattern) throws NoSessionException {
        List<ReferenceType> results = runtime.findClassesMatchingPattern(pattern);
        if (results.size() > 0) {
            //### Should handle multiple results sensibly.
            return results.get(0);
        }
        return null;
    }

    // Command: threads

    private void commandThreads(StringTokenizer t) throws NoSessionException {
        if (!t.hasMoreTokens()) {
            OutputSink out = env.getOutputSink();
            printThreadGroup(out, getDefaultThreadGroup(), 0);
            out.show();
            return;
        }
        String name = t.nextToken();
        ThreadGroupReference tg = findThreadGroup(name);
        if (tg == null) {
            env.failure(name + " is not a valid threadgroup name.");
        } else {
            OutputSink out = env.getOutputSink();
            printThreadGroup(out, tg, 0);
            out.show();
        }
    }

    private ThreadGroupReference findThreadGroup(String name) throws NoSessionException {
        //### Issue: Uniqueness of thread group names is not enforced.
        ThreadGroupIterator tgi = allThreadGroups();
        while (tgi.hasNext()) {
            ThreadGroupReference tg = tgi.nextThreadGroup();
            if (tg.name().equals(name)) {
                return tg;
            }
        }
        return null;
    }

    private int printThreadGroup(OutputSink out, ThreadGroupReference tg, int iThread) {
        out.println("Group " + tg.name() + ":");
        List<ThreadReference> tlist = tg.threads();
        int maxId = 0;
        int maxName = 0;
        for (int i = 0 ; i < tlist.size() ; i++) {
            ThreadReference thr = tlist.get(i);
            int len = Utils.description(thr).length();
            if (len > maxId)
                maxId = len;
            String name = thr.name();
            int iDot = name.lastIndexOf('.');
            if (iDot >= 0 && name.length() > iDot) {
                name = name.substring(iDot + 1);
            }
            if (name.length() > maxName)
                maxName = name.length();
        }
        String maxNumString = String.valueOf(iThread + tlist.size());
        int maxNumDigits = maxNumString.length();
        for (int i = 0 ; i < tlist.size() ; i++) {
            ThreadReference thr = tlist.get(i);
            char buf[] = new char[80];
            for (int j = 0; j < 79; j++) {
                buf[j] = ' ';
            }
            buf[79] = '\0';
            StringBuffer sbOut = new StringBuffer();
            sbOut.append(buf);

            // Right-justify the thread number at start of output string
            String numString = String.valueOf(iThread + i + 1);
            sbOut.insert(maxNumDigits - numString.length(),
                         numString);
            sbOut.insert(maxNumDigits, ".");

            int iBuf = maxNumDigits + 2;
            sbOut.insert(iBuf, Utils.description(thr));
            iBuf += maxId + 1;
            String name = thr.name();
            int iDot = name.lastIndexOf('.');
            if (iDot >= 0 && name.length() > iDot) {
                name = name.substring(iDot + 1);
            }
            sbOut.insert(iBuf, name);
            iBuf += maxName + 1;
            sbOut.insert(iBuf, Utils.getStatus(thr));
            sbOut.setLength(79);
            out.println(sbOut.toString());
        }
        for (ThreadGroupReference tg0 : tg.threadGroups()) {
            if (!tg.equals(tg0)) {  // TODO ref mgt
                iThread += printThreadGroup(out, tg0, iThread + tlist.size());
            }
        }
        return tlist.size();
    }

    // Command: threadgroups

    private void commandThreadGroups() throws NoSessionException {
        ThreadGroupIterator it = allThreadGroups();
        int cnt = 0;
        OutputSink out = env.getOutputSink();
        while (it.hasNext()) {
            ThreadGroupReference tg = it.nextThreadGroup();
            ++cnt;
            out.println("" + cnt + ". " + Utils.description(tg) + " " + tg.name());
        }
        out.show();
    }

    // Command: thread

    private void commandThread(StringTokenizer t) throws NoSessionException {
        if (!t.hasMoreTokens()) {
            env.error("Thread number not specified.");
            return;
        }
        ThreadReference thread = findThread(t.nextToken());
        if (thread != null) {
            //### Should notify user.
            context.setCurrentThread(thread);
        }
    }

    // Command: threadgroup

    private void commandThreadGroup(StringTokenizer t) throws NoSessionException {
        if (!t.hasMoreTokens()) {
            env.error("Threadgroup name not specified.");
            return;
        }
        String name = t.nextToken();
        ThreadGroupReference tg = findThreadGroup(name);
        if (tg == null) {
            env.failure(name + " is not a valid threadgroup name.");
        } else {
            //### Should notify user.
            setDefaultThreadGroup(tg);
        }
    }

    // Command: run

    private void commandRun(StringTokenizer t) throws NoSessionException {
        if (doLoad(false, t)) {
            env.notice("Running ...");
        }
    }

    // Command: load

    private void commandLoad(StringTokenizer t) throws NoSessionException {
        if (doLoad(true, t)) {}
    }

    private boolean doLoad(boolean suspended,
                           StringTokenizer t) throws NoSessionException {

        String clname;

        if (!t.hasMoreTokens()) {
            clname = context.getMainClassName();
            if (!clname.equals("")) {
                // Run from prevously-set class name.
                try {
                    String vmArgs = context.getVmArguments();
                    runtime.run(suspended,
                                vmArgs,
                                clname,
                                context.getProgramArguments());
                    return true;
                } catch (VMLaunchFailureException e) {
                    env.failure("Attempt to launch main class \"" + clname + "\" failed.");
                }
            } else {
                env.failure("No main class specifed and no current default defined.");
            }
        } else {
            clname = t.nextToken();
            StringBuffer sbuf = new StringBuffer();
            // Allow VM arguments to be specified here?
            while (t.hasMoreTokens()) {
                String tok = t.nextToken();
                sbuf.append(tok);
                if (t.hasMoreTokens()) {
                    sbuf.append(' ');
                }
            }
            String args = sbuf.toString();
            try {
                String vmArgs = context.getVmArguments();
                runtime.run(suspended, vmArgs, clname, args);
                context.setMainClassName(clname);
                //context.setVmArguments(vmArgs);
                context.setProgramArguments(args);
                return true;
            } catch (VMLaunchFailureException e) {
                env.failure("Attempt to launch main class \"" + clname + "\" failed.");
            }
        }
        return false;
    }

    // Command: connect

    private void commandConnect(StringTokenizer t) {
        try {
            LaunchTool.queryAndLaunchVM(runtime);
        } catch (VMLaunchFailureException e) {
            env.failure("Attempt to connect failed.");
        }
    }

    // Command: attach

    private void commandAttach(StringTokenizer t) {
        String portName;
        if (!t.hasMoreTokens()) {
            portName = context.getRemotePort();
            if (!portName.equals("")) {
                try {
                    runtime.attach(portName);
                } catch (VMLaunchFailureException e) {
                    env.failure("Attempt to attach to port \"" + portName + "\" failed.");
                }
            } else {
                env.failure("No port specifed and no current default defined.");
            }
        } else {
            portName = t.nextToken();
            try {
                runtime.attach(portName);
            } catch (VMLaunchFailureException e) {
                env.failure("Attempt to attach to port \"" + portName + "\" failed.");
            }
            context.setRemotePort(portName);
        }
    }

    // Command: detach

    private void commandDetach(StringTokenizer t) throws NoSessionException {
        runtime.detach();
    }

    // Command: interrupt

    private void commandInterrupt(StringTokenizer t) throws NoSessionException {
        runtime.interrupt();
    }

    // Command: suspend

    private void commandSuspend(StringTokenizer t) throws NoSessionException {
        if (!t.hasMoreTokens()) {
            // Suspend all threads in the current thread group.
            //### Issue: help message says default is all threads.
            //### Behavior here agrees with 'jdb', however.
            ThreadIterator ti = currentThreadGroupThreads();
            while (ti.hasNext()) {
                // TODO - don't suspend debugger threads
                ti.nextThread().suspend();
            }
            env.notice("All (non-system) threads suspended.");
        } else {
            while (t.hasMoreTokens()) {
                ThreadReference thread = findThread(t.nextToken());
                if (thread != null) {
                    //thread.suspend();
                    runtime.suspendThread(thread);
                }
            }
        }
    }

    // Command: resume

    private void commandResume(StringTokenizer t) throws NoSessionException {
         if (!t.hasMoreTokens()) {
            // Suspend all threads in the current thread group.
            //### Issue: help message says default is all threads.
            //### Behavior here agrees with 'jdb', however.
            ThreadIterator ti = currentThreadGroupThreads();
            while (ti.hasNext()) {
                // TODO - don't suspend debugger threads
                ti.nextThread().resume();
            }
            env.notice("All threads resumed.");
         } else {
             while (t.hasMoreTokens()) {
                ThreadReference thread = findThread(t.nextToken());
                if (thread != null) {
                    //thread.resume();
                    runtime.resumeThread(thread);
                }
             }
         }
    }

    // Command: cont

    private void commandCont() throws NoSessionException {
        try {
            runtime.go();
        } catch (VMNotInterruptedException e) {
            //### failure?
            env.notice("Target VM is already running.");
        }
    }

    // Command: step

    private void commandStep(StringTokenizer t) throws NoSessionException{
        ThreadReference current = context.getCurrentThread();
        if (current == null) {
            env.failure("No current thread.");
            return;
        }
        try {
            if (t.hasMoreTokens() &&
                t.nextToken().toLowerCase().equals("up")) {
                runtime.stepOut(current);
            } else {
                runtime.stepIntoLine(current);
            }
        } catch (AbsentInformationException e) {
            env.failure("No linenumber information available -- " +
                            "Try \"stepi\" to step by instructions.");
        }
    }

    // Command: stepi

    private void commandStepi() throws NoSessionException {
        ThreadReference current = context.getCurrentThread();
        if (current == null) {
            env.failure("No current thread.");
            return;
        }
        runtime.stepIntoInstruction(current);
    }

    // Command: next

    private void commandNext() throws NoSessionException {
        ThreadReference current = context.getCurrentThread();
        if (current == null) {
            env.failure("No current thread.");
            return;
        }
        try {
            runtime.stepOverLine(current);
        } catch (AbsentInformationException e) {
            env.failure("No linenumber information available -- " +
                            "Try \"nexti\" to step by instructions.");
        }
    }

    // Command: nexti  (NEW)

    private void commandNexti() throws NoSessionException {
        ThreadReference current = context.getCurrentThread();
        if (current == null) {
            env.failure("No current thread.");
            return;
        }
        runtime.stepOverInstruction(current);
    }

    // Command: kill

    private void commandKill(StringTokenizer t) throws NoSessionException {
        //### Should change the way in which thread ids and threadgroup names
        //### are distinguished.
         if (!t.hasMoreTokens()) {
            env.error("Usage: kill <threadgroup name> or <thread id>");
            return;
        }
        while (t.hasMoreTokens()) {
            String idToken = t.nextToken();
            ThreadReference thread = findThread(idToken);
            if (thread != null) {
                runtime.stopThread(thread);
                env.notice("Thread " + thread.name() + " killed.");
                return;
            } else {
                /* Check for threadgroup name, NOT skipping "system". */
                //### Should skip "system"?  Classic 'jdb' does this.
                //### Should deal with possible non-uniqueness of threadgroup names.
                ThreadGroupIterator itg = allThreadGroups();
                while (itg.hasNext()) {
                    ThreadGroupReference tg = itg.nextThreadGroup();
                    if (tg.name().equals(idToken)) {
                        ThreadIterator it = new ThreadIterator(tg);
                        while (it.hasNext()) {
                            runtime.stopThread(it.nextThread());
                        }
                        env.notice("Threadgroup " + tg.name() + "killed.");
                        return;
                    }
                }
                env.failure("\"" + idToken +
                            "\" is not a valid threadgroup or id.");
            }
        }
    }


    
// TODO private void commandCatchException(StringTokenizer t) throws NoSessionException {} // TODO private void commandIgnoreException(StringTokenizer t) throws NoSessionException {}
/************* // TODO private void commandCatchException(StringTokenizer t) throws NoSessionException {} // TODO private void commandIgnoreException(StringTokenizer t) throws NoSessionException {} *************/
// Command: up //### Print current frame after command? int readCount(StringTokenizer t) { int cnt = 1; if (t.hasMoreTokens()) { String idToken = t.nextToken(); int n; try { cnt = Integer.valueOf(idToken).intValue(); } catch (NumberFormatException e) { cnt = -1; } } return cnt; } void commandUp(StringTokenizer t) throws NoSessionException { ThreadReference current = context.getCurrentThread(); if (current == null) { env.failure("No current thread."); return; } int nLevels = readCount(t); if (nLevels <= 0) { env.error("usage: up [n frames]"); return; } try { int delta = context.moveCurrentFrameIndex(current, -nLevels); if (delta == 0) { env.notice("Already at top of stack."); } else if (-delta < nLevels) { env.notice("Moved up " + delta + " frames to top of stack."); } } catch (VMNotInterruptedException e) { env.failure("Target VM must be in interrupted state."); } } private void commandDown(StringTokenizer t) throws NoSessionException { ThreadReference current = context.getCurrentThread(); if (current == null) { env.failure("No current thread."); return; } int nLevels = readCount(t); if (nLevels <= 0) { env.error("usage: down [n frames]"); return; } try { int delta = context.moveCurrentFrameIndex(current, nLevels); if (delta == 0) { env.notice("Already at bottom of stack."); } else if (delta < nLevels) { env.notice("Moved down " + delta + " frames to bottom of stack."); } } catch (VMNotInterruptedException e) { env.failure("Target VM must be in interrupted state."); } } // Command: frame private void commandFrame(StringTokenizer t) throws NoSessionException { ThreadReference current = context.getCurrentThread(); if (current == null) { env.failure("No current thread."); return; } if (!t.hasMoreTokens()) { env.error("usage: frame <frame-index>"); return; } String idToken = t.nextToken(); int n; try { n = Integer.valueOf(idToken).intValue(); } catch (NumberFormatException e) { n = 0; } if (n <= 0) { env.error("use positive frame index"); return; } try { int delta = context.setCurrentFrameIndex(current, n); if (delta == 0) { env.notice("Frame unchanged."); } else if (delta < 0) { env.notice("Moved up " + -delta + " frames."); } else { env.notice("Moved down " + delta + " frames."); } } catch (VMNotInterruptedException e) { env.failure("Target VM must be in interrupted state."); } } // Command: where //### Should we insist that VM be interrupted here? //### There is an inconsistency between the 'where' command //### and 'up' and 'down' in this respect. private void commandWhere(StringTokenizer t, boolean showPC) throws NoSessionException { ThreadReference current = context.getCurrentThread(); if (!t.hasMoreTokens()) { if (current == null) { env.error("No thread specified."); return; } dumpStack(current, showPC); } else { String token = t.nextToken(); if (token.toLowerCase().equals("all")) { ThreadIterator it = allThreads(); while (it.hasNext()) { ThreadReference thread = it.next(); out.println(thread.name() + ": "); dumpStack(thread, showPC); } } else { ThreadReference thread = findThread(t.nextToken()); //### Do we want to set current thread here? //### Should notify user of change. if (thread != null) { context.setCurrentThread(thread); } dumpStack(thread, showPC); } } } private void dumpStack(ThreadReference thread, boolean showPC) { //### Check for these. //env.failure("Thread no longer exists."); //env.failure("Target VM must be in interrupted state."); //env.failure("Current thread isn't suspended."); //### Should handle extremely long stack traces sensibly for user. List<StackFrame> stack = null; try { stack = thread.frames(); } catch (IncompatibleThreadStateException e) { env.failure("Thread is not suspended."); } //### Fix this! //### Previously mishandled cases where thread was not current. //### Now, prints all of the stack regardless of current frame. int frameIndex = 0; //int frameIndex = context.getCurrentFrameIndex(); if (stack == null) { env.failure("Thread is not running (no stack)."); } else { OutputSink out = env.getOutputSink(); int nFrames = stack.size(); for (int i = frameIndex; i < nFrames; i++) { StackFrame frame = stack.get(i); Location loc = frame.location(); Method meth = loc.method(); out.print(" [" + (i + 1) + "] "); out.print(meth.declaringType().name()); out.print('.'); out.print(meth.name()); out.print(" ("); if (meth.isNative()) { out.print("native method"); } else if (loc.lineNumber() != -1) { try { out.print(loc.sourceName()); } catch (AbsentInformationException e) { out.print("<unknown>"); } out.print(':'); out.print(loc.lineNumber()); } out.print(')'); if (showPC) { long pc = loc.codeIndex(); if (pc != -1) { out.print(", pc = " + pc); } } out.println(); } out.show(); } } private void listEventRequests() throws NoSessionException { // Print set breakpoints List<EventRequestSpec> specs = runtime.eventRequestSpecs(); if (specs.isEmpty()) { env.notice("No breakpoints/watchpoints/exceptions set."); } else { OutputSink out = env.getOutputSink(); out.println("Current breakpoints/watchpoints/exceptions set:"); for (EventRequestSpec bp : specs) { out.println("\t" + bp); } out.show(); } } private BreakpointSpec parseBreakpointSpec(String bptSpec) { StringTokenizer t = new StringTokenizer(bptSpec); BreakpointSpec bpSpec = null; // try { String token = t.nextToken("@:( \t\n\r"); // We can't use hasMoreTokens here because it will cause any leading // paren to be lost. String rest; try { rest = t.nextToken("").trim(); } catch (NoSuchElementException e) { rest = null; } if ((rest != null) && rest.startsWith("@")) { t = new StringTokenizer(rest.substring(1)); String sourceName = token; String lineToken = t.nextToken(); int lineNumber = Integer.valueOf(lineToken).intValue(); if (t.hasMoreTokens()) { return null; } bpSpec = runtime.createSourceLineBreakpoint(sourceName, lineNumber); } else if ((rest != null) && rest.startsWith(":")) { t = new StringTokenizer(rest.substring(1)); String classId = token; String lineToken = t.nextToken(); int lineNumber = Integer.valueOf(lineToken).intValue(); if (t.hasMoreTokens()) { return null; } bpSpec = runtime.createClassLineBreakpoint(classId, lineNumber); } else { // Try stripping method from class.method token. int idot = token.lastIndexOf("."); if ( (idot <= 0) || /* No dot or dot in first char */ (idot >= token.length() - 1) ) { /* dot in last char */ return null; } String methodName = token.substring(idot + 1); String classId = token.substring(0, idot); List<String> argumentList = null; if (rest != null) { if (!rest.startsWith("(") || !rest.endsWith(")")) { //### Should throw exception with error message //out.println("Invalid method specification: " // + methodName + rest); return null; } // Trim the parens //### What about spaces in arglist? rest = rest.substring(1, rest.length() - 1); argumentList = new ArrayList<String>(); t = new StringTokenizer(rest, ","); while (t.hasMoreTokens()) { argumentList.add(t.nextToken()); } } bpSpec = runtime.createMethodBreakpoint(classId, methodName, argumentList); } // } catch (Exception e) { // env.error("Exception attempting to create breakpoint: " + e); // return null; // } return bpSpec; } private void commandStop(StringTokenizer t) throws NoSessionException { Location bploc; String token; if (!t.hasMoreTokens()) { listEventRequests(); } else { token = t.nextToken(); // Ignore optional "at" or "in" token. // Allowed for backward compatibility. if (token.equals("at") || token.equals("in")) { if (t.hasMoreTokens()) { token = t.nextToken(); } else { env.error("Missing breakpoint specification."); return; } } BreakpointSpec bpSpec = parseBreakpointSpec(token); if (bpSpec != null) { //### Add sanity-checks for deferred breakpoint. runtime.install(bpSpec); } else { env.error("Ill-formed breakpoint specification."); } } } private void commandClear(StringTokenizer t) throws NoSessionException { if (!t.hasMoreTokens()) { // Print set breakpoints listEventRequests(); return; } //### need 'clear all' BreakpointSpec bpSpec = parseBreakpointSpec(t.nextToken()); if (bpSpec != null) { List<EventRequestSpec> specs = runtime.eventRequestSpecs(); if (specs.isEmpty()) { env.notice("No breakpoints set."); } else { List<EventRequestSpec> toDelete = new ArrayList<EventRequestSpec>(); for (EventRequestSpec spec : specs) { if (spec.equals(bpSpec)) { toDelete.add(spec); } } // The request used for matching should be found if (toDelete.size() <= 1) { env.notice("No matching breakpoint set."); } for (EventRequestSpec spec : toDelete) { runtime.delete(spec); } } } else { env.error("Ill-formed breakpoint specification."); } } // Command: list private void commandList(StringTokenizer t) throws NoSessionException { ThreadReference current = context.getCurrentThread(); if (current == null) { env.error("No thread specified."); return; } Location loc; try { StackFrame frame = context.getCurrentFrame(current); if (frame == null) { env.failure("Thread has not yet begun execution."); return; } loc = frame.location(); } catch (VMNotInterruptedException e) { env.failure("Target VM must be in interrupted state."); return; } SourceModel source = sourceManager.sourceForLocation(loc); if (source == null) { if (loc.method().isNative()) { env.failure("Current method is native."); return; } env.failure("No source available for " + Utils.locationString(loc) + "."); return; } ReferenceType refType = loc.declaringType(); int lineno = loc.lineNumber(); if (t.hasMoreTokens()) { String id = t.nextToken(); // See if token is a line number. try { lineno = Integer.valueOf(id).intValue(); } catch (NumberFormatException nfe) { // It isn't -- see if it's a method name. List<Method> meths = refType.methodsByName(id); if (meths == null || meths.size() == 0) { env.failure(id + " is not a valid line number or " + "method name for class " + refType.name()); return; } else if (meths.size() > 1) { env.failure(id + " is an ambiguous method name in" + refType.name()); return; } loc = meths.get(0).location(); lineno = loc.lineNumber(); } } int startLine = (lineno > 4) ? lineno - 4 : 1; int endLine = startLine + 9; String sourceLine = source.sourceLine(lineno); if (sourceLine == null) { env.failure("" + lineno + " is an invalid line number for " + refType.name()); } else { OutputSink out = env.getOutputSink(); for (int i = startLine; i <= endLine; i++) { sourceLine = source.sourceLine(i); if (sourceLine == null) { break; } out.print(i); out.print("\t"); if (i == lineno) { out.print("=> "); } else { out.print(" "); } out.println(sourceLine); } out.show(); } } // Command: use // Get or set the source file path list. private void commandUse(StringTokenizer t) { if (!t.hasMoreTokens()) { out.println(sourceManager.getSourcePath().asString()); } else { //### Should throw exception for invalid path. //### E.g., vetoable property change. sourceManager.setSourcePath(new SearchPath(t.nextToken())); } } // Command: sourcepath // Get or set the source file path list. (Alternate to 'use'.) private void commandSourcepath(StringTokenizer t) { if (!t.hasMoreTokens()) { out.println(sourceManager.getSourcePath().asString()); } else { //### Should throw exception for invalid path. //### E.g., vetoable property change. sourceManager.setSourcePath(new SearchPath(t.nextToken())); } } // Command: classpath // Get or set the class file path list. private void commandClasspath(StringTokenizer t) { if (!t.hasMoreTokens()) { out.println(classManager.getClassPath().asString()); } else { //### Should throw exception for invalid path. //### E.g., vetoable property change. classManager.setClassPath(new SearchPath(t.nextToken())); } } // Command: view // Display source for source file or class. private void commandView(StringTokenizer t) throws NoSessionException { if (!t.hasMoreTokens()) { env.error("Argument required"); } else { String name = t.nextToken(); if (name.endsWith(".java") || name.indexOf(File.separatorChar) >= 0) { env.viewSource(name); } else { //### JDI crashes taking line number for class. /***** ReferenceType cls = findClass(name); if (cls != null) { env.viewLocation(cls.location()); } else { env.failure("No such class"); } *****/ String fileName = name.replace('.', File.separatorChar) + ".java"; env.viewSource(fileName); } } } // Command: locals // Print all local variables in current stack frame. private void commandLocals() throws NoSessionException { ThreadReference current = context.getCurrentThread(); if (current == null) { env.failure("No default thread specified: " + "use the \"thread\" command first."); return; } StackFrame frame; try { frame = context.getCurrentFrame(current); if (frame == null) { env.failure("Thread has not yet created any stack frames."); return; } } catch (VMNotInterruptedException e) { env.failure("Target VM must be in interrupted state."); return; } List<LocalVariable> vars; try { vars = frame.visibleVariables(); if (vars == null || vars.size() == 0) { env.failure("No local variables"); return; } } catch (AbsentInformationException e) { env.failure("Local variable information not available." + " Compile with -g to generate variable information"); return; } OutputSink out = env.getOutputSink(); out.println("Method arguments:"); for (LocalVariable var : vars) { if (var.isArgument()) { printVar(out, var, frame); } } out.println("Local variables:"); for (LocalVariable var : vars) { if (!var.isArgument()) { printVar(out, var, frame); } } out.show(); return; }
Command: monitor Monitor an expression
/** * Command: monitor * Monitor an expression */
private void commandMonitor(StringTokenizer t) throws NoSessionException { if (!t.hasMoreTokens()) { env.error("Argument required"); } else { env.getMonitorListModel().add(t.nextToken("")); } }
Command: unmonitor Unmonitor an expression
/** * Command: unmonitor * Unmonitor an expression */
private void commandUnmonitor(StringTokenizer t) throws NoSessionException { if (!t.hasMoreTokens()) { env.error("Argument required"); } else { env.getMonitorListModel().remove(t.nextToken("")); } } // Print a stack variable. private void printVar(OutputSink out, LocalVariable var, StackFrame frame) { out.print(" " + var.name()); if (var.isVisible(frame)) { Value val = frame.getValue(var); out.println(" = " + val.toString()); } else { out.println(" is not in scope"); } } // Command: print // Evaluate an expression. private void commandPrint(StringTokenizer t, boolean dumpObject) throws NoSessionException { if (!t.hasMoreTokens()) { //### Probably confused if expresion contains whitespace. env.error("No expression specified."); return; } ThreadReference current = context.getCurrentThread(); if (current == null) { env.failure("No default thread specified: " + "use the \"thread\" command first."); return; } StackFrame frame; try { frame = context.getCurrentFrame(current); if (frame == null) { env.failure("Thread has not yet created any stack frames."); return; } } catch (VMNotInterruptedException e) { env.failure("Target VM must be in interrupted state."); return; } while (t.hasMoreTokens()) { String expr = t.nextToken(""); Value val = null; try { val = runtime.evaluate(frame, expr); } catch(Exception e) { env.error("Exception: " + e); //### Fix this! } if (val == null) { return; // Error message already printed } OutputSink out = env.getOutputSink(); if (dumpObject && (val instanceof ObjectReference) && !(val instanceof StringReference)) { ObjectReference obj = (ObjectReference)val; ReferenceType refType = obj.referenceType(); out.println(expr + " = " + val.toString() + " {"); dump(out, obj, refType, refType); out.println("}"); } else { out.println(expr + " = " + val.toString()); } out.show(); } } private void dump(OutputSink out, ObjectReference obj, ReferenceType refType, ReferenceType refTypeBase) { for (Field field : refType.fields()) { out.print(" "); if (!refType.equals(refTypeBase)) { out.print(refType.name() + "."); } out.print(field.name() + ": "); Object o = obj.getValue(field); out.println((o == null) ? "null" : o.toString()); // Bug ID 4374471 } if (refType instanceof ClassType) { ClassType sup = ((ClassType)refType).superclass(); if (sup != null) { dump(out, obj, sup, refTypeBase); } } else if (refType instanceof InterfaceType) { for (InterfaceType sup : ((InterfaceType)refType).superinterfaces()) { dump(out, obj, sup, refTypeBase); } } } /* * Display help message. */ private void help() { out.println("** command list **"); out.println("threads [threadgroup] -- list threads"); out.println("thread <thread id> -- set default thread"); out.println("suspend [thread id(s)] -- suspend threads (default: all)"); out.println("resume [thread id(s)] -- resume threads (default: all)"); out.println("where [thread id] | all -- dump a thread's stack"); out.println("wherei [thread id] | all -- dump a thread's stack, with pc info"); out.println("threadgroups -- list threadgroups"); out.println("threadgroup <name> -- set current threadgroup\n"); // out.println("print <expression> -- print value of expression"); out.println("dump <expression> -- print all object information\n"); // out.println("eval <expression> -- evaluate expression (same as print)"); out.println("locals -- print all local variables in current stack frame\n"); out.println("classes -- list currently known classes"); out.println("methods <class id> -- list a class's methods\n"); out.println("stop [in] <class id>.<method>[(argument_type,...)] -- set a breakpoint in a method"); out.println("stop [at] <class id>:<line> -- set a breakpoint at a line"); out.println("up [n frames] -- move up a thread's stack"); out.println("down [n frames] -- move down a thread's stack"); out.println("frame <frame-id> -- to a frame"); out.println("clear <class id>.<method>[(argument_type,...)] -- clear a breakpoint in a method"); out.println("clear <class id>:<line> -- clear a breakpoint at a line"); out.println("clear -- list breakpoints"); out.println("step -- execute current line"); out.println("step up -- execute until the current method returns to its caller"); out.println("stepi -- execute current instruction"); out.println("next -- step one line (step OVER calls)"); out.println("nexti -- step one instruction (step OVER calls)"); out.println("cont -- continue execution from breakpoint\n"); // out.println("catch <class id> -- break for the specified exception"); // out.println("ignore <class id> -- ignore when the specified exception\n"); out.println("view classname|filename -- display source file"); out.println("list [line number|method] -- print source code context at line or method"); out.println("use <source file path> -- display or change the source path\n"); //### new out.println("sourcepath <source file path> -- display or change the source path\n"); //### new out.println("classpath <class file path> -- display or change the class path\n"); out.println("monitor <expression> -- evaluate an expression each time the program stops\n"); out.println("unmonitor <monitor#> -- delete a monitor\n"); out.println("read <filename> -- read and execute a command file\n"); // out.println("memory -- report memory usage"); // out.println("gc -- free unused objects\n"); out.println("run <class> [args] -- start execution of a Java class"); out.println("run -- re-execute last class run"); out.println("load <class> [args] -- start execution of a Java class, initially suspended"); out.println("load -- re-execute last class run, initially suspended"); out.println("attach <portname> -- debug existing process\n"); out.println("detach -- detach from debuggee process\n"); out.println("kill <thread(group)> -- kill a thread or threadgroup\n"); out.println("!! -- repeat last command"); out.println("help (or ?) -- list commands"); out.println("exit (or quit) -- exit debugger"); } /* * Execute a command. */ public void executeCommand(String command) { //### Treatment of 'out' here is dirty... out = env.getOutputSink(); if (echo) { out.println(">>> " + command); } StringTokenizer t = new StringTokenizer(command); try { String cmd; if (t.hasMoreTokens()) { cmd = t.nextToken().toLowerCase(); lastCommand = cmd; } else { cmd = lastCommand; } if (cmd.equals("print")) { commandPrint(t, false); } else if (cmd.equals("eval")) { commandPrint(t, false); } else if (cmd.equals("dump")) { commandPrint(t, true); } else if (cmd.equals("locals")) { commandLocals(); } else if (cmd.equals("classes")) { commandClasses(); } else if (cmd.equals("methods")) { commandMethods(t); } else if (cmd.equals("threads")) { commandThreads(t); } else if (cmd.equals("thread")) { commandThread(t); } else if (cmd.equals("suspend")) { commandSuspend(t); } else if (cmd.equals("resume")) { commandResume(t); } else if (cmd.equals("cont")) { commandCont(); } else if (cmd.equals("threadgroups")) { commandThreadGroups(); } else if (cmd.equals("threadgroup")) { commandThreadGroup(t); } else if (cmd.equals("run")) { commandRun(t); } else if (cmd.equals("load")) { commandLoad(t); } else if (cmd.equals("connect")) { commandConnect(t); } else if (cmd.equals("attach")) { commandAttach(t); } else if (cmd.equals("detach")) { commandDetach(t); } else if (cmd.equals("interrupt")) { commandInterrupt(t); //### Not implemented. // } else if (cmd.equals("catch")) { // commandCatchException(t); //### Not implemented. // } else if (cmd.equals("ignore")) { // commandIgnoreException(t); } else if (cmd.equals("step")) { commandStep(t); } else if (cmd.equals("stepi")) { commandStepi(); } else if (cmd.equals("next")) { commandNext(); } else if (cmd.equals("nexti")) { commandNexti(); } else if (cmd.equals("kill")) { commandKill(t); } else if (cmd.equals("where")) { commandWhere(t, false); } else if (cmd.equals("wherei")) { commandWhere(t, true); } else if (cmd.equals("up")) { commandUp(t); } else if (cmd.equals("down")) { commandDown(t); } else if (cmd.equals("frame")) { commandFrame(t); } else if (cmd.equals("stop")) { commandStop(t); } else if (cmd.equals("clear")) { commandClear(t); } else if (cmd.equals("list")) { commandList(t); } else if (cmd.equals("use")) { commandUse(t); } else if (cmd.equals("sourcepath")) { commandSourcepath(t); } else if (cmd.equals("classpath")) { commandClasspath(t); } else if (cmd.equals("monitor")) { commandMonitor(t); } else if (cmd.equals("unmonitor")) { commandUnmonitor(t); } else if (cmd.equals("view")) { commandView(t); // } else if (cmd.equals("read")) { // readCommand(t); } else if (cmd.equals("help") || cmd.equals("?")) { help(); } else if (cmd.equals("quit") || cmd.equals("exit")) { try { runtime.detach(); } catch (NoSessionException e) { // ignore } env.terminate(); } else { //### Dubious repeat-count feature inherited from 'jdb' if (t.hasMoreTokens()) { try { int repeat = Integer.parseInt(cmd); String subcom = t.nextToken(""); while (repeat-- > 0) { executeCommand(subcom); } return; } catch (NumberFormatException exc) { } } out.println("huh? Try help..."); out.flush(); } } catch (NoSessionException e) { out.println("There is no currently attached VM session."); out.flush(); } catch (Exception e) { out.println("Internal exception: " + e.toString()); out.flush(); System.out.println("JDB internal exception: " + e.toString()); e.printStackTrace(); } out.show(); } }