blob: 216b9c4ba20b333a11669539657615696b9fe879 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2015 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.debug.core;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.debug.core.DebugEvent;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.jdt.debug.core.IJavaLineBreakpoint;
import org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget;
import com.sun.jdi.VMDisconnectedException;
import com.sun.jdi.VirtualMachine;
import com.sun.jdi.event.Event;
import com.sun.jdi.event.EventIterator;
import com.sun.jdi.event.EventQueue;
import com.sun.jdi.event.EventSet;
import com.sun.jdi.event.VMDeathEvent;
import com.sun.jdi.event.VMDisconnectEvent;
import com.sun.jdi.event.VMStartEvent;
import com.sun.jdi.request.EventRequest;
/**
* Dispatches events generated by an underlying VM. There is one event
* dispatcher per JDI debug target.
* <p>
* Event listeners register with a debug target to handle specific event
* requests. A debug target forwards event listeners and requests to its event
* dispatcher. As events are received from the underlying VM, those listeners
* that registered to handle the specific events are notified.
* </p>
* <p>
* Events are processed in event sets. It is possible that one event can trigger
* more than one event request to be processed. In such cases all event requests
* triggered by that one event are processed, and each event listener votes on
* whether the thread in which the event occurred should be resumed. A thread is
* only resumed in if all event handlers agree that the thread should be
* resumed.
* </p>
*/
public class EventDispatcher implements Runnable {
/**
* The debug target this event dispatcher belongs to.
*/
private JDIDebugTarget fTarget;
/**
* Whether this dispatcher is shutdown.
*/
private boolean fShutdown;
/**
* Table of event listeners. Table is a mapping of <code>EventRequest</code>
* to <code>IJDIEventListener</code>.
*/
private HashMap<EventRequest, IJDIEventListener> fEventHandlers;
/**
* Queue of debug model events to fire, created when processing events on
* the target VM. Keyed by event sets, processed independently.
*/
private Map<EventSet, List<DebugEvent>> fSetToQueue = new HashMap<>();
/**
* Constructs a new event dispatcher listening for events originating from
* the specified debug target's underlying VM.
*
* @param target
* the target this event dispatcher belongs to
*/
public EventDispatcher(JDIDebugTarget target) {
fEventHandlers = new HashMap<>(10);
fTarget = target;
fShutdown = false;
}
/**
* Dispatch the given event set.
*
* @param eventSet
* events to dispatch
*/
private void dispatch(EventSet eventSet) {
if (isShutdown()) {
return;
}
if (JDIDebugOptions.DEBUG_JDI_EVENTS) {
EventIterator eventIter = eventSet.eventIterator();
StringBuffer buf = new StringBuffer("JDI Event Set: {\n"); //$NON-NLS-1$
while (eventIter.hasNext()) {
buf.append(eventIter.next());
if (eventIter.hasNext()) {
buf.append(", "); //$NON-NLS-1$
}
}
buf.append("}\n"); //$NON-NLS-1$
JDIDebugOptions.trace(buf.toString());
}
EventIterator iter = eventSet.eventIterator();
IJDIEventListener[] listeners = new IJDIEventListener[eventSet.size()];
boolean vote = false;
boolean resume = true;
int index = -1;
List<Event> deferredEvents = null;
while (iter.hasNext()) {
index++;
if (isShutdown()) {
return;
}
Event event = iter.nextEvent();
if (event == null) {
continue;
}
// Dispatch events to registered listeners, if any
IJDIEventListener listener = fEventHandlers.get(event.request());
listeners[index] = listener;
if (listener != null) {
if (listener instanceof IJavaLineBreakpoint) {
// Event dispatch to conditional breakpoints is deferred
// until after
// other listeners vote.
try {
if (((IJavaLineBreakpoint) listener).isConditionEnabled()) {
if (deferredEvents == null) {
deferredEvents = new ArrayList<>(5);
}
deferredEvents.add(event);
continue;
}
} catch (CoreException exception) {
JDIDebugPlugin.log(exception);
}
}
vote = true;
resume = listener.handleEvent(event, fTarget, !resume, eventSet) && resume;
continue;
}
// Dispatch VM start/end events
if (event instanceof VMDeathEvent) {
fTarget.handleVMDeath((VMDeathEvent) event);
shutdown(); // stop listening for events
} else if (event instanceof VMDisconnectEvent) {
fTarget.handleVMDisconnect((VMDisconnectEvent) event);
shutdown(); // stop listening for events
} else if (event instanceof VMStartEvent) {
fTarget.handleVMStart((VMStartEvent) event);
} else {
// not handled
}
}
// process deferred conditional breakpoint events
if (deferredEvents != null) {
Iterator<Event> deferredIter = deferredEvents.iterator();
while (deferredIter.hasNext()) {
if (isShutdown()) {
return;
}
Event event = deferredIter.next();
if (event == null) {
continue;
}
// Dispatch events to registered listeners, if any
IJDIEventListener listener = fEventHandlers
.get(event.request());
if (listener != null) {
vote = true;
resume = listener.handleEvent(event, fTarget, !resume, eventSet) && resume;
continue;
}
}
}
// notify handlers of the end result
index = -1;
iter = eventSet.eventIterator();
while (iter.hasNext()) {
index++;
Event event = iter.nextEvent();
// notify registered listener, if any
IJDIEventListener listener = listeners[index];
if (listener != null) {
listener.eventSetComplete(event, fTarget, !resume, eventSet);
}
}
// fire queued DEBUG events
fireEvents(eventSet);
if (vote && resume) {
try {
eventSet.resume();
} catch (VMDisconnectedException e) {
} catch (RuntimeException e) {
try {
fTarget.targetRequestFailed(
JDIDebugMessages.EventDispatcher_0, e);
} catch (DebugException de) {
JDIDebugPlugin.log(de);
}
}
}
}
/**
* Continuously reads events that are coming from the event queue, until
* this event dispatcher is shutdown. A debug target starts a thread on this
* method on startup.
*
* @see #shutdown()
*/
@Override
public void run() {
VirtualMachine vm = fTarget.getVM();
if (vm != null) {
EventQueue q = vm.eventQueue();
EventSet eventSet = null;
while (!isShutdown()) {
try {
try {
// Get the next event set.
eventSet = q.remove(1000);
} catch (VMDisconnectedException e) {
break;
}
if (!isShutdown() && eventSet != null) {
final EventSet set = eventSet;
Job job = new Job("JDI Event Dispatch") { //$NON-NLS-1$
@Override
protected IStatus run(IProgressMonitor monitor) {
dispatch(set);
return Status.OK_STATUS;
}
@Override
public boolean belongsTo(Object family) {
if (family instanceof Class) {
Class<?> clazz = (Class<?>) family;
EventIterator iterator = set.eventIterator();
while (iterator.hasNext()) {
Event event = iterator.nextEvent();
if (clazz.isInstance(event)) {
return true;
}
}
}
return false;
}
@Override
public String toString() {
try {
return super.toString() + " for [" + fTarget.getName() + "]"; //$NON-NLS-1$ //$NON-NLS-2$
}
catch (DebugException e) {
return super.toString();
}
}
};
job.setSystem(true);
job.schedule();
}
} catch (InterruptedException e) {
break;
}
}
}
}
/**
* Shutdown this event dispatcher - i.e. causes this event dispatcher to
* stop reading and dispatching events from the event queue. The thread
* associated with this runnable will exit.
*/
public void shutdown() {
fShutdown = true;
}
/**
* Returns whether this event dispatcher has been shutdown.
*
* @return whether this event dispatcher has been shutdown
*/
private boolean isShutdown() {
return fShutdown;
}
/**
* Registers the given listener for with the given event request. When an
* event is received from the underlying VM, that is associated with the
* given event request, the listener will be notified.
*
* @param listener
* the listener to register
* @param request
* the event request associated with events the listener is
* interested in
*/
public void addJDIEventListener(IJDIEventListener listener,
EventRequest request) {
fEventHandlers.put(request, listener);
}
/**
* De-registers the given listener and event request. The listener will no
* longer be notified of events associated with the request. Listeners are
* responsible for deleting the associated event request if required.
*
* @param listener
* the listener to de-register
* @param request
* the event request to de-register
*/
public void removeJDIEventListener(IJDIEventListener listener, EventRequest request) {
fEventHandlers.remove(request);
}
/**
* Adds the given event to the queue of debug events to fire when done
* dispatching events from the given event set.
*
* @param event
* the event to queue
* @param set
* event set the event is associated with
*/
public void queue(DebugEvent event, EventSet set) {
synchronized (fSetToQueue) {
List<DebugEvent> list = fSetToQueue.get(set);
if (list == null) {
list = new ArrayList<>(5);
fSetToQueue.put(set, list);
}
list.add(event);
}
}
/**
* Fires debug events in the event queue associated with the given event
* set, and clears the queue.
* @param set the set to fire events for
*/
private void fireEvents(EventSet set) {
DebugPlugin plugin = DebugPlugin.getDefault();
if (plugin != null) { // check that not in the process of shutting down
List<DebugEvent> list = null;
synchronized (fSetToQueue) {
list = fSetToQueue.remove(set);
}
if (list != null) {
DebugEvent[] events = list.toArray(new DebugEvent[list.size()]);
plugin.fireDebugEventSet(events);
}
}
}
}