blob: 5f7a88eea2610cd1aa26f8aab88a64d3bf2075b8 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2003 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.debug.internal.ui.views.launch;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
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.ILaunchesListener2;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.debug.core.model.IStackFrame;
import org.eclipse.debug.core.model.IThread;
import org.eclipse.debug.internal.ui.DebugUIPlugin;
import org.eclipse.debug.internal.ui.views.AbstractDebugEventHandler;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.StructuredSelection;
/**
* Handles debug events, updating the launch view and viewer.
*/
public class LaunchViewEventHandler extends AbstractDebugEventHandler implements ILaunchesListener2 {
/**
* The timer used to time step and evaluation events. The timer allows
* the UI to not refresh during fast evaluations and steps.
*/
private ThreadTimer fThreadTimer= new ThreadTimer();
/**
* Cache of the last top stack frame
*/
private IStackFrame fLastStackFrame = null;
/**
* Constructs an event handler for the given launch view.
*
* @param view launch view
*/
public LaunchViewEventHandler(LaunchView view) {
super(view);
DebugPlugin plugin= DebugPlugin.getDefault();
plugin.getLaunchManager().addLaunchListener(this);
}
/**
* @see AbstractDebugEventHandler#doHandleDebugEvents(DebugEvent[])
*/
protected void doHandleDebugEvents(DebugEvent[] events) {
fThreadTimer.handleDebugEvents(events);
Object suspendee = null;
for (int i = 0; i < events.length; i++) {
DebugEvent event = events[i];
Object source= event.getSource();
if (!(source instanceof IStackFrame || source instanceof IThread || source instanceof IDebugTarget || source instanceof IProcess)) {
// the launch view is not interested in any other types of elements
return;
}
switch (event.getKind()) {
case DebugEvent.CREATE :
insert(source);
if (source instanceof IDebugTarget) {
ILaunch launch= ((IDebugTarget)source).getLaunch();
getLaunchView().autoExpand(launch, false, true);
}
break;
case DebugEvent.TERMINATE :
if (source instanceof IThread) {
clearSourceSelection(source);
fThreadTimer.getTimedOutThreads().remove(source);
remove(source);
} else {
if (source instanceof IDebugTarget) {
clearSourceSelection(source);
}
Object parent = ((ITreeContentProvider)getTreeViewer().getContentProvider()).getParent(source);
refresh(parent);
}
break;
case DebugEvent.RESUME :
doHandleResumeEvent(event, source);
break;
case DebugEvent.SUSPEND :
if (suspendee == null || !suspendee.equals(source)) {
doHandleSuspendEvent(source, event);
suspendee = source;
}
break;
case DebugEvent.CHANGE :
if (source instanceof IStackFrame) {
IStackFrame lastFrame= getLaunchView().getStackFrame();
if (source.equals(lastFrame)) {
getLaunchView().setStackFrame(null);
getLaunchView().showEditorForCurrentSelection();
}
}
if (event.getDetail() == DebugEvent.STATE) {
labelChanged(source);
} else {
//structural change
refresh(source);
}
break;
}
}
}
/**
* Handles the given resume event with the given source.
*/
protected void doHandleResumeEvent(DebugEvent event, Object source) {
if (!event.isEvaluation()) {
clearSourceSelection(source);
}
if (event.isEvaluation() || event.isStepStart()) {
// Do not update for step starts and evaluation
// starts immediately. Instead, start the timer.
IThread thread= getThread(source);
if (thread != null) {
fThreadTimer.startTimer(thread);
}
return;
}
refresh(source);
if (source instanceof IThread) {
// When a thread resumes, try to select another suspended thread
// in the same target.
try {
IThread[] threads= ((IThread) source).getDebugTarget().getThreads();
for (int i = 0; i < threads.length; i++) {
IStackFrame frame = threads[i].getTopStackFrame();
if (frame != null) {
selectAndReveal(frame);
return;
}
}
} catch (DebugException e) {
}
selectAndReveal(source);
return;
}
}
/**
* Updates the stack frame icons for a running thread.
* This is useful for the case where a thread is resumed
* temporarily but the view should keep the stack frame
* visible (for example, step start or evaluation start).
*/
protected void updateRunningThread(IThread thread) {
labelChanged(thread);
getLaunchViewer().updateStackFrameImages(thread);
clearSourceSelection(thread);
}
protected void doHandleSuspendEvent(Object element, DebugEvent event) {
IThread thread= getThread(element);
if (thread != null) {
fThreadTimer.stopTimer(thread);
}
boolean wasTimedOut= fThreadTimer.getTimedOutThreads().remove(thread);
if (event.isEvaluation() && ((event.getDetail() & DebugEvent.EVALUATION_IMPLICIT) != 0)) {
if (thread != null && !wasTimedOut) {
// No refresh required for implicit evaluations that complete on time
return;
}
}
if (element instanceof IThread) {
doHandleSuspendThreadEvent((IThread)element, event, wasTimedOut);
return;
}
refresh(element);
}
/**
* Updates the given thread for the given suspend event.
*/
protected void doHandleSuspendThreadEvent(IThread thread, DebugEvent event, boolean wasTimedOut) {
// if the thread has already resumed, do nothing
if (!thread.isSuspended() || !isAvailable()) {
return;
}
// do not update source selection for evaluation events
boolean evaluationEvent = event.isEvaluation();
// if the top frame is the same, only update labels and images, and re-select
// the frame to display source
try {
IStackFrame frame = thread.getTopStackFrame();
if (frame != null && frame.equals(fLastStackFrame)) {
if (wasTimedOut) {
getLaunchViewer().updateStackFrameImages(thread);
}
getLaunchViewer().update(new Object[] {thread, frame}, null);
if (!evaluationEvent) {
getLaunchViewer().setSelection(new StructuredSelection(frame));
} else if (wasTimedOut) {
getLaunchView().showEditorForCurrentSelection();
}
return;
}
} catch (DebugException e) {
}
// Auto-expand the thread. Only select the thread if this wasn't the end
// of an evaluation
getLaunchView().autoExpand(thread, true, !evaluationEvent);
try {
fLastStackFrame = thread.getTopStackFrame();
} catch (DebugException e) {
fLastStackFrame = null;
}
}
/**
* @see AbstractDebugEventHandler#updateForDebugEvents(DebugEvent[])
*/
protected void updateForDebugEvents(DebugEvent[] events) {
super.updateForDebugEvents(events);
if (isViewVisible()) {
return;
}
doHandleDebugEvents(events);
}
/**
* De-registers this event handler from the debug model.
*/
public void dispose() {
super.dispose();
fThreadTimer.stop();
DebugPlugin plugin= DebugPlugin.getDefault();
plugin.getLaunchManager().removeLaunchListener(this);
}
/**
* Clear the selection in the editor - must be called in UI thread
*/
private void clearSourceSelection(Object source) {
if (getViewer() != null) {
getLaunchView().clearSourceSelection(source);
}
}
/**
* Returns this event handler's launch viewer
*
* @return launch viewer
*/
protected LaunchViewer getLaunchViewer() {
return (LaunchViewer)getViewer();
}
/**
* Returns this event handler's launch view
*
* @return launch view
*/
protected LaunchView getLaunchView() {
return (LaunchView)getView();
}
private IThread getThread(Object element) {
IThread thread = null;
if (element instanceof IThread) {
thread = (IThread) element;
} else if (element instanceof IStackFrame) {
thread = ((IStackFrame)element).getThread();
}
return thread;
}
class ThreadTimer {
private Thread fThread;
/**
* The time allotted before a thread will be updated
*/
private long TIMEOUT= 500;
/**
* Time in milliseconds that the thread timer started
* running with no timers.
*/
private long timeEmpty= 0;
/**
* The maximum time in milliseconds that the thread
* will continue running with no timers.
*/
private long MAX_TIME_EMPTY= 3000;
private boolean fStopped= false;
private Object fLock= new Object();
/**
* Maps threads that are currently performing being timed
* to the allowed time by which they must finish. If this
* limit expires before the timer is stopped, the thread will
* be refreshed.
*/
HashMap fStopTimes= new HashMap();
/**
* Collection of threads whose timers have expired.
*/
HashSet fTimedOutThreads= new HashSet();
public Set getTimedOutThreads() {
return fTimedOutThreads;
}
/**
* Handle debug events dispatched from launch view event handler.
* If there are no running targets, stop this timer.
*/
public void handleDebugEvents(DebugEvent[] events) {
if (fStopped) {
return;
}
DebugEvent event;
for (int i= 0, numEvents= events.length; i < numEvents; i++) {
event= events[i];
if (event.getKind() == DebugEvent.TERMINATE && event.getSource() instanceof IDebugTarget) {
ILaunch[] launches= DebugPlugin.getDefault().getLaunchManager().getLaunches();
// If there are no more active DebugTargets, stop the thread.
for (int j= 0; j < launches.length; j++) {
IDebugTarget[] targets= launches[j].getDebugTargets();
for (int k = 0; k < targets.length; k++) {
IDebugTarget target = targets[k];
if (target != null && !target.isDisconnected() && !target.isTerminated()) {
return;
}
}
}
// To get here, there must be no running DebugTargets
stop();
return;
}
}
}
public void startTimer(IThread thread) {
synchronized (fLock) {
fStopTimes.put(thread, new Long(System.currentTimeMillis() + TIMEOUT));
if (fThread == null) {
startThread();
}
}
}
public void stop() {
synchronized (fLock) {
fStopped= true;
fThread= null;
fStopTimes.clear();
}
}
public void stopTimer(IThread thread) {
synchronized (fLock) {
fStopTimes.remove(thread);
}
}
private void startThread() {
fThread= new Thread(new Runnable() {
public void run() {
fStopped= false;
while (!fStopped) {
checkTimers();
}
}
}, "Thread timer"); //$NON-NLS-1$
fThread.start();
}
private void checkTimers() {
long timeToWait= TIMEOUT;
Map.Entry[] entries;
synchronized (fLock) {
if (fStopTimes.size() == 0) {
if (timeEmpty == 0) {
timeEmpty= System.currentTimeMillis();
} else if (System.currentTimeMillis() - timeEmpty > MAX_TIME_EMPTY) {
stop();
return;
}
} else {
timeEmpty= 0;
}
entries= (Map.Entry[])fStopTimes.entrySet().toArray(new Map.Entry[0]);
}
long stopTime, currentTime= System.currentTimeMillis();
Long entryValue;
Map.Entry entry= null;
for (int i= 0, numEntries= entries.length; i < numEntries; i++) {
entry= entries[i];
entryValue= (Long)entry.getValue();
if (entryValue == null) {
continue;
}
stopTime= entryValue.longValue();
if (stopTime <= currentTime) {
// The timer has expired for this thread.
// Refresh the UI to show that the thread
// is performing a long evaluation
final IThread thread= (IThread)entry.getKey();
fStopTimes.remove(thread);
getView().asyncExec(new Runnable() {
public void run() {
fTimedOutThreads.add(thread);
updateRunningThread(thread);
}
});
} else {
timeToWait= Math.min(timeToWait, stopTime - currentTime);
}
}
try {
Thread.sleep(timeToWait);
} catch (InterruptedException e) {
}
}
}
/**
* @see org.eclipse.debug.core.ILaunchesListener#launchesAdded(org.eclipse.debug.core.ILaunch)
*/
public void launchesAdded(final ILaunch[] launches) {
Runnable r= new Runnable() {
public void run() {
if (isAvailable()) {
if (launches.length == 1) {
insert(launches[0]);
} else {
refresh();
}
for (int i = 0; i < launches.length; i++) {
if (launches[i].hasChildren()) {
getLaunchView().autoExpand(launches[i], false, i == (launches.length - 1));
}
}
}
}
};
getView().syncExec(r);
}
/**
* @see org.eclipse.debug.core.ILaunchesListener#launchesChanged(org.eclipse.debug.core.ILaunch)
*/
public void launchesChanged(final ILaunch[] launches) {
Runnable r= new Runnable() {
public void run() {
if (isAvailable()) {
if (launches.length == 1) {
refresh(launches[0]);
} else {
refresh();
}
for (int i = 0; i < launches.length; i++) {
if (launches[i].hasChildren()) {
getLaunchView().autoExpand(launches[i], false, i == (launches.length - 1));
}
}
}
}
};
getView().syncExec(r);
}
/**
* @see org.eclipse.debug.core.ILaunchesListener#launchesRemoved(org.eclipse.debug.core.ILaunch)
*/
public void launchesRemoved(final ILaunch[] launches) {
Runnable r= new Runnable() {
public void run() {
if (isAvailable()) {
if (launches.length == 1) {
remove(launches[0]);
} else {
refresh();
}
ILaunchManager lm= DebugPlugin.getDefault().getLaunchManager();
IDebugTarget[] targets= lm.getDebugTargets();
if (targets.length > 0) {
IDebugTarget target= targets[targets.length - 1];
try {
IThread[] threads= target.getThreads();
for (int i=0; i < threads.length; i++) {
if (threads[i].isSuspended()) {
getLaunchView().autoExpand(threads[i], false, true);
return;
}
}
} catch (DebugException de) {
DebugUIPlugin.log(de);
}
getLaunchView().autoExpand(target.getLaunch(), false, true);
}
}
}
};
getView().asyncExec(r);
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.ILaunchesListener2#launchesTerminated(org.eclipse.debug.core.ILaunch[])
*/
public void launchesTerminated(final ILaunch[] launches) {
Runnable r= new Runnable() {
public void run() {
getLaunchView().launchesTerminated(launches);
}
};
getView().asyncExec(r);
}
}