/******************************************************************************* * 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); } } } }