aboutsummaryrefslogtreecommitdiff
path: root/src/share/classes/com/sun/tools/example/debug/bdi/ExecutionManager.java
blob: 35f2075daa921a825c97c11322103feffb56420c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
/*
 * Copyright (c) 1998, 2008, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.tools.example.debug.bdi;

import com.sun.jdi.*;
import com.sun.jdi.request.*;
import com.sun.jdi.connect.*;
import com.sun.tools.example.debug.expr.ExpressionParser;
import com.sun.tools.example.debug.expr.ParseException;

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

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

import javax.swing.SwingUtilities;

/**
 * Move this towards being only state and functionality
 * that spans across Sessions (and thus VMs).
 */
public class ExecutionManager {

    private Session session;

    /**
     * Get/set JDI trace mode.
     */
    int traceMode = VirtualMachine.TRACE_NONE;

  //////////////////    Listener registration    //////////////////

  // Session Listeners

    ArrayList<SessionListener> sessionListeners = new ArrayList<SessionListener>();

    public void addSessionListener(SessionListener listener) {
        sessionListeners.add(listener);
    }

    public void removeSessionListener(SessionListener listener) {
        sessionListeners.remove(listener);
    }

  // Spec Listeners

  ArrayList<SpecListener> specListeners = new ArrayList<SpecListener>();

    public void addSpecListener(SpecListener cl) {
        specListeners.add(cl);
    }

    public void removeSpecListener(SpecListener cl) {
        specListeners.remove(cl);
    }

    // JDI Listeners

    ArrayList<JDIListener> jdiListeners = new ArrayList<JDIListener>();

    /**
     * Adds a JDIListener
     */
    public void addJDIListener(JDIListener jl) {
        jdiListeners.add(jl);
    }

    /**
     * Adds a JDIListener - at the specified position
     */
    public void addJDIListener(int index, JDIListener jl) {
        jdiListeners.add(index, jl);
    }

    /**
     * Removes a JDIListener
     */
    public void removeJDIListener(JDIListener jl) {
        jdiListeners.remove(jl);
    }

  // App Echo Listeners

    private ArrayList<OutputListener> appEchoListeners = new ArrayList<OutputListener>();

    public void addApplicationEchoListener(OutputListener l) {
        appEchoListeners.add(l);
    }

    public void removeApplicationEchoListener(OutputListener l) {
        appEchoListeners.remove(l);
    }

  // App Output Listeners

    private ArrayList<OutputListener> appOutputListeners = new ArrayList<OutputListener>();

    public void addApplicationOutputListener(OutputListener l) {
        appOutputListeners.add(l);
    }

    public void removeApplicationOutputListener(OutputListener l) {
        appOutputListeners.remove(l);
    }

  // App Error Listeners

    private ArrayList<OutputListener> appErrorListeners = new ArrayList<OutputListener>();

    public void addApplicationErrorListener(OutputListener l) {
        appErrorListeners.add(l);
    }

    public void removeApplicationErrorListener(OutputListener l) {
        appErrorListeners.remove(l);
    }

  // Diagnostic Listeners

    private ArrayList<OutputListener> diagnosticsListeners = new ArrayList<OutputListener>();

    public void addDiagnosticsListener(OutputListener l) {
        diagnosticsListeners.add(l);
    }

    public void removeDiagnosticsListener(OutputListener l) {
        diagnosticsListeners.remove(l);
    }

  ///////////    End Listener Registration    //////////////

    //### We probably don't want this public
    public VirtualMachine vm() {
        return session == null ? null : session.vm;
    }

    void ensureActiveSession() throws NoSessionException {
        if (session == null) {
         throw new NoSessionException();
      }
    }

    public EventRequestManager eventRequestManager() {
        return vm() == null ? null : vm().eventRequestManager();
    }

    /**
     * Get JDI trace mode.
     */
    public int getTraceMode(int mode) {
        return traceMode;
    }

    /**
     * Set JDI trace mode.
     */
    public void setTraceMode(int mode) {
        traceMode = mode;
        if (session != null) {
            session.setTraceMode(mode);
        }
    }

    /**
     * Determine if VM is interrupted, i.e, present and not running.
     */
    public boolean isInterrupted() /* should: throws NoSessionException */ {
//      ensureActiveSession();
        return session.interrupted;
    }

    /**
     * Return a list of ReferenceType objects for all
     * currently loaded classes and interfaces.
     * Array types are not returned.
     */
    public List<ReferenceType> allClasses() throws NoSessionException {
        ensureActiveSession();
        return vm().allClasses();
    }

