/******************************************************************************* * Copyright (c) 2005, 2013 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.debug.internal.ui.viewers.update; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; 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.debug.core.ILaunch; import org.eclipse.debug.core.ILaunchManager; import org.eclipse.debug.core.model.IDebugTarget; import org.eclipse.debug.core.model.IStackFrame; import org.eclipse.debug.core.model.IThread; import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDelta; import org.eclipse.debug.internal.ui.viewers.model.provisional.ModelDelta; import org.eclipse.debug.internal.ui.viewers.provisional.AbstractModelProxy; /** * @since 3.2 */ public class ThreadEventHandler extends DebugEventHandler { /** * Queue of suspended threads to choose from when needing * to select a thread when another is resumed. Threads * are added in the order they suspend. */ private Set<IThread> fThreadQueue = new LinkedHashSet<IThread>(); /** * Map of previous TOS per thread */ private Map<IThread, IStackFrame> fLastTopFrame = new HashMap<IThread, IStackFrame>(); /** * Constructs and event handler for a threads in the given viewer. * * @param viewer */ public ThreadEventHandler(AbstractModelProxy proxy) { super(proxy); } /* (non-Javadoc) * @see org.eclipse.debug.internal.ui.viewers.update.DebugEventHandler#dispose() */ @Override public synchronized void dispose() { fLastTopFrame.clear(); fThreadQueue.clear(); super.dispose(); } @Override protected void handleSuspend(DebugEvent event) { IThread thread = (IThread) event.getSource(); if (event.isEvaluation()) { ModelDelta delta = buildRootDelta(); ModelDelta node = addPathToThread(delta, thread); node = node.addNode(thread, IModelDelta.NO_CHANGE); try { IStackFrame frame = thread.getTopStackFrame(); if (frame != null) { int flag = IModelDelta.NO_CHANGE; if (event.getDetail() == DebugEvent.EVALUATION) { // explicit evaluations can change content flag = flag | IModelDelta.CONTENT; } else if (event.getDetail() == DebugEvent.EVALUATION_IMPLICIT) { // implicit evaluations can change state flag = flag | IModelDelta.STATE; } node.addNode(frame, flag); fireDelta(delta); } } catch (DebugException e) { } } else { queueSuspendedThread(event); int extras = IModelDelta.STATE; switch (event.getDetail()) { case DebugEvent.BREAKPOINT: // on breakpoint also position thread to be top element extras = IModelDelta.EXPAND | IModelDelta.REVEAL; break; case DebugEvent.CLIENT_REQUEST: extras = IModelDelta.EXPAND; break; default: break; } // wait until initialization is completed before sending suspend // event, see bug 491174 comment 1 waitForProxyInitialization(); fireDeltaUpdatingSelectedFrame(thread, IModelDelta.NO_CHANGE | extras, event); } } private void waitForProxyInitialization() { AbstractModelProxy modelProxy = getModelProxy(); if (modelProxy == null) { return; } Job[] proxyInitJobs = Job.getJobManager().find(modelProxy); while (proxyInitJobs.length > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { // cancel waiting, something bad is happened break; } proxyInitJobs = Job.getJobManager().find(modelProxy); } } private boolean isEqual(Object o1, Object o2) { if (o1 == o2) { return true; } if (o1 == null) { return false; } return o1.equals(o2); } @Override protected void handleResume(DebugEvent event) { IThread thread = removeSuspendedThread(event); fireDeltaAndClearTopFrame(thread, IModelDelta.STATE | IModelDelta.CONTENT | IModelDelta.SELECT); thread = getNextSuspendedThread(); if (thread != null) { fireDeltaUpdatingSelectedFrame(thread, IModelDelta.NO_CHANGE | IModelDelta.REVEAL, event); } } @Override protected void handleCreate(DebugEvent event) { fireDeltaAndClearTopFrame((IThread) event.getSource(), IModelDelta.ADDED | IModelDelta.STATE); } @Override protected void handleTerminate(DebugEvent event) { IThread thread = (IThread) event.getSource(); IDebugTarget target = thread.getDebugTarget(); // ignore thread termination if target is terminated/disconnected if (!(target.isTerminated() || target.isDisconnected())) { fireDeltaAndClearTopFrame(thread, IModelDelta.REMOVED); } } @Override protected void handleChange(DebugEvent event) { if (event.getDetail() == DebugEvent.STATE) { fireDeltaUpdatingThread((IThread) event.getSource(), IModelDelta.STATE); } else { fireDeltaUpdatingThread((IThread) event.getSource(), IModelDelta.CONTENT); } } @Override protected void handleLateSuspend(DebugEvent suspend, DebugEvent resume) { IThread thread = queueSuspendedThread(suspend); if (suspend.isEvaluation() && suspend.getDetail() == DebugEvent.EVALUATION_IMPLICIT) { // late implicit evaluation - update thread and frame ModelDelta delta = buildRootDelta(); ModelDelta node = addPathToThread(delta, thread); node = node.addNode(thread, IModelDelta.STATE); try { IStackFrame frame = thread.getTopStackFrame(); if (frame != null) { node.addNode(frame, IModelDelta.STATE); fireDelta(delta); } } catch (DebugException e) { } } else { fireDeltaUpdatingSelectedFrame(thread, IModelDelta.STATE | IModelDelta.EXPAND, suspend); } } @Override protected void handleSuspendTimeout(DebugEvent event) { IThread thread = removeSuspendedThread(event); // don't collapse thread when waiting for long step or evaluation to complete fireDeltaUpdatingThread(thread, IModelDelta.STATE); } protected ModelDelta buildRootDelta() { return new ModelDelta(getLaunchManager(), IModelDelta.NO_CHANGE); } /** * Returns the launch manager. * * @return the launch manager */ protected ILaunchManager getLaunchManager() { return DebugPlugin.getDefault().getLaunchManager(); } /** * Adds nodes into the delta up to but not including the given thread. * * @param delta root delta for the view (includes viewer input) * @param thread thread for which path is requested * @return */ protected ModelDelta addPathToThread(ModelDelta delta, IThread thread) { ILaunch launch = thread.getLaunch(); Object[] children = launch.getChildren(); delta = delta.addNode(launch, indexOf(getLaunchManager().getLaunches(), launch), IModelDelta.NO_CHANGE, children.length); IDebugTarget debugTarget = thread.getDebugTarget(); int numThreads = -1; try { numThreads = debugTarget.getThreads().length; } catch (DebugException e) { } return delta.addNode(debugTarget, indexOf(children, debugTarget), IModelDelta.NO_CHANGE, numThreads); } private void fireDeltaAndClearTopFrame(IThread thread, int flags) { ModelDelta delta = buildRootDelta(); ModelDelta node = addPathToThread(delta, thread); node.addNode(thread, indexOf(thread), flags); synchronized (this) { fLastTopFrame.remove(thread); } fireDelta(delta); } private void fireDeltaUpdatingSelectedFrame(IThread thread, int flags, DebugEvent event) { ModelDelta delta = buildRootDelta(); ModelDelta node = addPathToThread(delta, thread); IStackFrame prev = null; synchronized (this) { prev = fLastTopFrame.get(thread); } IStackFrame frame = null; try { Object frameToSelect = event.getData(); if (frameToSelect == null || !(frameToSelect instanceof IStackFrame)) { frame = thread.getTopStackFrame(); } else { frame = (IStackFrame)frameToSelect; } } catch (DebugException e) { } int threadIndex = indexOf(thread); int childCount = childCount(thread); if (isEqual(frame, prev)) { if (frame == null) { if (thread.isSuspended()) { // no frames, but suspended - update & select node = node.addNode(thread, threadIndex, flags | IModelDelta.STATE | IModelDelta.SELECT, childCount); } } else { node = node.addNode(thread, threadIndex, flags, childCount); } } else { if (event.getDetail() == DebugEvent.STEP_END) { if (prev == null) { // see bug 166602 - expand the thread if this is a step end with no previous top frame flags = flags | IModelDelta.EXPAND; } else if (frame == null) { // there was a previous frame and current is null on a step: transient state return; } } node = node.addNode(thread, threadIndex, flags | IModelDelta.CONTENT, childCount); } if (frame != null) { node.addNode(frame, indexOf(frame), IModelDelta.STATE | IModelDelta.SELECT, childCount(frame)); } synchronized (this) { if (!isDisposed()) { fLastTopFrame.put(thread, frame); } } fireDelta(delta); } /** * Returns the index of the given thread, relative to its parent in the view. * * @param thread thread * @return index of the thread, relative to its parent */ protected int indexOf(IThread thread) { try { return indexOf(thread.getDebugTarget().getThreads(), thread); } catch (DebugException e) { } return -1; } /** * Returns the index of the given frame, relative to its parent in the view. * * @param frame stack frame * @return index of the frame, relative to its thread */ protected int indexOf(IStackFrame frame) { try { return indexOf(frame.getThread().getStackFrames(), frame); } catch (DebugException e) { return -1; } } /** * Returns the number of children the given thread has in the view. * * @param thread thread * @return number of children */ protected int childCount(IThread thread) { try { return thread.getStackFrames().length; } catch (DebugException e) { } return -1; } /** * Returns the number of children the given frame has in the view. * * @param frame frame * @return child count */ protected int childCount(IStackFrame frame) { return 0; } private void fireDeltaUpdatingThread(IThread thread, int flags) { ModelDelta delta = buildRootDelta(); ModelDelta node = addPathToThread(delta, thread); node = node.addNode(thread, flags); fireDelta(delta); } @Override protected boolean handlesEvent(DebugEvent event) { return event.getSource() instanceof IThread; } protected synchronized IThread queueSuspendedThread(DebugEvent event) { IThread thread = (IThread) event.getSource(); if (!isDisposed()) { fThreadQueue.add(thread); } return thread; } protected synchronized IThread removeSuspendedThread(DebugEvent event) { IThread thread = (IThread)event.getSource(); fThreadQueue.remove(thread); return thread; } protected synchronized IThread queueSuspendedThread(IThread thread) { if (!isDisposed()) { fThreadQueue.add(thread); } return thread; } protected synchronized void removeQueuedThread(IThread thread) { fThreadQueue.remove(thread); } protected synchronized IThread getNextSuspendedThread() { if (!fThreadQueue.isEmpty()) { return fThreadQueue.iterator().next(); } return null; } }