package jde.debugger; import jde.debugger.spec.*; import java.util.*; import com.sun.jdi.*; import com.sun.jdi.event.*; import com.sun.jdi.request.*; import com.sun.jdi.connect.*; import jde.debugger.LispForm; /** * EventHandler.java *
* Each debugee VM has an event handler thread on the jdebug side * associated with it that receive all the events from the debugee * vm. In turn, the event handler thread passes the events on to the * jde, indicating if the vm/current thread was suspended. *
* Created: Tue Jul 6 14:08:44 1999 * * @author Amit Kumar * @since 0.1 */ public class EventHandler implements Runnable, Protocol { /** Are we connected to the VM? */ boolean connected = true; /** The process for which we're the event handler */ final DebuggeeProcess proc; /** The ID of the process */ final Integer procID; final JDE jde = JDE.getJDE(); /** * My own thread. Used when we want to suspend ourselves or some such * weird stuff :-) */ final Thread thread; /** Keeping track of if the thread is over yet or not */ boolean completed = false; /** * Used by the event handlers to indicate if the app should be * resumed after the event occured. */ boolean resumeApp; public EventHandler(DebuggeeProcess proc) { /* the q gets all the events from the vm */ this.proc = proc; this.procID = proc.getId(); this.thread = new Thread(this, "Event Handler for App #"+procID); this.thread.start(); } public void shutdown() { connected = false; thread.interrupt(); while (!completed) { try {wait();} catch (InterruptedException ex) {} } } /** * The thread reads an eventset at a time from the application queue, * and processes it. Essentially, one by one, each event of the eventset * is sent to the corresponding "handler", and if at least one of them * returns a non-null value, we pass on the eventset (and the values * returned by the handlers) to jde. Otherwise, we just resume the vm * (regardless of the suspend policy) and wait for the next eventset. *
* This is the syntax of the event set: *
* (JDE_BUG_EVENTSET suspend-state thread [event-string]+) ** *
* Firstly, when multiple events are sent (in an event-set), the * suspend policy of the entire event set is the one that would * suspend the most threads. So, if a breakpoint-hit is sent with * the method-entry event, and the breakpoint-hit wants to suspend * the vm, while method-entry wants to resume, overall, the event * set suspend policy will be to suspend *
* Further, some of the events might occur that haven't been * explicitly requested, and hence we haven't set their suspend * policy anyway. In these cases, and even otherwise, another * decision is made at the time of handling of events regarding the * suspension/resumption of the vm. *
* So, when an event set is received, each event is handled (via
* it's handler method). Each event ANDs to a variable resumeApp
* what it thinks should be done to the app: for example, the
* vmDeathEvent handler wants to resume, so it does
* resumeApp &= true;. In the same way, when a
* breakpoint is hit (and matches the constraints), it does a
* resumeApp &= false:.
*
* After all events have been handled, we check the value of * resumeApp. If it's true, we resume the vm. If not, we check * the suspend-policy of the event set, and take appropriate action. *
* This two-tier suspend-policy handling might be simplified to * some extent once all commands support setting of a suspend * policy, with that and that alone being used to decide on what * to do with the vm. *
* A problem with that approach is that since we do a second pass * on the events when we get the eventset to determine if the * events should be sent to the user (eg. a breakpoint-hit is still * not interesting if it doesn't occur on the "right" thread), the * latter approach might not work well at all. *
null if there is no thread associated, otherwise
* the ThreadReference of the Thread
*/
private ThreadReference getCurrentThread(EventSet eventSet) {
ThreadReference thread;
if (eventSet.size() > 0) {
/*
* If any event in the set has a thread associated with it,
* they all will, so just grab the first one.
*/
Event event = (Event)eventSet.iterator().next(); // Is there a better way?
thread = getEventThread(event);
} else {
thread = null;
}
return thread;
}
/**
* Black magic to divine the ThreadReference of the
* event. Question: can we use "Black magic" and "divine" in the same
* sentence?
*
* @param event An event from the event set: any event will do
* @return The ThreadReference of the thread
*/
private ThreadReference getEventThread(Event event) {
if (event instanceof ClassPrepareEvent) {
return ((ClassPrepareEvent)event).thread();
} else if (event instanceof LocatableEvent) {
return ((LocatableEvent)event).thread();
} else if (event instanceof ThreadStartEvent) {
return ((ThreadStartEvent)event).thread();
} else if (event instanceof ThreadDeathEvent) {
return ((ThreadDeathEvent)event).thread();
} else if (event instanceof VMStartEvent) {
return ((VMStartEvent)event).thread();
} else {
return null;
}
}
/**
* For events with corresponding specs (eg. watchpoints), checks to see
* if the event thread matches the thread constraint in the spec.
* * * Syntax: *
* nil # If no such constraint * # was found in the spec * "Error mesg" # Constraint found, but error * # during evaluation * (list "on_thread_id" threadID) # Constraint found, satisfied * (list "on_thread_name" "name") ** * Comments: *
* * Syntax: *
* nil # If no such constraint * # was found in the spec * "Error mesg" # Constraint found, but error * # during evaluation * (list "expression") # Constraint found, satisfied ** * Comments: *
* * Syntax: *
* nil # if no such constraint was in spec * "Error mesg" # Constraint found, error in eval. * (list object-id) # Constraint found, satisfied. ** * Comments: *
* * Syntax: *
* (list {@link Protocol#EVENT_BREAKPOINT_HIT breakpoint-hit-function} specID {@link Rep#getLocationRep(Location) location} {@link #threadMatch thread-string} {@link #expressionSuccess expression-string})
*
*
* Comments:
* * * Syntax: *
* (list {@link Protocol#EVENT_STEP_COMPLETED step-function} {@link Rep#getLocationRep(Location) location-string})
*
*/
private LispForm stepEvent(StepEvent event) {
resumeApp &= false;
return new LispForm("(list '"
+EVENT_STEP_COMPLETED
+" "+Rep.getLocationRep(event.location())
+")");
}
/**
* A Watchpoint occured. We check (based on information in the event
* request; put in when the spec was resolved; that's sent along with
* the event) if the user wants to know about this event. If so, we
* return a lispform, else return null.
* * * Syntax: *
* (list {@link Protocol#EVENT_WATCHPOINT_HIT watchpoint-hit-function} specID {@link Rep#getObjectRep(ObjectReference,ObjectStore) object-on-which-hit} {@link Rep#getFieldValueRep(Field, Value, ObjectStore) field-and-value} {@link Rep#getLocationRep(Location) location}
* {@link #objectIDMatches object-id-string} {@link #threadMatch thread-string} {@link #expressionSuccess expression-string})
*
*
* Comments:
* * * Syntax: *
* (list {@link Protocol#EVENT_EXCEPTION exception-function} specID {@link Rep#getObjectRep(ObjectReference,ObjectStore) exception} {@link #threadMatch thread-string})
*
*
* Comments:
* * * Syntax: *
* (list {@link Protocol#EVENT_METHOD_ENTRY method-entry-function} {@link Rep#getMethodRep method})
*
*/
private LispForm methodEntryEvent(MethodEntryEvent event) {
resumeApp &= false;
return new LispForm("(list '"
+ EVENT_METHOD_ENTRY
+ BR +Rep.getMethodRep(event.method())
+")");
}
/**
* A method was exit.
* * * Syntax: *
* (list {@link Protocol#EVENT_METHOD_EXIT method-exit-function} {@link Rep#getMethodRep method})
*
*/
private LispForm methodExitEvent(MethodExitEvent event) {
resumeApp &= false;
return new LispForm("(list '"
+ EVENT_METHOD_EXIT
+ BR +Rep.getMethodRep(event.method())
+")");
}
/**
* A thread started
* * * Syntax: *
* (list {@link Protocol#EVENT_THREAD_START thread-start-function} {@link Rep#getThreadRep thread})
*
*/
private LispForm threadStartEvent(ThreadStartEvent event) {
resumeApp &= false;
return new LispForm("(list '"
+ EVENT_THREAD_START
+ BR +Rep.getThreadRep(event.thread(),
proc.getStore())
+")");
}
/**
* A thread died.
* * * Syntax: *
* (list {@link Protocol#EVENT_THREAD_DEATH thread-death-function} {@link Rep#getThreadRep thread})
*
*/
private LispForm threadDeathEvent(ThreadDeathEvent event) {
resumeApp &= false;
return new LispForm("(list '"
+ EVENT_THREAD_DEATH
+ BR +Rep.getThreadRep(event.thread(),
proc.getStore())
+")");
}
/**
* A class was prepared. The user might not have even requested for
* this event: we set it up by default in {@link Application#Application}
* because we need it for resolution of specs.
* * If a user also requests for this event, a particular property is * likely not set, and that is how we know that we're supposed to * handle the event for the user. *
* * Syntax: *
* (list {@link Protocol#EVENT_CLASS_PREPARE class-prepare-function} reference-type)
*
*/
private LispForm classPrepareEvent(ClassPrepareEvent event) {
proc.resolve(event.referenceType());
// now find out if this event was also requested by the user.
// it will be, if the "default" property does NOT exists in the
// corresponding request.
EventRequest request = event.request();
if (request.getProperty("default") == null) {
resumeApp &= false;
return new LispForm("(list '"+EVENT_CLASS_PREPARE
+" \""+event.referenceType().name()+"\")");
} else {
resumeApp &= true;
return null;
}
}
/**
* A class was unloaded.
* * * Syntax: *
* (list {@link Protocol#EVENT_CLASS_UNLOAD class-unload-function} reference-type)
*
*/
private LispForm classUnloadEvent(ClassUnloadEvent event) {
resumeApp &= false;
return new LispForm("(list '"+EVENT_CLASS_UNLOAD
+" \""+event.className()+"\")");
}
private LispForm vmStartEvent(Event event) {
resumeApp &= false;
return new LispForm("(list '"+EVENT_VM_START+")");
}
private LispForm vmDeathEvent(Event event) {
resumeApp &= true;
return new LispForm("(list '"+EVENT_VM_DEATH+")");
}
private LispForm vmDisconnectEvent(Event event) {
connected = false;
resumeApp &= true;
proc.shutdown();
return new LispForm("(list '"+EVENT_VM_DISCONNECT+")");
}
} // EventHandler