    /**
     * Return a ReferenceType object for the currently
     * loaded class or interface whose fully-qualified
     * class name is specified, else return null if there
     * is none.
     *
     * In general, we must return a list of types, because
     * multiple class loaders could have loaded a class
     * with the same fully-qualified name.
     */
    public List<ReferenceType> findClassesByName(String name) throws NoSessionException {
        ensureActiveSession();
        return vm().classesByName(name);
    }

    /**
     * Return a list of ReferenceType objects for all
     * currently loaded classes and interfaces whose name
     * matches the given pattern.  The pattern syntax is
     * open to some future revision, but currently consists
     * of a fully-qualified class name in which the first
     * component may optionally be a "*" character, designating
     * an arbitrary prefix.
     */
    public List<ReferenceType> findClassesMatchingPattern(String pattern)
                                                throws NoSessionException {
        ensureActiveSession();
        List<ReferenceType> result = new ArrayList<ReferenceType>();  //### Is default size OK?
        if (pattern.startsWith("*.")) {
            // Wildcard matches any leading package name.
            pattern = pattern.substring(1);
            for (ReferenceType type : vm().allClasses()) {
                if (type.name().endsWith(pattern)) {
                    result.add(type);
                }
            }
            return result;
        } else {
            // It's a class name.
            return vm().classesByName(pattern);
        }
    }

    /*
     * Return a list of ThreadReference objects corresponding
     * to the threads that are currently active in the VM.
     * A thread is removed from the list just before the
     * thread terminates.
     */

    public List<ThreadReference> allThreads() throws NoSessionException {
        ensureActiveSession();
        return vm().allThreads();
    }

    /*
     * Return a list of ThreadGroupReference objects corresponding
     * to the top-level threadgroups that are currently active in the VM.
     * Note that a thread group may be empty, or contain no threads as
     * descendents.
     */

    public List<ThreadGroupReference> topLevelThreadGroups() throws NoSessionException {
        ensureActiveSession();
        return vm().topLevelThreadGroups();
    }

    /*
     * Return the system threadgroup.
     */

    public ThreadGroupReference systemThreadGroup()
                                                throws NoSessionException {
        ensureActiveSession();
        return vm().topLevelThreadGroups().get(0);
    }

    /*
     * Evaluate an expression.
     */

    public Value evaluate(final StackFrame f, String expr)
        throws ParseException,
                                            InvocationException,
                                            InvalidTypeException,
                                            ClassNotLoadedException,
                                            NoSessionException,
                                            IncompatibleThreadStateException {
        ExpressionParser.GetFrame frameGetter = null;
        ensureActiveSession();
        if (f != null) {
            frameGetter = new ExpressionParser.GetFrame() {
                @Override
                public StackFrame get() /* throws IncompatibleThreadStateException */ {
                    return f;
                }
            };
        }
        return ExpressionParser.evaluate(expr, vm(), frameGetter);
    }


    /*
     * Start a new VM.
     */

    public void run(boolean suspended,
                    String vmArgs,
                    String className,
                    String args) throws VMLaunchFailureException {

        endSession();

        //### Set a breakpoint on 'main' method.
        //### Would be cleaner if we could just bring up VM already suspended.
        if (suspended) {
            //### Set breakpoint at 'main(java.lang.String[])'.
            List<String> argList = new ArrayList<String>(1);
            argList.add("java.lang.String[]");
            createMethodBreakpoint(className, "main", argList);
        }

        String cmdLine = className + " " + args;

        startSession(new ChildSession(this, vmArgs, cmdLine,
                                      appInput, appOutput, appError,
                                      diagnostics));
    }

    /*
     * Attach to an existing VM.
     */
    public void attach(String portName) throws VMLaunchFailureException {
        endSession();

        //### Changes made here for connectors have broken the
        //### the 'Session' abstraction.  The 'Session.attach()'
        //### method is intended to encapsulate all of the various
        //### ways in which session start-up can fail. (maddox 12/18/98)

        /*
         * Now that attaches and launches both go through Connectors,
         * it may be worth creating a new subclass of Session for
         * attach sessions.
         */
        VirtualMachineManager mgr = Bootstrap.virtualMachineManager();
        AttachingConnector connector = mgr.attachingConnectors().get(0);
        Map<String, Connector.Argument> arguments = connector.defaultArguments();
        arguments.get("port").setValue(portName);

        Session newSession = internalAttach(connector, arguments);
        if (newSession != null) {
            startSession(newSession);
        }
    }

