001: /*BEGIN_COPYRIGHT_BLOCK
002: *
003: * Copyright (c) 2001-2007, JavaPLT group at Rice University (javaplt@rice.edu)
004: * All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions are met:
008: * * Redistributions of source code must retain the above copyright
009: * notice, this list of conditions and the following disclaimer.
010: * * Redistributions in binary form must reproduce the above copyright
011: * notice, this list of conditions and the following disclaimer in the
012: * documentation and/or other materials provided with the distribution.
013: * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
014: * names of its contributors may be used to endorse or promote products
015: * derived from this software without specific prior written permission.
016: *
017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
018: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
019: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
020: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
021: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
022: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
023: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
024: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
025: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
026: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
027: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028: *
029: * This software is Open Source Initiative approved Open Source Software.
030: * Open Source Initative Approved is a trademark of the Open Source Initiative.
031: *
032: * This file is part of DrJava. Download the current version of this project
033: * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
034: *
035: * END_COPYRIGHT_BLOCK*/
036:
037: package edu.rice.cs.drjava.model.debug.jpda;
038:
039: import edu.rice.cs.util.Log;
040:
041: import com.sun.jdi.*;
042: import com.sun.jdi.event.*;
043: import com.sun.jdi.request.*;
044: import java.util.*;
045: import java.io.*;
046: import edu.rice.cs.drjava.model.debug.DebugException;
047:
048: /** A thread that listens and responds to events from JPDA when the debugger has attached to another JVM.
049: * @version $Id: EventHandlerThread.java 4255 2007-08-28 19:17:37Z mgricken $
050: */
051: public class EventHandlerThread extends Thread {
052:
053: /** Debugger to which this class reports events. */
054: private final JPDADebugger _debugger;
055:
056: /** JPDA reference to the VirtualMachine generating the events. */
057: private final VirtualMachine _vm;
058:
059: /** Whether this event handler is currently connected to the JPDA VirtualMachine. */
060: private volatile boolean _connected;
061:
062: /** A log for recording messages in a file. */
063: private static final Log _log = new Log("EventTest", false);
064:
065: /** Creates a new EventHandlerThread to listen to events from the given debugger and virtual machine. Calling
066: * this Thread's start() method causes it to begin listenting.
067: * @param debugger Debugger to which to report events
068: * @param vm JPDA reference to the VirtualMachine generating the events
069: */
070: EventHandlerThread(JPDADebugger debugger, VirtualMachine vm) {
071: super ("DrJava Debug Event Handler");
072: _debugger = debugger;
073: _vm = vm;
074: _connected = true;
075: }
076:
077: /** Logs any unexpected behavior that occurs (but which should not cause DrJava to abort).
078: * @param message message to print to the log
079: */
080: private void _log(String message) {
081: _log.log(message);
082: }
083:
084: /** Logs any unexpected behavior that occurs (but which should not cause DrJava to abort).
085: * @param message message to print to the log
086: * @param t Exception or Error being logged
087: */
088: private void _log(String message, Throwable t) {
089: _log.log(message, t);
090: }
091:
092: /** Continually consumes events from the VM's event queue until it is disconnected.*/
093: public void run() {
094: _debugger.notifyDebuggerStarted();
095:
096: EventQueue queue = _vm.eventQueue();
097: while (_connected) {
098: try {
099: try {
100: // Remove and consume a set of events from the queue (blocks for an event)
101: EventSet eventSet = queue.remove();
102: EventIterator it = eventSet.eventIterator();
103:
104: while (it.hasNext())
105: handleEvent(it.nextEvent());
106: } catch (InterruptedException ie) {
107: // Don't need to do anything. If the VM was disconnected,
108: // the loop will terminate.
109: _log("InterruptedException in main loop: " + ie);
110: } catch (VMDisconnectedException de) {
111: // We expect this to happen if the other JVM is reset
112: handleDisconnectedException();
113: break;
114: }
115: } catch (Exception e) {
116: // Log and report to the debugger
117: _log("Exception in main event handler loop.", e);
118: _debugger.eventHandlerError(e);
119: _debugger
120: .printMessage("An exception occurred in the event handler:\n"
121: + e);
122: _debugger
123: .printMessage("The debugger may have become unstable as a result.");
124: ByteArrayOutputStream baos = new ByteArrayOutputStream();
125: e.printStackTrace(new PrintWriter(baos, true));
126: _debugger.printMessage("Stack trace: "
127: + baos.toString());
128: }
129: }
130:
131: _debugger.notifyDebuggerShutdown();
132: }
133:
134: /** Processes a given event from JPDA. A visitor approach would be much better for this, but Sun's Event class
135: * doesn't have an appropriate visit() method.
136: */
137: private void handleEvent(Event e) throws DebugException {
138: // Utilities.showDebug("EventHandler.handleEvent(" + e + ") called");
139: _log("handling event: " + e);
140:
141: if (e instanceof BreakpointEvent)
142: _handleBreakpointEvent((BreakpointEvent) e);
143: else if (e instanceof StepEvent)
144: _handleStepEvent((StepEvent) e);
145: //else if (e instanceof ModificationWatchpointEvent) {
146: // _handleModificationWatchpointEvent((ModificationWatchpointEvent) e);
147: //}
148: else if (e instanceof ClassPrepareEvent)
149: _handleClassPrepareEvent((ClassPrepareEvent) e);
150: else if (e instanceof ThreadStartEvent)
151: _handleThreadStartEvent((ThreadStartEvent) e);
152: else if (e instanceof ThreadDeathEvent)
153: _handleThreadDeathEvent((ThreadDeathEvent) e);
154: else if (e instanceof VMDeathEvent)
155: _handleVMDeathEvent((VMDeathEvent) e);
156: else if (e instanceof VMDisconnectEvent)
157: _handleVMDisconnectEvent((VMDisconnectEvent) e);
158: else
159: throw new DebugException("Unexpected event type: " + e);
160: }
161:
162: /** Returns whether the given thread is both suspended and has stack frames. */
163: private boolean _isSuspendedWithFrames(ThreadReference thread)
164: throws DebugException {
165:
166: try {
167: return thread.isSuspended() && thread.frameCount() > 0;
168: } catch (IncompatibleThreadStateException itse) {
169: throw new DebugException(
170: "Could not count frames on a suspended thread: "
171: + itse);
172: }
173: }
174:
175: /** Responds to a breakpoint event.
176: * @param e breakpoint event from JPDA
177: */
178: private void _handleBreakpointEvent(BreakpointEvent e)
179: throws DebugException {
180: synchronized (_debugger) {
181: if (_isSuspendedWithFrames(e.thread())
182: && _debugger.setCurrentThread(e.thread())) {
183: // Utilities.showDebug("EventHandlerThread._handleBreakpointEvent(" + e + ") called");
184: _debugger.currThreadSuspended();
185: // _debugger.scrollToSource(e);
186: _debugger.reachedBreakpoint((BreakpointRequest) e
187: .request());
188: }
189: }
190: }
191:
192: /** Responds to a step event.
193: * @param e step event from JPDA
194: */
195: private void _handleStepEvent(StepEvent e) throws DebugException {
196: // preload document without holding _debugger lock to avoid deadlock
197: // in bug [ 1696060 ] Debugger Infinite Loop
198: // if the document is not already open, the event thread may load the document and then call a
199: // synchronized method in the debugger, so we must do this before we hold the _debugger lock
200: _debugger.preloadDocument(e.location());
201: // now acquire _debugger lock, the event thread won't need tge _debugger lock anymore
202: synchronized (_debugger) {
203: if (_isSuspendedWithFrames(e.thread())
204: && _debugger.setCurrentThread(e.thread())) {
205: _debugger.printMessage("Stepped to "
206: + e.location().declaringType().name() + "."
207: + e.location().method().name()
208: + "(...) [line " + e.location().lineNumber()
209: + "]");
210: _debugger.currThreadSuspended();
211: // _debugger.scrollToSource(e);
212: }
213: // Delete the step request so it doesn't happen again
214: _debugger.getEventRequestManager().deleteEventRequest(
215: e.request());
216: }
217: }
218:
219: // /** Responds to an event for a modified watchpoint.
220: // * This event is not currently expected in DrJava.
221: // * @param e modification watchpoint event from JPDA
222: // */
223: // private void _handleModificationWatchpointEvent(ModificationWatchpointEvent e) {
224: // _debugger.printMessage("ModificationWatchpointEvent occured ");
225: // _debugger.printMessage("Field: " + e.field() + " Value: " +
226: // e.valueToBe() +"]");
227: // }
228:
229: /** Responds when a class of interest has been prepared. Allows the debugger to set a pending breakpoint before any
230: * code in the class is executed.
231: * @param e class prepare event from JPDA
232: * @throws DebugException if actions performed on the prepared class fail
233: */
234: private void _handleClassPrepareEvent(ClassPrepareEvent e)
235: throws DebugException {
236: synchronized (_debugger) {
237: _debugger.getPendingRequestManager().classPrepared(e);
238: // resume this thread which was suspended because its
239: // suspend policy was SUSPEND_EVENT_THREAD
240: e.thread().resume();
241: }
242: }
243:
244: /** Responds to a thread start event.
245: * @param e thread start event from JPDA
246: */
247: private void _handleThreadStartEvent(ThreadStartEvent e) {
248: synchronized (_debugger) {
249: _debugger.threadStarted();
250: }
251: }
252:
253: /** Reponds to a thread death event.
254: * @param e thread death event from JPDA
255: */
256: private void _handleThreadDeathEvent(ThreadDeathEvent e)
257: throws DebugException {
258: // no need to check if there are suspended threads on the stack
259: // because all that logic should be in the debugger
260: synchronized (_debugger) {
261: ThreadReference running = _debugger
262: .getCurrentRunningThread();
263: if (e.thread().equals(running)) {
264: // Delete any step requests pending on this thread
265: EventRequestManager erm = _vm.eventRequestManager();
266: List steps = erm.stepRequests();
267: for (int i = 0; i < steps.size(); i++) {
268: StepRequest step = (StepRequest) steps.get(i);
269: if (step.thread().equals(e.thread())) {
270: erm.deleteEventRequest(step);
271:
272: // There can only be one step request per thread,
273: // so we can stop looking
274: break;
275: }
276: }
277: _debugger.currThreadDied();
278: } else
279: _debugger.nonCurrThreadDied();
280: }
281:
282: // Thread is suspended on death, so resume it now.
283: e.thread().resume();
284: }
285:
286: /** Responds if the virtual machine being debugged dies.
287: * @param e virtual machine death event from JPDA
288: */
289: private void _handleVMDeathEvent(VMDeathEvent e)
290: throws DebugException {
291: _cleanUp(e);
292: }
293:
294: /**
295: * Responds if the virtual machine being debugged disconnects.
296: * @param e virtual machine disconnect event from JPDA
297: */
298: private void _handleVMDisconnectEvent(VMDisconnectEvent e)
299: throws DebugException {
300: _cleanUp(e);
301: }
302:
303: /** Cleans up the state after the virtual machine being debugged dies or disconnects.
304: * @param e JPDA event indicating the debugging session has ended
305: */
306: private void _cleanUp(Event e) throws DebugException {
307: synchronized (_debugger) {
308: _connected = false;
309: if (_debugger.isReady()) {
310: // caused crash if "Run Document's Main Method" was invoked while debugging
311: // if (_debugger.hasSuspendedThreads()) _debugger.currThreadDied();
312: _debugger.shutdown();
313: }
314: }
315: }
316:
317: /** Responds when a VMDisconnectedException occurs while dealing with another event. We need to flush the event
318: * queue, dealing only with exit events (VMDeath, VMDisconnect) so that we terminate correctly. */
319: private void handleDisconnectedException() throws DebugException {
320: EventQueue queue = _vm.eventQueue();
321: while (_connected) {
322: try {
323: EventSet eventSet = queue.remove();
324: EventIterator iter = eventSet.eventIterator();
325: while (iter.hasNext()) {
326: Event event = iter.nextEvent();
327: if (event instanceof VMDeathEvent)
328: _handleVMDeathEvent((VMDeathEvent) event);
329: else if (event instanceof VMDisconnectEvent)
330: _handleVMDisconnectEvent((VMDisconnectEvent) event);
331: // else ignore the event
332: }
333: eventSet.resume(); // Resume the VM
334: } catch (InterruptedException ie) {
335: // ignore
336: _log(
337: "InterruptedException after a disconnected exception.",
338: ie);
339: } catch (VMDisconnectedException de) {
340: // try to continue flushing the event queue anyway
341: _log("A second VMDisconnectedException.", de);
342: }
343: }
344: }
345: }
|