1 /*
   2  * Copyright (c) 1998, 2011, 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.bdi;
  36 
  37 import com.sun.jdi.*;
  38 import com.sun.jdi.request.*;
  39 import com.sun.jdi.connect.*;
  40 import com.sun.tools.example.debug.expr.ExpressionParser;
  41 import com.sun.tools.example.debug.expr.ParseException;
  42 
  43 import java.io.*;
  44 import java.util.*;
  45 
  46 import com.sun.tools.example.debug.event.*;
  47 
  48 import javax.swing.SwingUtilities;
  49 
  50 /**
  51  * Move this towards being only state and functionality
  52  * that spans across Sessions (and thus VMs).
  53  */
  54 public class ExecutionManager {
  55 
  56     private Session session;
  57 
  58     /**
  59      * Get/set JDI trace mode.
  60      */
  61     int traceMode = VirtualMachine.TRACE_NONE;
  62 
  63   //////////////////    Listener registration    //////////////////
  64 
  65   // Session Listeners
  66 
  67     ArrayList<SessionListener> sessionListeners = new ArrayList<SessionListener>();
  68 
  69     public void addSessionListener(SessionListener listener) {
  70         sessionListeners.add(listener);
  71     }
  72 
  73     public void removeSessionListener(SessionListener listener) {
  74         sessionListeners.remove(listener);
  75     }
  76 
  77   // Spec Listeners
  78 
  79   ArrayList<SpecListener> specListeners = new ArrayList<SpecListener>();
  80 
  81     public void addSpecListener(SpecListener cl) {
  82         specListeners.add(cl);
  83     }
  84 
  85     public void removeSpecListener(SpecListener cl) {
  86         specListeners.remove(cl);
  87     }
  88 
  89     // JDI Listeners
  90 
  91     ArrayList<JDIListener> jdiListeners = new ArrayList<JDIListener>();
  92 
  93     /**
  94      * Adds a JDIListener
  95      */
  96     public void addJDIListener(JDIListener jl) {
  97         jdiListeners.add(jl);
  98     }
  99 
 100     /**
 101      * Adds a JDIListener - at the specified position
 102      */
 103     public void addJDIListener(int index, JDIListener jl) {
 104         jdiListeners.add(index, jl);
 105     }
 106 
 107     /**
 108      * Removes a JDIListener
 109      */
 110     public void removeJDIListener(JDIListener jl) {
 111         jdiListeners.remove(jl);
 112     }
 113 
 114   // App Echo Listeners
 115 
 116     private ArrayList<OutputListener> appEchoListeners = new ArrayList<OutputListener>();
 117 
 118     public void addApplicationEchoListener(OutputListener l) {
 119         appEchoListeners.add(l);
 120     }
 121 
 122     public void removeApplicationEchoListener(OutputListener l) {
 123         appEchoListeners.remove(l);
 124     }
 125 
 126   // App Output Listeners
 127 
 128     private ArrayList<OutputListener> appOutputListeners = new ArrayList<OutputListener>();
 129 
 130     public void addApplicationOutputListener(OutputListener l) {
 131         appOutputListeners.add(l);
 132     }
 133 
 134     public void removeApplicationOutputListener(OutputListener l) {
 135         appOutputListeners.remove(l);
 136     }
 137 
 138   // App Error Listeners
 139 
 140     private ArrayList<OutputListener> appErrorListeners = new ArrayList<OutputListener>();
 141 
 142     public void addApplicationErrorListener(OutputListener l) {
 143         appErrorListeners.add(l);
 144     }
 145 
 146     public void removeApplicationErrorListener(OutputListener l) {
 147         appErrorListeners.remove(l);
 148     }
 149 
 150   // Diagnostic Listeners
 151 
 152     private ArrayList<OutputListener> diagnosticsListeners = new ArrayList<OutputListener>();
 153 
 154     public void addDiagnosticsListener(OutputListener l) {
 155         diagnosticsListeners.add(l);
 156     }
 157 
 158     public void removeDiagnosticsListener(OutputListener l) {
 159         diagnosticsListeners.remove(l);
 160     }
 161 
 162   ///////////    End Listener Registration    //////////////
 163 
 164     //### We probably don't want this public
 165     public VirtualMachine vm() {
 166         return session == null ? null : session.vm;
 167     }
 168 
 169     void ensureActiveSession() throws NoSessionException {
 170         if (session == null) {
 171          throw new NoSessionException();
 172       }
 173     }
 174 
 175     public EventRequestManager eventRequestManager() {
 176         return vm() == null ? null : vm().eventRequestManager();
 177     }
 178 
 179     /**
 180      * Get JDI trace mode.
 181      */
 182     public int getTraceMode(int mode) {
 183         return traceMode;
 184     }
 185 
 186     /**
 187      * Set JDI trace mode.
 188      */
 189     public void setTraceMode(int mode) {
 190         traceMode = mode;
 191         if (session != null) {
 192             session.setTraceMode(mode);
 193         }
 194     }
 195 
 196     /**
 197      * Determine if VM is interrupted, i.e, present and not running.
 198      */
 199     public boolean isInterrupted() /* should: throws NoSessionException */ {
 200 //      ensureActiveSession();
 201         return session.interrupted;
 202     }
 203 
 204     /**
 205      * Return a list of ReferenceType objects for all
 206      * currently loaded classes and interfaces.
 207      * Array types are not returned.
 208      */
 209     public List<ReferenceType> allClasses() throws NoSessionException {
 210         ensureActiveSession();
 211         return vm().allClasses();
 212     }
 213 
 214     /**
 215      * Return a ReferenceType object for the currently
 216      * loaded class or interface whose fully-qualified
 217      * class name is specified, else return null if there
 218      * is none.
 219      *
 220      * In general, we must return a list of types, because
 221      * multiple class loaders could have loaded a class
 222      * with the same fully-qualified name.
 223      */
 224     public List<ReferenceType> findClassesByName(String name) throws NoSessionException {
 225         ensureActiveSession();
 226         return vm().classesByName(name);
 227     }
 228 
 229     /**
 230      * Return a list of ReferenceType objects for all
 231      * currently loaded classes and interfaces whose name
 232      * matches the given pattern.  The pattern syntax is
 233      * open to some future revision, but currently consists
 234      * of a fully-qualified class name in which the first
 235      * component may optionally be a "*" character, designating
 236      * an arbitrary prefix.
 237      */
 238     public List<ReferenceType> findClassesMatchingPattern(String pattern)
 239                                                 throws NoSessionException {
 240         ensureActiveSession();
 241         List<ReferenceType> result = new ArrayList<ReferenceType>();  //### Is default size OK?
 242         if (pattern.startsWith("*.")) {
 243             // Wildcard matches any leading package name.
 244             pattern = pattern.substring(1);
 245             for (ReferenceType type : vm().allClasses()) {
 246                 if (type.name().endsWith(pattern)) {
 247                     result.add(type);
 248                 }
 249             }
 250             return result;
 251         } else {
 252             // It's a class name.
 253             return vm().classesByName(pattern);
 254         }
 255     }
 256 
 257     /*
 258      * Return a list of ThreadReference objects corresponding
 259      * to the threads that are currently active in the VM.
 260      * A thread is removed from the list just before the
 261      * thread terminates.
 262      */
 263 
 264     public List<ThreadReference> allThreads() throws NoSessionException {
 265         ensureActiveSession();
 266         return vm().allThreads();
 267     }
 268 
 269     /*
 270      * Return a list of ThreadGroupReference objects corresponding
 271      * to the top-level threadgroups that are currently active in the VM.
 272      * Note that a thread group may be empty, or contain no threads as
 273      * descendents.
 274      */
 275 
 276     public List<ThreadGroupReference> topLevelThreadGroups() throws NoSessionException {
 277         ensureActiveSession();
 278         return vm().topLevelThreadGroups();
 279     }
 280 
 281     /*
 282      * Return the system threadgroup.
 283      */
 284 
 285     public ThreadGroupReference systemThreadGroup()
 286                                                 throws NoSessionException {
 287         ensureActiveSession();
 288         return vm().topLevelThreadGroups().get(0);
 289     }
 290 
 291     /*
 292      * Evaluate an expression.
 293      */
 294 
 295     public Value evaluate(final StackFrame f, String expr)
 296         throws ParseException,
 297                                             InvocationException,
 298                                             InvalidTypeException,
 299                                             ClassNotLoadedException,
 300                                             NoSessionException,
 301                                             IncompatibleThreadStateException {
 302         ExpressionParser.GetFrame frameGetter = null;
 303         ensureActiveSession();
 304         if (f != null) {
 305             frameGetter = new ExpressionParser.GetFrame() {
 306                 @Override
 307                 public StackFrame get() /* throws IncompatibleThreadStateException */ {
 308                     return f;
 309                 }
 310             };
 311         }
 312         return ExpressionParser.evaluate(expr, vm(), frameGetter);
 313     }
 314 
 315 
 316     /*
 317      * Start a new VM.
 318      */
 319 
 320     public void run(boolean suspended,
 321                     String vmArgs,
 322                     String className,
 323                     String args) throws VMLaunchFailureException {
 324 
 325         endSession();
 326 
 327         //### Set a breakpoint on 'main' method.
 328         //### Would be cleaner if we could just bring up VM already suspended.
 329         if (suspended) {
 330             //### Set breakpoint at 'main(java.lang.String[])'.
 331             List<String> argList = new ArrayList<String>(1);
 332             argList.add("java.lang.String[]");
 333             createMethodBreakpoint(className, "main", argList);
 334         }
 335 
 336         String cmdLine = className + " " + args;
 337 
 338         startSession(new ChildSession(this, vmArgs, cmdLine,
 339                                       appInput, appOutput, appError,
 340                                       diagnostics));
 341     }
 342 
 343     /*
 344      * Attach to an existing VM.
 345      */
 346     public void attach(String portName) throws VMLaunchFailureException {
 347         endSession();
 348 
 349         //### Changes made here for connectors have broken the
 350         //### the 'Session' abstraction.  The 'Session.attach()'
 351         //### method is intended to encapsulate all of the various
 352         //### ways in which session start-up can fail. (maddox 12/18/98)
 353 
 354         /*
 355          * Now that attaches and launches both go through Connectors,
 356          * it may be worth creating a new subclass of Session for
 357          * attach sessions.
 358          */
 359         VirtualMachineManager mgr = Bootstrap.virtualMachineManager();
 360         AttachingConnector connector = mgr.attachingConnectors().get(0);
 361         Map<String, Connector.Argument> arguments = connector.defaultArguments();
 362         arguments.get("port").setValue(portName);
 363 
 364         Session newSession = internalAttach(connector, arguments);
 365         if (newSession != null) {
 366             startSession(newSession);
 367         }
 368     }
 369 
 370     private Session internalAttach(AttachingConnector connector,
 371                                    Map<String, Connector.Argument> arguments) {
 372         try {
 373             VirtualMachine vm = connector.attach(arguments);
 374             return new Session(vm, this, diagnostics);
 375         } catch (IOException ioe) {
 376             diagnostics.putString("\n Unable to attach to target VM: " +
 377                                   ioe.getMessage());
 378         } catch (IllegalConnectorArgumentsException icae) {
 379             diagnostics.putString("\n Invalid connector arguments: " +
 380                                   icae.getMessage());
 381         }
 382         return null;
 383     }
 384 
 385     private Session internalListen(ListeningConnector connector,
 386                                    Map<String, Connector.Argument> arguments) {
 387         try {
 388             VirtualMachine vm = connector.accept(arguments);
 389             return new Session(vm, this, diagnostics);
 390         } catch (IOException ioe) {
 391             diagnostics.putString(
 392                   "\n Unable to accept connection to target VM: " +
 393                                   ioe.getMessage());
 394         } catch (IllegalConnectorArgumentsException icae) {
 395             diagnostics.putString("\n Invalid connector arguments: " +
 396                                   icae.getMessage());
 397         }
 398         return null;
 399     }
 400 
 401     /*
 402      * Connect via user specified arguments
 403      * @return true on success
 404      */
 405     public boolean explictStart(Connector connector, Map<String, Connector.Argument> arguments)
 406                                            throws VMLaunchFailureException {
 407         Session newSession = null;
 408 
 409         endSession();
 410 
 411         if (connector instanceof LaunchingConnector) {
 412             // we were launched, use ChildSession
 413             newSession = new ChildSession(this, (LaunchingConnector)connector,
 414                                           arguments,
 415                                           appInput, appOutput, appError,
 416                                           diagnostics);
 417         } else if (connector instanceof AttachingConnector) {
 418             newSession = internalAttach((AttachingConnector)connector,
 419                                         arguments);
 420         } else if (connector instanceof ListeningConnector) {
 421             newSession = internalListen((ListeningConnector)connector,
 422                                         arguments);
 423         } else {
 424             diagnostics.putString("\n Unknown connector: " + connector);
 425         }
 426         if (newSession != null) {
 427             startSession(newSession);
 428         }
 429         return newSession != null;
 430     }
 431 
 432     /*
 433      * Detach from VM.  If VM was started by debugger, terminate it.
 434      */
 435     public void detach() throws NoSessionException {
 436         ensureActiveSession();
 437         endSession();
 438     }
 439 
 440     private void startSession(Session s) throws VMLaunchFailureException {
 441         if (!s.attach()) {
 442             throw new VMLaunchFailureException();
 443         }
 444         session = s;
 445         EventRequestManager em = vm().eventRequestManager();
 446         ClassPrepareRequest classPrepareRequest = em.createClassPrepareRequest();
 447         //### We must allow the deferred breakpoints to be resolved before
 448         //### we continue executing the class.  We could optimize if there
 449         //### were no deferred breakpoints outstanding for a particular class.
 450         //### Can we do this with JDI?
 451         classPrepareRequest.setSuspendPolicy(EventRequest.SUSPEND_ALL);
 452         classPrepareRequest.enable();
 453         ClassUnloadRequest classUnloadRequest = em.createClassUnloadRequest();
 454         classUnloadRequest.setSuspendPolicy(EventRequest.SUSPEND_NONE);
 455         classUnloadRequest.enable();
 456         ThreadStartRequest threadStartRequest = em.createThreadStartRequest();
 457         threadStartRequest.setSuspendPolicy(EventRequest.SUSPEND_NONE);
 458         threadStartRequest.enable();
 459         ThreadDeathRequest threadDeathRequest = em.createThreadDeathRequest();
 460         threadDeathRequest.setSuspendPolicy(EventRequest.SUSPEND_NONE);
 461         threadDeathRequest.enable();
 462         ExceptionRequest exceptionRequest =
 463                                 em.createExceptionRequest(null, false, true);
 464         exceptionRequest.setSuspendPolicy(EventRequest.SUSPEND_ALL);
 465         exceptionRequest.enable();
 466         validateThreadInfo();
 467         session.interrupted = true;
 468         notifySessionStart();
 469     }
 470 
 471     void endSession() {
 472         if (session != null) {
 473             session.detach();
 474             session = null;
 475             invalidateThreadInfo();
 476             notifySessionDeath();
 477         }
 478     }
 479 
 480     /*
 481      * Suspend all VM activity.
 482      */
 483 
 484     public void interrupt() throws NoSessionException {
 485         ensureActiveSession();
 486         vm().suspend();
 487         //### Is it guaranteed that the interrupt has happened?
 488         validateThreadInfo();
 489         session.interrupted = true;
 490         notifyInterrupted();
 491     }
 492 
 493     /*
 494      * Resume interrupted VM.
 495      */
 496 
 497     public void go() throws NoSessionException, VMNotInterruptedException {
 498         ensureActiveSession();
 499         invalidateThreadInfo();
 500         session.interrupted = false;
 501         notifyContinued();
 502         vm().resume();
 503     }
 504 
 505     /*
 506      * Stepping.
 507      */
 508     void clearPreviousStep(ThreadReference thread) {
 509         /*
 510          * A previous step may not have completed on this thread;
 511          * if so, it gets removed here.
 512          */
 513          EventRequestManager mgr = vm().eventRequestManager();
 514          for (StepRequest request : mgr.stepRequests()) {
 515              if (request.thread().equals(thread)) {
 516                  mgr.deleteEventRequest(request);
 517                  break;
 518              }
 519          }
 520     }
 521 
 522     private void generalStep(ThreadReference thread, int size, int depth)
 523                         throws NoSessionException {
 524         ensureActiveSession();
 525         invalidateThreadInfo();
 526         session.interrupted = false;
 527         notifyContinued();
 528 
 529         clearPreviousStep(thread);
 530         EventRequestManager reqMgr = vm().eventRequestManager();
 531         StepRequest request = reqMgr.createStepRequest(thread,
 532                                                        size, depth);
 533         // We want just the next step event and no others
 534         request.addCountFilter(1);
 535         request.enable();
 536         vm().resume();
 537     }
 538 
 539     public void stepIntoInstruction(ThreadReference thread)
 540                         throws NoSessionException {
 541         generalStep(thread, StepRequest.STEP_MIN, StepRequest.STEP_INTO);
 542     }
 543 
 544     public void stepOverInstruction(ThreadReference thread)
 545                         throws NoSessionException {
 546         generalStep(thread, StepRequest.STEP_MIN, StepRequest.STEP_OVER);
 547     }
 548 
 549     public void stepIntoLine(ThreadReference thread)
 550                         throws NoSessionException,
 551                         AbsentInformationException {
 552         generalStep(thread, StepRequest.STEP_LINE, StepRequest.STEP_INTO);
 553     }
 554 
 555     public void stepOverLine(ThreadReference thread)
 556                         throws NoSessionException,
 557                         AbsentInformationException {
 558         generalStep(thread, StepRequest.STEP_LINE, StepRequest.STEP_OVER);
 559     }
 560 
 561     public void stepOut(ThreadReference thread)
 562                         throws NoSessionException {
 563         generalStep(thread, StepRequest.STEP_MIN, StepRequest.STEP_OUT);
 564     }
 565 
 566     /*
 567      * Thread control.
 568      */
 569 
 570     public void suspendThread(ThreadReference thread) throws NoSessionException {
 571         ensureActiveSession();
 572         thread.suspend();
 573     }
 574 
 575     public void resumeThread(ThreadReference thread) throws NoSessionException {
 576         ensureActiveSession();
 577         thread.resume();
 578     }
 579 
 580     public void stopThread(ThreadReference thread) throws NoSessionException {
 581         ensureActiveSession();
 582         //### Need an exception now.  Which one to use?
 583         //thread.stop();
 584     }
 585 
 586     /*
 587      * ThreadInfo objects -- Allow query of thread status and stack.
 588      */
 589 
 590     private List<ThreadInfo> threadInfoList = new LinkedList<ThreadInfo>();
 591     //### Should be weak! (in the value, not the key)
 592     private HashMap<ThreadReference, ThreadInfo> threadInfoMap = new HashMap<ThreadReference, ThreadInfo>();
 593 
 594     public ThreadInfo threadInfo(ThreadReference thread) {
 595         if (session == null || thread == null) {
 596             return null;
 597         }
 598         ThreadInfo info = threadInfoMap.get(thread);
 599         if (info == null) {
 600             //### Should not hardcode initial frame count and prefetch here!
 601             //info = new ThreadInfo(thread, 10, 10);
 602             info = new ThreadInfo(thread);
 603             if (session.interrupted) {
 604                 info.validate();
 605             }
 606             threadInfoList.add(info);
 607             threadInfoMap.put(thread, info);
 608         }
 609         return info;
 610     }
 611 
 612      void validateThreadInfo() {
 613         session.interrupted = true;
 614         for (ThreadInfo threadInfo : threadInfoList) {
 615             threadInfo.validate();
 616             }
 617     }
 618 
 619     private void invalidateThreadInfo() {
 620         if (session != null) {
 621             session.interrupted = false;
 622             for (ThreadInfo threadInfo : threadInfoList) {
 623                 threadInfo.invalidate();
 624             }
 625         }
 626     }
 627 
 628     void removeThreadInfo(ThreadReference thread) {
 629         ThreadInfo info = threadInfoMap.get(thread);
 630         if (info != null) {
 631             info.invalidate();
 632             threadInfoMap.remove(thread);
 633             threadInfoList.remove(info);
 634         }
 635     }
 636 
 637     /*
 638      * Listen for Session control events.
 639      */
 640 
 641     private void notifyInterrupted() {
 642       ArrayList<SessionListener> l = new ArrayList<SessionListener>(sessionListeners);
 643         EventObject evt = new EventObject(this);
 644         for (int i = 0; i < l.size(); i++) {
 645             l.get(i).sessionInterrupt(evt);
 646         }
 647     }
 648 
 649     private void notifyContinued() {
 650         ArrayList<SessionListener> l = new ArrayList<SessionListener>(sessionListeners);
 651         EventObject evt = new EventObject(this);
 652         for (int i = 0; i < l.size(); i++) {
 653             l.get(i).sessionContinue(evt);
 654         }
 655     }
 656 
 657     private void notifySessionStart() {
 658         ArrayList<SessionListener> l = new ArrayList<SessionListener>(sessionListeners);
 659         EventObject evt = new EventObject(this);
 660         for (int i = 0; i < l.size(); i++) {
 661             l.get(i).sessionStart(evt);
 662         }
 663     }
 664 
 665     private void notifySessionDeath() {
 666 /*** noop for now
 667         ArrayList<SessionListener> l = new ArrayList<SessionListener>(sessionListeners);
 668         EventObject evt = new EventObject(this);
 669         for (int i = 0; i < l.size(); i++) {
 670             ((SessionListener)l.get(i)).sessionDeath(evt);
 671         }
 672 ****/
 673     }
 674 
 675     /*
 676      * Listen for input and output requests from the application
 677      * being debugged.  These are generated only when the debuggee
 678      * is spawned as a child of the debugger.
 679      */
 680 
 681     private Object inputLock = new Object();
 682     private LinkedList<String> inputBuffer = new LinkedList<String>();
 683 
 684     private void resetInputBuffer() {
 685         synchronized (inputLock) {
 686             inputBuffer = new LinkedList<String>();
 687         }
 688     }
 689 
 690     public void sendLineToApplication(String line) {
 691         synchronized (inputLock) {
 692             inputBuffer.addFirst(line);
 693             inputLock.notifyAll();
 694         }
 695     }
 696 
 697     private InputListener appInput = new InputListener() {
 698         @Override
 699         public String getLine() {
 700             // Don't allow reader to be interrupted -- catch and retry.
 701             String line = null;
 702             while (line == null) {
 703                 synchronized (inputLock) {
 704                     try {
 705                         while (inputBuffer.size() < 1) {
 706                             inputLock.wait();
 707                         }
 708                         line = inputBuffer.removeLast();
 709                     } catch (InterruptedException e) {}
 710                 }
 711             }
 712             // We must not be holding inputLock here, as the listener
 713             // that we call to echo a line might call us re-entrantly
 714             // to provide another line of input.
 715             // Run in Swing event dispatcher thread.
 716             final String input = line;
 717             SwingUtilities.invokeLater(new Runnable() {
 718                 @Override
 719                 public void run() {
 720                     echoInputLine(input);
 721                 }
 722             });
 723             return line;
 724         }
 725     };
 726 
 727     private static String newline = System.getProperty("line.separator");
 728 
 729     private void echoInputLine(String line) {
 730         ArrayList<OutputListener> l = new ArrayList<OutputListener>(appEchoListeners);
 731         for (int i = 0; i < l.size(); i++) {
 732             OutputListener ol = l.get(i);
 733             ol.putString(line);
 734             ol.putString(newline);
 735         }
 736     }
 737 
 738     private OutputListener appOutput = new OutputListener() {
 739       @Override
 740         public void putString(String string) {
 741             ArrayList<OutputListener> l = new ArrayList<OutputListener>(appEchoListeners);
 742             for (int i = 0; i < l.size(); i++) {
 743                 l.get(i).putString(string);
 744             }
 745         }
 746     };
 747 
 748     private OutputListener appError = new OutputListener() {
 749       @Override
 750         public void putString(String string) {
 751             ArrayList<OutputListener> l = new ArrayList<OutputListener>(appEchoListeners);
 752             for (int i = 0; i < l.size(); i++) {
 753                 l.get(i).putString(string);
 754             }
 755         }
 756     };
 757 
 758    private OutputListener diagnostics = new OutputListener() {
 759       @Override
 760         public void putString(String string) {
 761             ArrayList<OutputListener> l = new ArrayList<OutputListener>(diagnosticsListeners);
 762             for (int i = 0; i < l.size(); i++) {
 763                 l.get(i).putString(string);
 764             }
 765         }
 766    };
 767 
 768   /////////////    Spec Request Creation/Deletion/Query   ///////////
 769 
 770     private EventRequestSpecList specList = new EventRequestSpecList(this);
 771 
 772     public BreakpointSpec
 773     createSourceLineBreakpoint(String sourceName, int line) {
 774         return specList.createSourceLineBreakpoint(sourceName, line);
 775     }
 776 
 777     public BreakpointSpec
 778     createClassLineBreakpoint(String classPattern, int line) {
 779         return specList.createClassLineBreakpoint(classPattern, line);
 780     }
 781 
 782     public BreakpointSpec
 783     createMethodBreakpoint(String classPattern,
 784                            String methodId, List<String> methodArgs) {
 785         return specList.createMethodBreakpoint(classPattern,
 786                                                  methodId, methodArgs);
 787     }
 788 
 789     public ExceptionSpec
 790     createExceptionIntercept(String classPattern,
 791                              boolean notifyCaught,
 792                              boolean notifyUncaught) {
 793         return specList.createExceptionIntercept(classPattern,
 794                                                    notifyCaught,
 795                                                    notifyUncaught);
 796     }
 797 
 798     public AccessWatchpointSpec
 799     createAccessWatchpoint(String classPattern, String fieldId) {
 800         return specList.createAccessWatchpoint(classPattern, fieldId);
 801     }
 802 
 803     public ModificationWatchpointSpec
 804     createModificationWatchpoint(String classPattern, String fieldId) {
 805         return specList.createModificationWatchpoint(classPattern,
 806                                                        fieldId);
 807     }
 808 
 809     public void delete(EventRequestSpec spec) {
 810         specList.delete(spec);
 811     }
 812 
 813     void resolve(ReferenceType refType) {
 814         specList.resolve(refType);
 815     }
 816 
 817     public void install(EventRequestSpec spec) {
 818         specList.install(spec, vm());
 819     }
 820 
 821     public List<EventRequestSpec> eventRequestSpecs() {
 822         return specList.eventRequestSpecs();
 823     }
 824 }