    private Session internalAttach(AttachingConnector connector,
                                   Map<String, Connector.Argument> arguments) {
        try {
            VirtualMachine vm = connector.attach(arguments);
            return new Session(vm, this, diagnostics);
        } catch (IOException ioe) {
            diagnostics.putString("\n Unable to attach to target VM: " +
                                  ioe.getMessage());
        } catch (IllegalConnectorArgumentsException icae) {
            diagnostics.putString("\n Invalid connector arguments: " +
                                  icae.getMessage());
        }
        return null;
    }

    private Session internalListen(ListeningConnector connector,
                                   Map<String, Connector.Argument> arguments) {
        try {
            VirtualMachine vm = connector.accept(arguments);
            return new Session(vm, this, diagnostics);
        } catch (IOException ioe) {
            diagnostics.putString(
                  "\n Unable to accept connection to target VM: " +
                                  ioe.getMessage());
        } catch (IllegalConnectorArgumentsException icae) {
            diagnostics.putString("\n Invalid connector arguments: " +
                                  icae.getMessage());
        }
        return null;
    }

    /*
     * Connect via user specified arguments
     * @return true on success
     */
    public boolean explictStart(Connector connector, Map<String, Connector.Argument> arguments)
                                           throws VMLaunchFailureException {
        Session newSession = null;

        endSession();

        if (connector instanceof LaunchingConnector) {
            // we were launched, use ChildSession
            newSession = new ChildSession(this, (LaunchingConnector)connector,
                                          arguments,
                                          appInput, appOutput, appError,
                                          diagnostics);
        } else if (connector instanceof AttachingConnector) {
            newSession = internalAttach((AttachingConnector)connector,
                                        arguments);
        } else if (connector instanceof ListeningConnector) {
            newSession = internalListen((ListeningConnector)connector,
                                        arguments);
        } else {
            diagnostics.putString("\n Unknown connector: " + connector);
        }
        if (newSession != null) {
            startSession(newSession);
        }
        return newSession != null;
    }

    /*
     * Detach from VM.  If VM was started by debugger, terminate it.
     */
    public void detach() throws NoSessionException {
        ensureActiveSession();
        endSession();
    }

    private void startSession(Session s) throws VMLaunchFailureException {
        if (!s.attach()) {
            throw new VMLaunchFailureException();
        }
        session = s;
        EventRequestManager em = vm().eventRequestManager();
        ClassPrepareRequest classPrepareRequest = em.createClassPrepareRequest();
        //### We must allow the deferred breakpoints to be resolved before
        //### we continue executing the class.  We could optimize if there
        //### were no deferred breakpoints outstanding for a particular class.
        //### Can we do this with JDI?
        classPrepareRequest.setSuspendPolicy(EventRequest.SUSPEND_ALL);
        classPrepareRequest.enable();
        ClassUnloadRequest classUnloadRequest = em.createClassUnloadRequest();
        classUnloadRequest.setSuspendPolicy(EventRequest.SUSPEND_NONE);
        classUnloadRequest.enable();
        ThreadStartRequest threadStartRequest = em.createThreadStartRequest();
        threadStartRequest.setSuspendPolicy(EventRequest.SUSPEND_NONE);
        threadStartRequest.enable();
        ThreadDeathRequest threadDeathRequest = em.createThreadDeathRequest();
        threadDeathRequest.setSuspendPolicy(EventRequest.SUSPEND_NONE);
        threadDeathRequest.enable();
        ExceptionRequest exceptionRequest =
                                em.createExceptionRequest(null, false, true);
        exceptionRequest.setSuspendPolicy(EventRequest.SUSPEND_ALL);
        exceptionRequest.enable();
        validateThreadInfo();
        session.interrupted = true;
        notifySessionStart();
    }

    void endSession() {
        if (session != null) {
            session.detach();
            session = null;
            invalidateThreadInfo();
            notifySessionDeath();
        }
    }

    /*
     * Suspend all VM activity.
     */

    public void interrupt() throws NoSessionException {
        ensureActiveSession();
        vm().suspend();
        //### Is it guaranteed that the interrupt has happened?
        validateThreadInfo();
        session.interrupted = true;
        notifyInterrupted();
    }

    /*
     * Resume interrupted VM.
     */

    public void go() throws NoSessionException, VMNotInterruptedException {
        ensureActiveSession();
        invalidateThreadInfo();
        session.interrupted = false;
        notifyContinued();
        vm().resume();
    }

