1 /*
   2  * Copyright (c) 1998, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 /*
  27  * This source code is provided to illustrate the usage of a given feature
  28  * or technique and has been deliberately simplified. Additional steps
  29  * required for a production-quality application, such as security checks,
  30  * input validation and proper error handling, might not be present in
  31  * this sample code.
  32  */
  33 
  34 
  35 package com.sun.tools.example.debug.gui;
  36 
  37 import java.io.*;
  38 import java.util.*;
  39 
  40 import com.sun.jdi.*;
  41 import com.sun.tools.example.debug.bdi.*;
  42 
  43 public class CommandInterpreter {
  44 
  45     boolean echo;
  46 
  47     Environment env;
  48 
  49     private ContextManager context;
  50     private ExecutionManager runtime;
  51     private ClassManager classManager;
  52     private SourceManager sourceManager;
  53 
  54     private OutputSink out; //### Hack!  Should be local in each method used.
  55     private String lastCommand = "help";
  56 
  57     public CommandInterpreter(Environment env) {
  58         this(env, true);
  59     }
  60 
  61     public CommandInterpreter(Environment env, boolean echo) {
  62         this.env = env;
  63         this.echo = echo;
  64         this.runtime = env.getExecutionManager();
  65         this.context = env.getContextManager();
  66         this.classManager = env.getClassManager();
  67         this.sourceManager = env.getSourceManager();
  68     }
  69 
  70     private ThreadReference[] threads = null;
  71 
  72     /*
  73      * The numbering of threads is relative to the current set of threads,
  74      * and may be affected by the creation and termination of new threads.
  75      * Commands issued using such thread ids will only give reliable behavior
  76      * relative to what was shown earlier in 'list' commands if the VM is interrupted.
  77      * We need a better scheme.
  78      */
  79 
  80     private ThreadReference[] threads() throws NoSessionException {
  81         if (threads == null) {
  82             ThreadIterator ti = new ThreadIterator(getDefaultThreadGroup());
  83             List<ThreadReference> tlist = new ArrayList<ThreadReference>();
  84             while (ti.hasNext()) {
  85                 tlist.add(ti.nextThread());
  86             }
  87             threads = tlist.toArray(new ThreadReference[tlist.size()]);
  88         }
  89         return threads;
  90     }
  91 
  92     private ThreadReference findThread(String idToken) throws NoSessionException {
  93         String id;
  94         ThreadReference thread = null;
  95         if (idToken.startsWith("t@")) {
  96             id = idToken.substring(2);
  97         } else {
  98             id = idToken;
  99         }
 100         try {
 101             ThreadReference[] threads = threads();
 102             long threadID = Long.parseLong(id, 16);
 103             for (ThreadReference thread2 : threads) {
 104                 if (thread2.uniqueID() == threadID) {
 105                     thread = thread2;
 106                     break;
 107                 }
 108             }
 109             if (thread == null) {
 110                 //env.failure("No thread for id \"" + idToken + "\"");
 111                 env.failure("\"" + idToken + "\" is not a valid thread id.");
 112             }
 113         } catch (NumberFormatException e) {
 114             env.error("Thread id \"" + idToken + "\" is ill-formed.");
 115             thread = null;
 116         }
 117         return thread;
 118     }
 119 
 120     private ThreadIterator allThreads() throws NoSessionException {
 121         threads = null;
 122         //### Why not use runtime.allThreads().iterator() ?
 123         return new ThreadIterator(runtime.topLevelThreadGroups());
 124     }
 125 
 126     private ThreadIterator currentThreadGroupThreads() throws NoSessionException {
 127         threads = null;
 128         return new ThreadIterator(getDefaultThreadGroup());
 129     }
 130 
 131     private ThreadGroupIterator allThreadGroups() throws NoSessionException {
 132         threads = null;
 133         return new ThreadGroupIterator(runtime.topLevelThreadGroups());
 134     }
 135 
 136     private ThreadGroupReference defaultThreadGroup;
 137 
 138     private ThreadGroupReference getDefaultThreadGroup() throws NoSessionException {
 139         if (defaultThreadGroup == null) {
 140             defaultThreadGroup = runtime.systemThreadGroup();
 141         }
 142         return defaultThreadGroup;
 143     }
 144 
 145     private void setDefaultThreadGroup(ThreadGroupReference tg) {
 146         defaultThreadGroup = tg;
 147     }
 148 
 149     /*
 150      * Command handlers.
 151      */
 152 
 153     // Command: classes
 154 
 155     private void commandClasses() throws NoSessionException {
 156         OutputSink out = env.getOutputSink();
 157         //out.println("** classes list **");
 158         for (ReferenceType refType : runtime.allClasses()) {
 159             out.println(refType.name());
 160         }
 161         out.show();
 162     }
 163 
 164 
 165     // Command: methods
 166 
 167     private void commandMethods(StringTokenizer t) throws NoSessionException {
 168         if (!t.hasMoreTokens()) {
 169             env.error("No class specified.");
 170             return;
 171         }
 172         String idClass = t.nextToken();
 173         ReferenceType cls = findClass(idClass);
 174         if (cls != null) {
 175             List<Method> methods = cls.allMethods();
 176             OutputSink out = env.getOutputSink();
 177             for (int i = 0; i < methods.size(); i++) {
 178                 Method method = methods.get(i);
 179                 out.print(method.declaringType().name() + " " +
 180                             method.name() + "(");
 181                 Iterator<String> it = method.argumentTypeNames().iterator();
 182                 if (it.hasNext()) {
 183                     while (true) {
 184                         out.print(it.next());
 185                         if (!it.hasNext()) {
 186                             break;
 187                         }
 188                         out.print(", ");
 189                     }
 190                 }
 191                 out.println(")");
 192             }
 193             out.show();
 194         } else {
 195             //### Should validate class name syntax.
 196             env.failure("\"" + idClass + "\" is not a valid id or class name.");
 197         }
 198     }
 199 
 200     private ReferenceType findClass(String pattern) throws NoSessionException {
 201         List<ReferenceType> results = runtime.findClassesMatchingPattern(pattern);
 202         if (results.size() > 0) {
 203             //### Should handle multiple results sensibly.
 204             return results.get(0);
 205         }
 206         return null;
 207     }
 208 
 209     // Command: threads
 210 
 211     private void commandThreads(StringTokenizer t) throws NoSessionException {
 212         if (!t.hasMoreTokens()) {
 213             OutputSink out = env.getOutputSink();
 214             printThreadGroup(out, getDefaultThreadGroup(), 0);
 215             out.show();
 216             return;
 217         }
 218         String name = t.nextToken();
 219         ThreadGroupReference tg = findThreadGroup(name);
 220         if (tg == null) {
 221             env.failure(name + " is not a valid threadgroup name.");
 222         } else {
 223             OutputSink out = env.getOutputSink();
 224             printThreadGroup(out, tg, 0);
 225             out.show();
 226         }
 227     }
 228 
 229     private ThreadGroupReference findThreadGroup(String name) throws NoSessionException {
 230         //### Issue: Uniqueness of thread group names is not enforced.
 231         ThreadGroupIterator tgi = allThreadGroups();
 232         while (tgi.hasNext()) {
 233             ThreadGroupReference tg = tgi.nextThreadGroup();
 234             if (tg.name().equals(name)) {
 235                 return tg;
 236             }
 237         }
 238         return null;
 239     }
 240 
 241     private int printThreadGroup(OutputSink out, ThreadGroupReference tg, int iThread) {
 242         out.println("Group " + tg.name() + ":");
 243         List<ThreadReference> tlist = tg.threads();
 244         int maxId = 0;
 245         int maxName = 0;
 246         for (int i = 0 ; i < tlist.size() ; i++) {
 247             ThreadReference thr = tlist.get(i);
 248             int len = Utils.description(thr).length();
 249             if (len > maxId) {
 250                 maxId = len;
 251             }
 252             String name = thr.name();
 253             int iDot = name.lastIndexOf('.');
 254             if (iDot >= 0 && name.length() > iDot) {
 255                 name = name.substring(iDot + 1);
 256             }
 257             if (name.length() > maxName) {
 258                 maxName = name.length();
 259         }
 260         }
 261         String maxNumString = String.valueOf(iThread + tlist.size());
 262         int maxNumDigits = maxNumString.length();
 263         for (int i = 0 ; i < tlist.size() ; i++) {
 264             ThreadReference thr = tlist.get(i);
 265             char buf[] = new char[80];
 266             for (int j = 0; j < 79; j++) {
 267                 buf[j] = ' ';
 268             }
 269             buf[79] = '\0';
 270             StringBuilder sbOut = new StringBuilder();
 271             sbOut.append(buf);
 272 
 273             // Right-justify the thread number at start of output string
 274             String numString = String.valueOf(iThread + i + 1);
 275             sbOut.insert(maxNumDigits - numString.length(),
 276                          numString);
 277             sbOut.insert(maxNumDigits, ".");
 278 
 279             int iBuf = maxNumDigits + 2;
 280             sbOut.insert(iBuf, Utils.description(thr));
 281             iBuf += maxId + 1;
 282             String name = thr.name();
 283             int iDot = name.lastIndexOf('.');
 284             if (iDot >= 0 && name.length() > iDot) {
 285                 name = name.substring(iDot + 1);
 286             }
 287             sbOut.insert(iBuf, name);
 288             iBuf += maxName + 1;
 289             sbOut.insert(iBuf, Utils.getStatus(thr));
 290             sbOut.setLength(79);
 291             out.println(sbOut.toString());
 292         }
 293         for (ThreadGroupReference tg0 : tg.threadGroups()) {
 294             if (!tg.equals(tg0)) {  // TODO ref mgt
 295                 iThread += printThreadGroup(out, tg0, iThread + tlist.size());
 296             }
 297         }
 298         return tlist.size();
 299     }
 300 
 301     // Command: threadgroups
 302 
 303     private void commandThreadGroups() throws NoSessionException {
 304         ThreadGroupIterator it = allThreadGroups();
 305         int cnt = 0;
 306         OutputSink out = env.getOutputSink();
 307         while (it.hasNext()) {
 308             ThreadGroupReference tg = it.nextThreadGroup();
 309             ++cnt;
 310             out.println("" + cnt + ". " + Utils.description(tg) + " " + tg.name());
 311         }
 312         out.show();
 313     }
 314 
 315     // Command: thread
 316 
 317     private void commandThread(StringTokenizer t) throws NoSessionException {
 318         if (!t.hasMoreTokens()) {
 319             env.error("Thread number not specified.");
 320             return;
 321         }
 322         ThreadReference thread = findThread(t.nextToken());
 323         if (thread != null) {
 324             //### Should notify user.
 325             context.setCurrentThread(thread);
 326         }
 327     }
 328 
 329     // Command: threadgroup
 330 
 331     private void commandThreadGroup(StringTokenizer t) throws NoSessionException {
 332         if (!t.hasMoreTokens()) {
 333             env.error("Threadgroup name not specified.");
 334             return;
 335         }
 336         String name = t.nextToken();
 337         ThreadGroupReference tg = findThreadGroup(name);
 338         if (tg == null) {
 339             env.failure(name + " is not a valid threadgroup name.");
 340         } else {
 341             //### Should notify user.
 342             setDefaultThreadGroup(tg);
 343         }
 344     }
 345 
 346     // Command: run
 347 
 348     private void commandRun(StringTokenizer t) throws NoSessionException {
 349         if (doLoad(false, t)) {
 350             env.notice("Running ...");
 351         }
 352     }
 353 
 354     // Command: load
 355 
 356     private void commandLoad(StringTokenizer t) throws NoSessionException {
 357         if (doLoad(true, t)) {}
 358     }
 359 
 360     private boolean doLoad(boolean suspended,
 361                            StringTokenizer t) throws NoSessionException {
 362 
 363         String clname;
 364 
 365         if (!t.hasMoreTokens()) {
 366             clname = context.getMainClassName();
 367             if (!clname.equals("")) {
 368                 // Run from prevously-set class name.
 369                 try {
 370                     String vmArgs = context.getVmArguments();
 371                     runtime.run(suspended,
 372                                 vmArgs,
 373                                 clname,
 374                                 context.getProgramArguments());
 375                     return true;
 376                 } catch (VMLaunchFailureException e) {
 377                     env.failure("Attempt to launch main class \"" + clname + "\" failed.");
 378                 }
 379             } else {
 380                 env.failure("No main class specified and no current default defined.");
 381             }
 382         } else {
 383             clname = t.nextToken();
 384             StringBuilder str = new StringBuilder();
 385             // Allow VM arguments to be specified here?
 386             while (t.hasMoreTokens()) {
 387                 String tok = t.nextToken();
 388                 str.append(tok);
 389                 if (t.hasMoreTokens()) {
 390                     str.append(' ');
 391                 }
 392             }
 393             String args = str.toString();
 394             try {
 395                 String vmArgs = context.getVmArguments();
 396                 runtime.run(suspended, vmArgs, clname, args);
 397                 context.setMainClassName(clname);
 398                 //context.setVmArguments(vmArgs);
 399                 context.setProgramArguments(args);
 400                 return true;
 401             } catch (VMLaunchFailureException e) {
 402                 env.failure("Attempt to launch main class \"" + clname + "\" failed.");
 403             }
 404         }
 405         return false;
 406     }
 407 
 408     // Command: connect
 409 
 410     private void commandConnect(StringTokenizer t) {
 411         try {
 412             LaunchTool.queryAndLaunchVM(runtime);
 413         } catch (VMLaunchFailureException e) {
 414             env.failure("Attempt to connect failed.");
 415         }
 416     }
 417 
 418     // Command: attach
 419 
 420     private void commandAttach(StringTokenizer t) {
 421         String portName;
 422         if (!t.hasMoreTokens()) {
 423             portName = context.getRemotePort();
 424             if (!portName.equals("")) {
 425                 try {
 426                     runtime.attach(portName);
 427                 } catch (VMLaunchFailureException e) {
 428                     env.failure("Attempt to attach to port \"" + portName + "\" failed.");
 429                 }
 430             } else {
 431                 env.failure("No port specified and no current default defined.");
 432             }
 433         } else {
 434             portName = t.nextToken();
 435             try {
 436                 runtime.attach(portName);
 437             } catch (VMLaunchFailureException e) {
 438                 env.failure("Attempt to attach to port \"" + portName + "\" failed.");
 439             }
 440             context.setRemotePort(portName);
 441         }
 442     }
 443 
 444     // Command: detach
 445 
 446     private void commandDetach(StringTokenizer t) throws NoSessionException {
 447         runtime.detach();
 448     }
 449 
 450     // Command: interrupt
 451 
 452     private void commandInterrupt(StringTokenizer t) throws NoSessionException {
 453         runtime.interrupt();
 454     }
 455 
 456     // Command: suspend
 457 
 458     private void commandSuspend(StringTokenizer t) throws NoSessionException {
 459         if (!t.hasMoreTokens()) {
 460             // Suspend all threads in the current thread group.
 461             //### Issue: help message says default is all threads.
 462             //### Behavior here agrees with 'jdb', however.
 463             ThreadIterator ti = currentThreadGroupThreads();
 464             while (ti.hasNext()) {
 465                 // TODO - don't suspend debugger threads
 466                 ti.nextThread().suspend();
 467             }
 468             env.notice("All (non-system) threads suspended.");
 469         } else {
 470             while (t.hasMoreTokens()) {
 471                 ThreadReference thread = findThread(t.nextToken());
 472                 if (thread != null) {
 473                     //thread.suspend();
 474                     runtime.suspendThread(thread);
 475                 }
 476             }
 477         }
 478     }
 479 
 480     // Command: resume
 481 
 482     private void commandResume(StringTokenizer t) throws NoSessionException {
 483          if (!t.hasMoreTokens()) {
 484             // Suspend all threads in the current thread group.
 485             //### Issue: help message says default is all threads.
 486             //### Behavior here agrees with 'jdb', however.
 487             ThreadIterator ti = currentThreadGroupThreads();
 488             while (ti.hasNext()) {
 489                 // TODO - don't suspend debugger threads
 490                 ti.nextThread().resume();
 491             }
 492             env.notice("All threads resumed.");
 493          } else {
 494              while (t.hasMoreTokens()) {
 495                 ThreadReference thread = findThread(t.nextToken());
 496                 if (thread != null) {
 497                     //thread.resume();
 498                     runtime.resumeThread(thread);
 499                 }
 500              }
 501          }
 502     }
 503 
 504     // Command: cont
 505 
 506     private void commandCont() throws NoSessionException {
 507         try {
 508             runtime.go();
 509         } catch (VMNotInterruptedException e) {
 510             //### failure?
 511             env.notice("Target VM is already running.");
 512         }
 513     }
 514 
 515     // Command: step
 516 
 517     private void commandStep(StringTokenizer t) throws NoSessionException{
 518         ThreadReference current = context.getCurrentThread();
 519         if (current == null) {
 520             env.failure("No current thread.");
 521             return;
 522         }
 523         try {
 524             if (t.hasMoreTokens() &&
 525                 t.nextToken().toLowerCase().equals("up")) {
 526                 runtime.stepOut(current);
 527             } else {
 528                 runtime.stepIntoLine(current);
 529             }
 530         } catch (AbsentInformationException e) {
 531             env.failure("No linenumber information available -- " +
 532                             "Try \"stepi\" to step by instructions.");
 533         }
 534     }
 535 
 536     // Command: stepi
 537 
 538     private void commandStepi() throws NoSessionException {
 539         ThreadReference current = context.getCurrentThread();
 540         if (current == null) {
 541             env.failure("No current thread.");
 542             return;
 543         }
 544         runtime.stepIntoInstruction(current);
 545     }
 546 
 547     // Command: next
 548 
 549     private void commandNext() throws NoSessionException {
 550         ThreadReference current = context.getCurrentThread();
 551         if (current == null) {
 552             env.failure("No current thread.");
 553             return;
 554         }
 555         try {
 556             runtime.stepOverLine(current);
 557         } catch (AbsentInformationException e) {
 558             env.failure("No linenumber information available -- " +
 559                             "Try \"nexti\" to step by instructions.");
 560         }
 561     }
 562 
 563     // Command: nexti  (NEW)
 564 
 565     private void commandNexti() throws NoSessionException {
 566         ThreadReference current = context.getCurrentThread();
 567         if (current == null) {
 568             env.failure("No current thread.");
 569             return;
 570         }
 571         runtime.stepOverInstruction(current);
 572     }
 573 
 574     // Command: kill
 575 
 576     private void commandKill(StringTokenizer t) throws NoSessionException {
 577         //### Should change the way in which thread ids and threadgroup names
 578         //### are distinguished.
 579          if (!t.hasMoreTokens()) {
 580             env.error("Usage: kill <threadgroup name> or <thread id>");
 581             return;
 582         }
 583         while (t.hasMoreTokens()) {
 584             String idToken = t.nextToken();
 585             ThreadReference thread = findThread(idToken);
 586             if (thread != null) {
 587                 runtime.stopThread(thread);
 588                 env.notice("Thread " + thread.name() + " killed.");
 589                 return;
 590             } else {
 591                 /* Check for threadgroup name, NOT skipping "system". */
 592                 //### Should skip "system"?  Classic 'jdb' does this.
 593                 //### Should deal with possible non-uniqueness of threadgroup names.
 594                 ThreadGroupIterator itg = allThreadGroups();
 595                 while (itg.hasNext()) {
 596                     ThreadGroupReference tg = itg.nextThreadGroup();
 597                     if (tg.name().equals(idToken)) {
 598                         ThreadIterator it = new ThreadIterator(tg);
 599                         while (it.hasNext()) {
 600                             runtime.stopThread(it.nextThread());
 601                         }
 602                         env.notice("Threadgroup " + tg.name() + "killed.");
 603                         return;
 604                     }
 605                 }
 606                 env.failure("\"" + idToken +
 607                             "\" is not a valid threadgroup or id.");
 608             }
 609         }
 610     }
 611 
 612 
 613     /*************
 614     // TODO
 615     private void commandCatchException(StringTokenizer t) throws NoSessionException {}
 616     // TODO
 617     private void commandIgnoreException(StringTokenizer t) throws NoSessionException {}
 618     *************/
 619 
 620     // Command: up
 621 
 622     //### Print current frame after command?
 623 
 624     int readCount(StringTokenizer t) {
 625         int cnt = 1;
 626         if (t.hasMoreTokens()) {
 627             String idToken = t.nextToken();
 628             try {
 629                 cnt = Integer.valueOf(idToken).intValue();
 630             } catch (NumberFormatException e) {
 631                 cnt = -1;
 632             }
 633         }
 634         return cnt;
 635     }
 636 
 637     void commandUp(StringTokenizer t) throws NoSessionException {
 638         ThreadReference current = context.getCurrentThread();
 639         if (current == null) {
 640             env.failure("No current thread.");
 641             return;
 642         }
 643         int nLevels = readCount(t);
 644         if (nLevels <= 0) {
 645             env.error("usage: up [n frames]");
 646             return;
 647         }
 648         try {
 649             int delta = context.moveCurrentFrameIndex(current, -nLevels);
 650             if (delta == 0) {
 651                 env.notice("Already at top of stack.");
 652             } else if (-delta < nLevels) {
 653                 env.notice("Moved up " + delta + " frames to top of stack.");
 654             }
 655         } catch (VMNotInterruptedException e) {
 656             env.failure("Target VM must be in interrupted state.");
 657         }
 658     }
 659 
 660     private void commandDown(StringTokenizer t) throws NoSessionException {
 661         ThreadReference current = context.getCurrentThread();
 662         if (current == null) {
 663             env.failure("No current thread.");
 664             return;
 665         }
 666         int nLevels = readCount(t);
 667         if (nLevels <= 0) {
 668             env.error("usage: down [n frames]");
 669             return;
 670         }
 671         try {
 672             int delta = context.moveCurrentFrameIndex(current, nLevels);
 673             if (delta == 0) {
 674                 env.notice("Already at bottom of stack.");
 675             } else if (delta < nLevels) {
 676                 env.notice("Moved down " + delta + " frames to bottom of stack.");
 677             }
 678         } catch (VMNotInterruptedException e) {
 679             env.failure("Target VM must be in interrupted state.");
 680         }
 681     }
 682 
 683     // Command: frame
 684 
 685     private void commandFrame(StringTokenizer t) throws NoSessionException {
 686         ThreadReference current = context.getCurrentThread();
 687         if (current == null) {
 688             env.failure("No current thread.");
 689             return;
 690         }
 691         if (!t.hasMoreTokens()) {
 692             env.error("usage: frame <frame-index>");
 693             return;
 694         }
 695         String idToken = t.nextToken();
 696         int n;
 697         try {
 698             n = Integer.valueOf(idToken).intValue();
 699         } catch (NumberFormatException e) {
 700             n = 0;
 701         }
 702         if (n <= 0) {
 703             env.error("use positive frame index");
 704             return;
 705         }
 706         try {
 707             int delta = context.setCurrentFrameIndex(current, n);
 708             if (delta == 0) {
 709                 env.notice("Frame unchanged.");
 710             } else if (delta < 0) {
 711                 env.notice("Moved up " + -delta + " frames.");
 712             } else {
 713                 env.notice("Moved down " + delta + " frames.");
 714             }
 715         } catch (VMNotInterruptedException e) {
 716             env.failure("Target VM must be in interrupted state.");
 717         }
 718     }
 719 
 720     // Command: where
 721 
 722     //### Should we insist that VM be interrupted here?
 723     //### There is an inconsistency between the 'where' command
 724     //### and 'up' and 'down' in this respect.
 725 
 726     private void commandWhere(StringTokenizer t, boolean showPC)
 727                                                 throws NoSessionException {
 728         ThreadReference current = context.getCurrentThread();
 729         if (!t.hasMoreTokens()) {
 730             if (current == null) {
 731                 env.error("No thread specified.");
 732                 return;
 733             }
 734             dumpStack(current, showPC);
 735         } else {
 736             String token = t.nextToken();
 737             if (token.toLowerCase().equals("all")) {
 738                 ThreadIterator it = allThreads();
 739                 while (it.hasNext()) {
 740                     ThreadReference thread = it.next();
 741                     out.println(thread.name() + ": ");
 742                     dumpStack(thread, showPC);
 743                 }
 744             } else {
 745                 ThreadReference thread = findThread(t.nextToken());
 746                 //### Do we want to set current thread here?
 747                 //### Should notify user of change.
 748                 if (thread != null) {
 749                     context.setCurrentThread(thread);
 750                 }
 751                 dumpStack(thread, showPC);
 752             }
 753         }
 754     }
 755 
 756     private void dumpStack(ThreadReference thread, boolean showPC) {
 757         //### Check for these.
 758         //env.failure("Thread no longer exists.");
 759         //env.failure("Target VM must be in interrupted state.");
 760         //env.failure("Current thread isn't suspended.");
 761         //### Should handle extremely long stack traces sensibly for user.
 762         List<StackFrame> stack = null;
 763         try {
 764             stack = thread.frames();
 765         } catch (IncompatibleThreadStateException e) {
 766             env.failure("Thread is not suspended.");
 767         }
 768         //### Fix this!
 769         //### Previously mishandled cases where thread was not current.
 770         //### Now, prints all of the stack regardless of current frame.
 771         int frameIndex = 0;
 772         //int frameIndex = context.getCurrentFrameIndex();
 773         if (stack == null) {
 774             env.failure("Thread is not running (no stack).");
 775         } else {
 776             OutputSink out = env.getOutputSink();
 777             int nFrames = stack.size();
 778             for (int i = frameIndex; i < nFrames; i++) {
 779                 StackFrame frame = stack.get(i);
 780                 Location loc = frame.location();
 781                 Method meth = loc.method();
 782                 out.print("  [" + (i + 1) + "] ");
 783                 out.print(meth.declaringType().name());
 784                 out.print('.');
 785                 out.print(meth.name());
 786                 out.print(" (");
 787                 if (meth.isNative()) {
 788                     out.print("native method");
 789                 } else if (loc.lineNumber() != -1) {
 790                     try {
 791                         out.print(loc.sourceName());
 792                     } catch (AbsentInformationException e) {
 793                         out.print("<unknown>");
 794                     }
 795                     out.print(':');
 796                     out.print(loc.lineNumber());
 797                 }
 798                 out.print(')');
 799                 if (showPC) {
 800                     long pc = loc.codeIndex();
 801                     if (pc != -1) {
 802                         out.print(", pc = " + pc);
 803                     }
 804                 }
 805                 out.println();
 806             }
 807             out.show();
 808         }
 809     }
 810 
 811     private void listEventRequests() throws NoSessionException {
 812         // Print set breakpoints
 813         List<EventRequestSpec> specs = runtime.eventRequestSpecs();
 814         if (specs.isEmpty()) {
 815             env.notice("No breakpoints/watchpoints/exceptions set.");
 816         } else {
 817             OutputSink out = env.getOutputSink();
 818             out.println("Current breakpoints/watchpoints/exceptions set:");
 819             for (EventRequestSpec bp : specs) {
 820                 out.println("\t" + bp);
 821             }
 822             out.show();
 823         }
 824     }
 825 
 826     private BreakpointSpec parseBreakpointSpec(String bptSpec) {
 827         StringTokenizer t = new StringTokenizer(bptSpec);
 828         BreakpointSpec bpSpec = null;
 829 //        try {
 830             String token = t.nextToken("@:( \t\n\r");
 831             // We can't use hasMoreTokens here because it will cause any leading
 832             // paren to be lost.
 833             String rest;
 834             try {
 835                 rest = t.nextToken("").trim();
 836             } catch (NoSuchElementException e) {
 837                 rest = null;
 838             }
 839             if ((rest != null) && rest.startsWith("@")) {
 840                 t = new StringTokenizer(rest.substring(1));
 841                 String sourceName = token;
 842                 String lineToken = t.nextToken();
 843                 int lineNumber = Integer.valueOf(lineToken).intValue();
 844                 if (t.hasMoreTokens()) {
 845                     return null;
 846                 }
 847                 bpSpec = runtime.createSourceLineBreakpoint(sourceName,
 848                                                             lineNumber);
 849             } else if ((rest != null) && rest.startsWith(":")) {
 850                 t = new StringTokenizer(rest.substring(1));
 851                 String classId = token;
 852                 String lineToken = t.nextToken();
 853                 int lineNumber = Integer.valueOf(lineToken).intValue();
 854                 if (t.hasMoreTokens()) {
 855                     return null;
 856                 }
 857                 bpSpec = runtime.createClassLineBreakpoint(classId, lineNumber);
 858             } else {
 859                 // Try stripping method from class.method token.
 860                 int idot = token.lastIndexOf('.');
 861                 if ( (idot <= 0) ||        /* No dot or dot in first char */
 862                      (idot >= token.length() - 1) ) { /* dot in last char */
 863                     return null;
 864                 }
 865                 String methodName = token.substring(idot + 1);
 866                 String classId = token.substring(0, idot);
 867                 List<String> argumentList = null;
 868                 if (rest != null) {
 869                     if (!rest.startsWith("(") || !rest.endsWith(")")) {
 870                         //### Should throw exception with error message
 871                         //out.println("Invalid method specification: "
 872                         //            + methodName + rest);
 873                         return null;
 874                     }
 875                     // Trim the parens
 876                     //### What about spaces in arglist?
 877                     rest = rest.substring(1, rest.length() - 1);
 878                     argumentList = new ArrayList<String>();
 879                     t = new StringTokenizer(rest, ",");
 880                     while (t.hasMoreTokens()) {
 881                         argumentList.add(t.nextToken());
 882                     }
 883                 }
 884                 bpSpec = runtime.createMethodBreakpoint(classId,
 885                                                        methodName,
 886                                                        argumentList);
 887             }
 888 //        } catch (Exception e) {
 889 //            env.error("Exception attempting to create breakpoint: " + e);
 890 //            return null;
 891 //        }
 892         return bpSpec;
 893     }
 894 
 895     private void commandStop(StringTokenizer t) throws NoSessionException {
 896         String token;
 897 
 898         if (!t.hasMoreTokens()) {
 899             listEventRequests();
 900         } else {
 901             token = t.nextToken();
 902             // Ignore optional "at" or "in" token.
 903             // Allowed for backward compatibility.
 904             if (token.equals("at") || token.equals("in")) {
 905                 if (t.hasMoreTokens()) {
 906                     token = t.nextToken();
 907                 } else {
 908                     env.error("Missing breakpoint specification.");
 909                     return;
 910                 }
 911             }
 912             BreakpointSpec bpSpec = parseBreakpointSpec(token);
 913             if (bpSpec != null) {
 914                 //### Add sanity-checks for deferred breakpoint.
 915                 runtime.install(bpSpec);
 916             } else {
 917                 env.error("Ill-formed breakpoint specification.");
 918             }
 919         }
 920     }
 921 
 922     private void commandClear(StringTokenizer t) throws NoSessionException {
 923         if (!t.hasMoreTokens()) {
 924             // Print set breakpoints
 925             listEventRequests();
 926             return;
 927         }
 928         //### need 'clear all'
 929         BreakpointSpec bpSpec = parseBreakpointSpec(t.nextToken());
 930         if (bpSpec != null) {
 931             List<EventRequestSpec> specs = runtime.eventRequestSpecs();
 932 
 933             if (specs.isEmpty()) {
 934                 env.notice("No breakpoints set.");
 935             } else {
 936                 List<EventRequestSpec> toDelete = new ArrayList<EventRequestSpec>();
 937                 for (EventRequestSpec spec : specs) {
 938                     if (spec.equals(bpSpec)) {
 939                         toDelete.add(spec);
 940                     }
 941                 }
 942                 // The request used for matching should be found
 943                 if (toDelete.size() <= 1) {
 944                     env.notice("No matching breakpoint set.");
 945                 }
 946                 for (EventRequestSpec spec : toDelete) {
 947                     runtime.delete(spec);
 948                 }
 949             }
 950         } else {
 951             env.error("Ill-formed breakpoint specification.");
 952         }
 953     }
 954 
 955     // Command: list
 956 
 957     private void commandList(StringTokenizer t) throws NoSessionException {
 958         ThreadReference current = context.getCurrentThread();
 959         if (current == null) {
 960             env.error("No thread specified.");
 961             return;
 962         }
 963         Location loc;
 964         try {
 965             StackFrame frame = context.getCurrentFrame(current);
 966             if (frame == null) {
 967                 env.failure("Thread has not yet begun execution.");
 968                 return;
 969             }
 970             loc = frame.location();
 971         } catch (VMNotInterruptedException e) {
 972             env.failure("Target VM must be in interrupted state.");
 973             return;
 974         }
 975         SourceModel source = sourceManager.sourceForLocation(loc);
 976         if (source == null) {
 977             if (loc.method().isNative()) {
 978                 env.failure("Current method is native.");
 979                 return;
 980             }
 981             env.failure("No source available for " + Utils.locationString(loc) + ".");
 982             return;
 983         }
 984         ReferenceType refType = loc.declaringType();
 985         int lineno = loc.lineNumber();
 986         if (t.hasMoreTokens()) {
 987             String id = t.nextToken();
 988             // See if token is a line number.
 989             try {
 990                 lineno = Integer.valueOf(id).intValue();
 991             } catch (NumberFormatException nfe) {
 992                 // It isn't -- see if it's a method name.
 993                 List<Method> meths = refType.methodsByName(id);
 994                 if (meths == null || meths.size() == 0) {
 995                     env.failure(id +
 996                                 " is not a valid line number or " +
 997                                 "method name for class " +
 998                                 refType.name());
 999                     return;
1000                 } else if (meths.size() > 1) {
1001                     env.failure(id +
1002                                 " is an ambiguous method name in" +
1003                                 refType.name());
1004                     return;
1005                 }
1006                 loc = meths.get(0).location();
1007                 lineno = loc.lineNumber();
1008             }
1009         }
1010         int startLine = (lineno > 4) ? lineno - 4 : 1;
1011         int endLine = startLine + 9;
1012         String sourceLine = source.sourceLine(lineno);
1013         if (sourceLine == null) {
1014             env.failure("" +
1015                         lineno +
1016                         " is an invalid line number for " +
1017                         refType.name());
1018         } else {
1019             OutputSink out = env.getOutputSink();
1020             for (int i = startLine; i <= endLine; i++) {
1021                 sourceLine = source.sourceLine(i);
1022                 if (sourceLine == null) {
1023                     break;
1024                 }
1025                 out.print(i);
1026                 out.print("\t");
1027                 if (i == lineno) {
1028                     out.print("=> ");
1029                 } else {
1030                     out.print("   ");
1031                 }
1032                 out.println(sourceLine);
1033             }
1034             out.show();
1035         }
1036     }
1037 
1038     // Command: use
1039     // Get or set the source file path list.
1040 
1041     private void commandUse(StringTokenizer t) {
1042         if (!t.hasMoreTokens()) {
1043             out.println(sourceManager.getSourcePath().asString());
1044         } else {
1045             //### Should throw exception for invalid path.
1046             //### E.g., vetoable property change.
1047             sourceManager.setSourcePath(new SearchPath(t.nextToken()));
1048         }
1049     }
1050 
1051     // Command: sourcepath
1052     // Get or set the source file path list.  (Alternate to 'use'.)
1053 
1054     private void commandSourcepath(StringTokenizer t) {
1055         if (!t.hasMoreTokens()) {
1056             out.println(sourceManager.getSourcePath().asString());
1057         } else {
1058             //### Should throw exception for invalid path.
1059             //### E.g., vetoable property change.
1060             sourceManager.setSourcePath(new SearchPath(t.nextToken()));
1061         }
1062     }
1063 
1064     // Command: classpath
1065     // Get or set the class file path list.
1066 
1067     private void commandClasspath(StringTokenizer t) {
1068         if (!t.hasMoreTokens()) {
1069             out.println(classManager.getClassPath().asString());
1070         } else {
1071             //### Should throw exception for invalid path.
1072             //### E.g., vetoable property change.
1073             classManager.setClassPath(new SearchPath(t.nextToken()));
1074         }
1075     }
1076 
1077     // Command: view
1078     // Display source for source file or class.
1079 
1080     private void commandView(StringTokenizer t) throws NoSessionException {
1081         if (!t.hasMoreTokens()) {
1082             env.error("Argument required");
1083         } else {
1084             String name = t.nextToken();
1085             if (name.endsWith(".java") ||
1086                 name.indexOf(File.separatorChar) >= 0) {
1087                 env.viewSource(name);
1088             } else {
1089                 //### JDI crashes taking line number for class.
1090                 /*****
1091                 ReferenceType cls = findClass(name);
1092                 if (cls != null) {
1093                     env.viewLocation(cls.location());
1094                 } else {
1095                     env.failure("No such class");
1096                 }
1097                 *****/
1098                 String fileName = name.replace('.', File.separatorChar) + ".java";
1099                 env.viewSource(fileName);
1100             }
1101         }
1102     }
1103 
1104     // Command: locals
1105     // Print all local variables in current stack frame.
1106 
1107     private void commandLocals() throws NoSessionException {
1108         ThreadReference current = context.getCurrentThread();
1109         if (current == null) {
1110             env.failure("No default thread specified: " +
1111                         "use the \"thread\" command first.");
1112             return;
1113         }
1114         StackFrame frame;
1115         try {
1116             frame = context.getCurrentFrame(current);
1117             if (frame == null) {
1118                 env.failure("Thread has not yet created any stack frames.");
1119                 return;
1120             }
1121         } catch (VMNotInterruptedException e) {
1122             env.failure("Target VM must be in interrupted state.");
1123             return;
1124         }
1125 
1126         List<LocalVariable> vars;
1127         try {
1128             vars = frame.visibleVariables();
1129             if (vars == null || vars.size() == 0) {
1130                 env.failure("No local variables");
1131                 return;
1132             }
1133         } catch (AbsentInformationException e) {
1134             env.failure("Local variable information not available." +
1135                         " Compile with -g to generate variable information");
1136             return;
1137         }
1138 
1139         OutputSink out = env.getOutputSink();
1140         out.println("Method arguments:");
1141         for (LocalVariable var : vars) {
1142             if (var.isArgument()) {
1143                 printVar(out, var, frame);
1144             }
1145         }
1146         out.println("Local variables:");
1147         for (LocalVariable var : vars) {
1148             if (!var.isArgument()) {
1149                 printVar(out, var, frame);
1150             }
1151         }
1152         out.show();
1153         return;
1154     }
1155 
1156     /**
1157      * Command: monitor
1158      * Monitor an expression
1159      */
1160     private void commandMonitor(StringTokenizer t) throws NoSessionException {
1161         if (!t.hasMoreTokens()) {
1162             env.error("Argument required");
1163         } else {
1164             env.getMonitorListModel().add(t.nextToken(""));
1165         }
1166     }
1167 
1168     /**
1169      * Command: unmonitor
1170      * Unmonitor an expression
1171      */
1172     private void commandUnmonitor(StringTokenizer t) throws NoSessionException {
1173         if (!t.hasMoreTokens()) {
1174             env.error("Argument required");
1175         } else {
1176             env.getMonitorListModel().remove(t.nextToken(""));
1177         }
1178     }
1179 
1180     // Print a stack variable.
1181 
1182     private void printVar(OutputSink out, LocalVariable var, StackFrame frame) {
1183         out.print("  " + var.name());
1184         if (var.isVisible(frame)) {
1185             Value val = frame.getValue(var);
1186             out.println(" = " + val.toString());
1187         } else {
1188             out.println(" is not in scope");
1189         }
1190     }
1191 
1192     // Command: print
1193     // Evaluate an expression.
1194 
1195     private void commandPrint(StringTokenizer t, boolean dumpObject) throws NoSessionException {
1196         if (!t.hasMoreTokens()) {
1197             //### Probably confused if expresion contains whitespace.
1198             env.error("No expression specified.");
1199             return;
1200         }
1201         ThreadReference current = context.getCurrentThread();
1202         if (current == null) {
1203             env.failure("No default thread specified: " +
1204                         "use the \"thread\" command first.");
1205             return;
1206         }
1207         StackFrame frame;
1208         try {
1209             frame = context.getCurrentFrame(current);
1210             if (frame == null) {
1211                 env.failure("Thread has not yet created any stack frames.");
1212                 return;
1213             }
1214         } catch (VMNotInterruptedException e) {
1215             env.failure("Target VM must be in interrupted state.");
1216             return;
1217         }
1218         while (t.hasMoreTokens()) {
1219             String expr = t.nextToken("");
1220             Value val = null;
1221             try {
1222                 val = runtime.evaluate(frame, expr);
1223             } catch(Exception e) {
1224                 env.error("Exception: " + e);
1225                 //### Fix this!
1226             }
1227             if (val == null) {
1228                 return;  // Error message already printed
1229             }
1230             OutputSink out = env.getOutputSink();
1231             if (dumpObject && (val instanceof ObjectReference) &&
1232                                  !(val instanceof StringReference)) {
1233                 ObjectReference obj = (ObjectReference)val;
1234                 ReferenceType refType = obj.referenceType();
1235                 out.println(expr + " = " + val.toString() + " {");
1236                 dump(out, obj, refType, refType);
1237                 out.println("}");
1238             } else {
1239                 out.println(expr + " = " + val.toString());
1240             }
1241             out.show();
1242         }
1243     }
1244 
1245     private void dump(OutputSink out,
1246                       ObjectReference obj, ReferenceType refType,
1247                       ReferenceType refTypeBase) {
1248         for (Field field : refType.fields()) {
1249             out.print("    ");
1250             if (!refType.equals(refTypeBase)) {
1251                 out.print(refType.name() + ".");
1252             }
1253             out.print(field.name() + ": ");
1254             Object o = obj.getValue(field);
1255             out.println((o == null) ? "null" : o.toString()); // Bug ID 4374471
1256         }
1257         if (refType instanceof ClassType) {
1258             ClassType sup = ((ClassType)refType).superclass();
1259             if (sup != null) {
1260                 dump(out, obj, sup, refTypeBase);
1261             }
1262         } else if (refType instanceof InterfaceType) {
1263             for (InterfaceType sup : ((InterfaceType)refType).superinterfaces()) {
1264                 dump(out, obj, sup, refTypeBase);
1265             }
1266         }
1267     }
1268 
1269     /*
1270      * Display help message.
1271      */
1272 
1273     private void help() {
1274         out.println("** command list **");
1275         out.println("threads [threadgroup]     -- list threads");
1276         out.println("thread <thread id>        -- set default thread");
1277         out.println("suspend [thread id(s)]    -- suspend threads (default: all)");
1278         out.println("resume [thread id(s)]     -- resume threads (default: all)");
1279         out.println("where [thread id] | all   -- dump a thread's stack");
1280         out.println("wherei [thread id] | all  -- dump a thread's stack, with pc info");
1281         out.println("threadgroups              -- list threadgroups");
1282         out.println("threadgroup <name>        -- set current threadgroup\n");
1283 //      out.println("print <expression>        -- print value of expression");
1284         out.println("dump <expression>         -- print all object information\n");
1285 //      out.println("eval <expression>         -- evaluate expression (same as print)");
1286         out.println("locals                    -- print all local variables in current stack frame\n");
1287         out.println("classes                   -- list currently known classes");
1288         out.println("methods <class id>        -- list a class's methods\n");
1289         out.println("stop [in] <class id>.<method>[(argument_type,...)] -- set a breakpoint in a method");
1290         out.println("stop [at] <class id>:<line> -- set a breakpoint at a line");
1291         out.println("up [n frames]             -- move up a thread's stack");
1292         out.println("down [n frames]           -- move down a thread's stack");
1293         out.println("frame <frame-id>           -- to a frame");
1294         out.println("clear <class id>.<method>[(argument_type,...)]   -- clear a breakpoint in a method");
1295         out.println("clear <class id>:<line>   -- clear a breakpoint at a line");
1296         out.println("clear                     -- list breakpoints");
1297         out.println("step                      -- execute current line");
1298         out.println("step up                   -- execute until the current method returns to its caller");
1299         out.println("stepi                     -- execute current instruction");
1300         out.println("next                      -- step one line (step OVER calls)");
1301         out.println("nexti                     -- step one instruction (step OVER calls)");
1302         out.println("cont                      -- continue execution from breakpoint\n");
1303 //      out.println("catch <class id>          -- break for the specified exception");
1304 //      out.println("ignore <class id>         -- ignore when the specified exception\n");
1305         out.println("view classname|filename   -- display source file");
1306         out.println("list [line number|method] -- print source code context at line or method");
1307         out.println("use <source file path>    -- display or change the source path\n");
1308 //### new
1309         out.println("sourcepath <source file path>    -- display or change the source path\n");
1310 //### new
1311         out.println("classpath <class file path>    -- display or change the class path\n");
1312         out.println("monitor <expression>      -- evaluate an expression each time the program stops\n");
1313         out.println("unmonitor <monitor#>      -- delete a monitor\n");
1314         out.println("read <filename>           -- read and execute a command file\n");
1315 //      out.println("memory                    -- report memory usage");
1316 //      out.println("gc                        -- free unused objects\n");
1317         out.println("run <class> [args]        -- start execution of a Java class");
1318         out.println("run                       -- re-execute last class run");
1319         out.println("load <class> [args]       -- start execution of a Java class, initially suspended");
1320         out.println("load                      -- re-execute last class run, initially suspended");
1321         out.println("attach <portname>         -- debug existing process\n");
1322         out.println("detach                    -- detach from debuggee process\n");
1323         out.println("kill <thread(group)>      -- kill a thread or threadgroup\n");
1324         out.println("!!                        -- repeat last command");
1325         out.println("help (or ?)               -- list commands");
1326         out.println("exit (or quit)            -- exit debugger");
1327     }
1328 
1329     /*
1330      * Execute a command.
1331      */
1332 
1333     public void executeCommand(String command) {
1334         //### Treatment of 'out' here is dirty...
1335         out = env.getOutputSink();
1336         if (echo) {
1337             out.println(">>> " + command);
1338         }
1339         StringTokenizer t = new StringTokenizer(command);
1340         try {
1341             String cmd;
1342             if (t.hasMoreTokens()) {
1343                 cmd = t.nextToken().toLowerCase();
1344                 lastCommand = cmd;
1345             } else {
1346                 cmd = lastCommand;
1347             }
1348             if (cmd.equals("print")) {
1349                 commandPrint(t, false);
1350             } else if (cmd.equals("eval")) {
1351                 commandPrint(t, false);
1352             } else if (cmd.equals("dump")) {
1353                 commandPrint(t, true);
1354             } else if (cmd.equals("locals")) {
1355                 commandLocals();
1356             } else if (cmd.equals("classes")) {
1357                 commandClasses();
1358             } else if (cmd.equals("methods")) {
1359                 commandMethods(t);
1360             } else if (cmd.equals("threads")) {
1361                 commandThreads(t);
1362             } else if (cmd.equals("thread")) {
1363                 commandThread(t);
1364             } else if (cmd.equals("suspend")) {
1365                 commandSuspend(t);
1366             } else if (cmd.equals("resume")) {
1367                 commandResume(t);
1368             } else if (cmd.equals("cont")) {
1369                 commandCont();
1370             } else if (cmd.equals("threadgroups")) {
1371                 commandThreadGroups();
1372             } else if (cmd.equals("threadgroup")) {
1373                 commandThreadGroup(t);
1374             } else if (cmd.equals("run")) {
1375                 commandRun(t);
1376             } else if (cmd.equals("load")) {
1377                 commandLoad(t);
1378             } else if (cmd.equals("connect")) {
1379                 commandConnect(t);
1380             } else if (cmd.equals("attach")) {
1381                 commandAttach(t);
1382             } else if (cmd.equals("detach")) {
1383                 commandDetach(t);
1384             } else if (cmd.equals("interrupt")) {
1385                 commandInterrupt(t);
1386 //### Not implemented.
1387 //          } else if (cmd.equals("catch")) {
1388 //              commandCatchException(t);
1389 //### Not implemented.
1390 //          } else if (cmd.equals("ignore")) {
1391 //              commandIgnoreException(t);
1392             } else if (cmd.equals("step")) {
1393                 commandStep(t);
1394             } else if (cmd.equals("stepi")) {
1395                 commandStepi();
1396             } else if (cmd.equals("next")) {
1397                 commandNext();
1398             } else if (cmd.equals("nexti")) {
1399                 commandNexti();
1400             } else if (cmd.equals("kill")) {
1401                 commandKill(t);
1402             } else if (cmd.equals("where")) {
1403                 commandWhere(t, false);
1404             } else if (cmd.equals("wherei")) {
1405                 commandWhere(t, true);
1406             } else if (cmd.equals("up")) {
1407                 commandUp(t);
1408             } else if (cmd.equals("down")) {
1409                 commandDown(t);
1410             } else if (cmd.equals("frame")) {
1411                 commandFrame(t);
1412             } else if (cmd.equals("stop")) {
1413                 commandStop(t);
1414             } else if (cmd.equals("clear")) {
1415                 commandClear(t);
1416             } else if (cmd.equals("list")) {
1417                 commandList(t);
1418             } else if (cmd.equals("use")) {
1419                 commandUse(t);
1420             } else if (cmd.equals("sourcepath")) {
1421                 commandSourcepath(t);
1422             } else if (cmd.equals("classpath")) {
1423                 commandClasspath(t);
1424             } else if (cmd.equals("monitor")) {
1425                 commandMonitor(t);
1426             } else if (cmd.equals("unmonitor")) {
1427                 commandUnmonitor(t);
1428             } else if (cmd.equals("view")) {
1429                 commandView(t);
1430 //          } else if (cmd.equals("read")) {
1431 //              readCommand(t);
1432             } else if (cmd.equals("help") || cmd.equals("?")) {
1433                 help();
1434             } else if (cmd.equals("quit") || cmd.equals("exit")) {
1435                 try {
1436                     runtime.detach();
1437                 } catch (NoSessionException e) {
1438                     // ignore
1439                 }
1440                 env.terminate();
1441             } else {
1442                 //### Dubious repeat-count feature inherited from 'jdb'
1443                 if (t.hasMoreTokens()) {
1444                     try {
1445                         int repeat = Integer.parseInt(cmd);
1446                         String subcom = t.nextToken("");
1447                         while (repeat-- > 0) {
1448                             executeCommand(subcom);
1449                         }
1450                         return;
1451                     } catch (NumberFormatException exc) {
1452                     }
1453                 }
1454                 out.println("huh? Try help...");
1455                 out.flush();
1456             }
1457         } catch (NoSessionException e) {
1458             out.println("There is no currently attached VM session.");
1459             out.flush();
1460         } catch (Exception e) {
1461             out.println("Internal exception: " + e.toString());
1462             out.flush();
1463             System.out.println("JDB internal exception: " + e.toString());
1464             e.printStackTrace();
1465         }
1466         out.show();
1467     }
1468 }