public class

CommandInterpreter

extends Object
/*
 * 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 = (ThreadReference[])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 {
        List list = runtime.allClasses();
        OutputSink out = env.getOutputSink();
        //out.println("** classes list **");
        for (int i = 0 ; i < list.size() ; i++) {
            ReferenceType refType = (ReferenceType)list.get(i);
            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 methods = cls.allMethods();
            OutputSink out = env.getOutputSink();
            for (int i = 0; i < methods.size(); i++) {
                Method method = (Method)methods.get(i);
                out.print(method.declaringType().name() + " " +
                            method.name() + "(");
                Iterator it = method.argumentTypeNames().iterator();
                if (it.hasNext()) {
                    while (true) {
                        out.print((String)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 results = runtime.findClassesMatchingPattern(pattern);
        if (results.size() > 0) {
            //### Should handle multiple results sensibly.
            return (ReferenceType)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 tlist = tg.threads();
        int maxId = 0;
        int maxName = 0;
        for (int i = 0 ; i < tlist.size() ; i++) {
            ThreadReference thr = (ThreadReference)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 = (ThreadReference)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());
        }
        List tglist = tg.threadGroups();
        for (int ig = 0; ig < tglist.size(); ig++) {
            ThreadGroupReference tg0 = (ThreadGroupReference)tglist.get(ig);
            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 {}
    *************/

    // 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 = (ThreadReference)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 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 = (StackFrame)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 instanceof Method && ((Method)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
        Iterator iter = runtime.eventRequestSpecs().iterator();
        if (!iter.hasNext()) {
            env.notice("No breakpoints/watchpoints/exceptions set.");
        } else {
            OutputSink out = env.getOutputSink();
            out.println("Current breakpoints/watchpoints/exceptions set:");
            while (iter.hasNext()) {
                EventRequestSpec  bp = (EventRequestSpec)iter.next();
                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) {
            Iterator iter = runtime.eventRequestSpecs().iterator();
            if (!iter.hasNext()) {
                env.notice("No breakpoints set.");
            } else {
                List<BreakpointSpec> toDelete = new ArrayList<BreakpointSpec>();
                while (iter.hasNext()) {
                    BreakpointSpec spec = (BreakpointSpec)iter.next();
                    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 (BreakpointSpec 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 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 = ((Method)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 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 (Iterator it = vars.iterator(); it.hasNext(); ) {
            LocalVariable var = (LocalVariable)it.next();
            if (var.isArgument()) {
                printVar(out, var, frame);
            }
        }
        out.println("Local variables:");
        for (Iterator it = vars.iterator(); it.hasNext(); ) {
            LocalVariable var = (LocalVariable)it.next();
            if (!var.isArgument()) {
                printVar(out, var, frame);
            }
        }
        out.show();
        return;
    }

    /**
     * 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
     */
    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 (Iterator it = refType.fields().iterator(); it.hasNext(); ) {
            Field field = (Field)it.next();
            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) {
            List sups = ((InterfaceType)refType).superinterfaces();
            for (Iterator it = sups.iterator(); it.hasNext(); ) {
                dump(out, obj, (ReferenceType)it.next(), 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();
    }
}