    /*
     * Stepping.
     */
    void clearPreviousStep(ThreadReference thread) {
        /*
         * A previous step may not have completed on this thread;
         * if so, it gets removed here.
         */
         EventRequestManager mgr = vm().eventRequestManager();
         for (StepRequest request : mgr.stepRequests()) {
             if (request.thread().equals(thread)) {
                 mgr.deleteEventRequest(request);
                 break;
             }
         }
    }

    private void generalStep(ThreadReference thread, int size, int depth)
                        throws NoSessionException {
        ensureActiveSession();
        invalidateThreadInfo();
        session.interrupted = false;
        notifyContinued();

        clearPreviousStep(thread);
        EventRequestManager reqMgr = vm().eventRequestManager();
        StepRequest request = reqMgr.createStepRequest(thread,
                                                       size, depth);
        // We want just the next step event and no others
        request.addCountFilter(1);
        request.enable();
        vm().resume();
    }

    public void stepIntoInstruction(ThreadReference thread)
                        throws NoSessionException {
        generalStep(thread, StepRequest.STEP_MIN, StepRequest.STEP_INTO);
    }

    public void stepOverInstruction(ThreadReference thread)
                        throws NoSessionException {
        generalStep(thread, StepRequest.STEP_MIN, StepRequest.STEP_OVER);
    }

    public void stepIntoLine(ThreadReference thread)
                        throws NoSessionException,
                        AbsentInformationException {
        generalStep(thread, StepRequest.STEP_LINE, StepRequest.STEP_INTO);
    }

    public void stepOverLine(ThreadReference thread)
                        throws NoSessionException,
                        AbsentInformationException {
        generalStep(thread, StepRequest.STEP_LINE, StepRequest.STEP_OVER);
    }

    public void stepOut(ThreadReference thread)
                        throws NoSessionException {
        generalStep(thread, StepRequest.STEP_MIN, StepRequest.STEP_OUT);
    }

    /*
     * Thread control.
     */

    public void suspendThread(ThreadReference thread) throws NoSessionException {
        ensureActiveSession();
        thread.suspend();
    }

    public void resumeThread(ThreadReference thread) throws NoSessionException {
        ensureActiveSession();
        thread.resume();
    }

    public void stopThread(ThreadReference thread) throws NoSessionException {
        ensureActiveSession();
        //### Need an exception now.  Which one to use?
        //thread.stop();
    }

    /*
     * ThreadInfo objects -- Allow query of thread status and stack.
     */

    private List<ThreadInfo> threadInfoList = new LinkedList<ThreadInfo>();
    //### Should be weak! (in the value, not the key)
    private HashMap<ThreadReference, ThreadInfo> threadInfoMap = new HashMap<ThreadReference, ThreadInfo>();

    public ThreadInfo threadInfo(ThreadReference thread) {
        if (session == null || thread == null) {
            return null;
        }
        ThreadInfo info = threadInfoMap.get(thread);
        if (info == null) {
            //### Should not hardcode initial frame count and prefetch here!
            //info = new ThreadInfo(thread, 10, 10);
            info = new ThreadInfo(thread);
            if (session.interrupted) {
                info.validate();
            }
            threadInfoList.add(info);
            threadInfoMap.put(thread, info);
        }
        return info;
    }

     void validateThreadInfo() {
        session.interrupted = true;
        for (ThreadInfo threadInfo : threadInfoList) {
            threadInfo.validate();
            }
    }

    private void invalidateThreadInfo() {
        if (session != null) {
            session.interrupted = false;
            for (ThreadInfo threadInfo : threadInfoList) {
                threadInfo.invalidate();
            }
        }
    }

    void removeThreadInfo(ThreadReference thread) {
        ThreadInfo info = threadInfoMap.get(thread);
        if (info != null) {
            info.invalidate();
            threadInfoMap.remove(thread);
            threadInfoList.remove(info);
        }
    }

    /*
     * Listen for Session control events.
     */

    private void notifyInterrupted() {
      ArrayList<SessionListener> l = new ArrayList<SessionListener>(sessionListeners);
        EventObject evt = new EventObject(this);
        for (int i = 0; i < l.size(); i++) {
            l.get(i).sessionInterrupt(evt);
        }
    }

    private void notifyContinued() {
        ArrayList<SessionListener> l = new ArrayList<SessionListener>(sessionListeners);
        EventObject evt = new EventObject(this);
        for (int i = 0; i < l.size(); i++) {
            l.get(i).sessionContinue(evt);
        }
    }

    private void notifySessionStart() {
        ArrayList<SessionListener> l = new ArrayList<SessionListener>(sessionListeners);
        EventObject evt = new EventObject(this);
        for (int i = 0; i < l.size(); i++) {
            l.get(i).sessionStart(evt);
        }
    }

