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 }