/*
* Copyright (c) 1998, 2008, 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.
*/
/*
* This source code is provided to illustrate the usage of a given feature
* or technique and has been deliberately simplified. Additional steps
* required for a production-quality application, such as security checks,
* input validation and proper error handling, might not be present in
* this sample code.
*/
package com.sun.tools.example.debug.gui;
import java.io.*;
import java.util.*;
import com.sun.jdi.*;
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 (ThreadReference thread2 : threads) {
if (thread2.uniqueID() == threadID) {
thread = thread2;
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 {}
*************/
// Command: up
//### Print current frame after command?
int readCount(StringTokenizer t) {
int cnt = 1;
if (t.hasMoreTokens()) {
String idToken = t.nextToken();
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 {
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
*/
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 (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();
}
}