    private void notifySessionDeath() {
/*** noop for now
        ArrayList<SessionListener> l = new ArrayList<SessionListener>(sessionListeners);
        EventObject evt = new EventObject(this);
        for (int i = 0; i < l.size(); i++) {
            ((SessionListener)l.get(i)).sessionDeath(evt);
        }
****/
    }

    /*
     * Listen for input and output requests from the application
     * being debugged.  These are generated only when the debuggee
     * is spawned as a child of the debugger.
     */

    private Object inputLock = new Object();
    private LinkedList<String> inputBuffer = new LinkedList<String>();

    private void resetInputBuffer() {
        synchronized (inputLock) {
            inputBuffer = new LinkedList<String>();
        }
    }

    public void sendLineToApplication(String line) {
        synchronized (inputLock) {
            inputBuffer.addFirst(line);
            inputLock.notifyAll();
        }
    }

    private InputListener appInput = new InputListener() {
        @Override
        public String getLine() {
            // Don't allow reader to be interrupted -- catch and retry.
            String line = null;
            while (line == null) {
                synchronized (inputLock) {
                    try {
                        while (inputBuffer.size() < 1) {
                            inputLock.wait();
                        }
                        line = inputBuffer.removeLast();
                    } catch (InterruptedException e) {}
                }
            }
            // We must not be holding inputLock here, as the listener
            // that we call to echo a line might call us re-entrantly
            // to provide another line of input.
            // Run in Swing event dispatcher thread.
            final String input = line;
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    echoInputLine(input);
                }
            });
            return line;
        }
    };

    private static String newline = System.getProperty("line.separator");

    private void echoInputLine(String line) {
        ArrayList<OutputListener> l = new ArrayList<OutputListener>(appEchoListeners);
        for (int i = 0; i < l.size(); i++) {
            OutputListener ol = l.get(i);
            ol.putString(line);
            ol.putString(newline);
        }
    }

    private OutputListener appOutput = new OutputListener() {
      @Override
        public void putString(String string) {
            ArrayList<OutputListener> l = new ArrayList<OutputListener>(appEchoListeners);
            for (int i = 0; i < l.size(); i++) {
                l.get(i).putString(string);
            }
        }
    };

    private OutputListener appError = new OutputListener() {
      @Override
        public void putString(String string) {
            ArrayList<OutputListener> l = new ArrayList<OutputListener>(appEchoListeners);
            for (int i = 0; i < l.size(); i++) {
                l.get(i).putString(string);
            }
        }
    };

   private OutputListener diagnostics = new OutputListener() {
      @Override
        public void putString(String string) {
            ArrayList<OutputListener> l = new ArrayList<OutputListener>(diagnosticsListeners);
            for (int i = 0; i < l.size(); i++) {
                l.get(i).putString(string);
            }
        }
   };

  /////////////    Spec Request Creation/Deletion/Query   ///////////

    private EventRequestSpecList specList = new EventRequestSpecList(this);

    public BreakpointSpec
    createSourceLineBreakpoint(String sourceName, int line) {
        return specList.createSourceLineBreakpoint(sourceName, line);
    }

    public BreakpointSpec
    createClassLineBreakpoint(String classPattern, int line) {
        return specList.createClassLineBreakpoint(classPattern, line);
    }

    public BreakpointSpec
    createMethodBreakpoint(String classPattern,
                           String methodId, List<String> methodArgs) {
        return specList.createMethodBreakpoint(classPattern,
                                                 methodId, methodArgs);
    }

    public ExceptionSpec
    createExceptionIntercept(String classPattern,
                             boolean notifyCaught,
                             boolean notifyUncaught) {
        return specList.createExceptionIntercept(classPattern,
                                                   notifyCaught,
                                                   notifyUncaught);
    }

    public AccessWatchpointSpec
    createAccessWatchpoint(String classPattern, String fieldId) {
        return specList.createAccessWatchpoint(classPattern, fieldId);
    }

    public ModificationWatchpointSpec
    createModificationWatchpoint(String classPattern, String fieldId) {
        return specList.createModificationWatchpoint(classPattern,
                                                       fieldId);
    }

    public void delete(EventRequestSpec spec) {
        specList.delete(spec);
    }

    void resolve(ReferenceType refType) {
        specList.resolve(refType);
    }

    public void install(EventRequestSpec spec) {
        specList.install(spec, vm());
    }

    public List<EventRequestSpec> eventRequestSpecs() {
        return specList.eventRequestSpecs();
    }
}