/*******************************************************************************
* Copyright (c) 2006, 2014 Wind River Systems 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:
* Wind River Systems - initial API and implementation
* Ericsson - Modified for new functionality
*******************************************************************************/
package org.eclipse.cdt.dsf.debug.ui.viewmodel.launch;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.cdt.debug.ui.CDebugUIPlugin;
import org.eclipse.cdt.dsf.concurrent.DsfRunnable;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.concurrent.ThreadSafe;
import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.datamodel.IDMContext;
import org.eclipse.cdt.dsf.datamodel.IDMEvent;
import org.eclipse.cdt.dsf.debug.service.IRunControl;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IExitedDMEvent;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IStartedDMEvent;
import org.eclipse.cdt.dsf.debug.service.IRunControl.ISuspendedDMEvent;
import org.eclipse.cdt.dsf.debug.ui.IDsfDebugUIConstants;
import org.eclipse.cdt.dsf.debug.ui.viewmodel.launch.LaunchRootVMNode.LaunchesEvent;
import org.eclipse.cdt.dsf.debug.ui.viewmodel.launch.StackFramesVMNode.IncompleteStackVMContext;
import org.eclipse.cdt.dsf.internal.ui.DsfUIPlugin;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.cdt.dsf.ui.viewmodel.AbstractVMAdapter;
import org.eclipse.cdt.dsf.ui.viewmodel.IRootVMNode;
import org.eclipse.cdt.dsf.ui.viewmodel.IVMNode;
import org.eclipse.cdt.dsf.ui.viewmodel.datamodel.AbstractDMVMProvider;
import org.eclipse.cdt.dsf.ui.viewmodel.update.AutomaticUpdatePolicy;
import org.eclipse.cdt.dsf.ui.viewmodel.update.IVMUpdatePolicy;
import org.eclipse.cdt.dsf.ui.viewmodel.update.ManualUpdatePolicy;
import org.eclipse.debug.core.DebugEvent;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.IDebugEventSetListener;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchesListener2;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDelta;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
/**
* @since 1.1
*/
public class AbstractLaunchVMProvider extends AbstractDMVMProvider
implements IDebugEventSetListener, ILaunchesListener2
{
/**
* Delay (in milliseconds) before a full stack trace will be requested.
*/
private static final int FRAME_UPDATE_DELAY= 200;
private final Map<IExecutionDMContext,ScheduledFuture<?>> fRefreshStackFramesFutures = new HashMap<IExecutionDMContext,ScheduledFuture<?>>();
private IPropertyChangeListener fPreferencesListener;
@ThreadSafe
public AbstractLaunchVMProvider(AbstractVMAdapter adapter, IPresentationContext presentationContext, DsfSession session)
{
super(adapter, presentationContext, session);
final IPreferenceStore store= DsfUIPlugin.getDefault().getPreferenceStore();
if (store.getBoolean(IDsfDebugUIConstants.PREF_STACK_FRAME_LIMIT_ENABLE)) {
getPresentationContext().setProperty(IDsfDebugUIConstants.PREF_STACK_FRAME_LIMIT, store.getInt(IDsfDebugUIConstants.PREF_STACK_FRAME_LIMIT));
}
fPreferencesListener = new IPropertyChangeListener() {
@Override
public void propertyChange(final PropertyChangeEvent event) {
handlePropertyChanged(store, event);
}};
store.addPropertyChangeListener(fPreferencesListener);
final IPreferenceStore cStore= CDebugUIPlugin.getDefault().getPreferenceStore();
getPresentationContext().setProperty(IDsfDebugUIConstants.DEBUG_VIEW_SHOW_FULL_PATH_PROPERTY, cStore.getBoolean(IDsfDebugUIConstants.DEBUG_VIEW_SHOW_FULL_PATH_PROPERTY));
cStore.addPropertyChangeListener(fPreferencesListener);
// Register the LaunchVM provider as a listener to debug and launch
// events. These events are used by the launch and processes nodes.
DebugPlugin.getDefault().addDebugEventListener(this);
DebugPlugin.getDefault().getLaunchManager().addLaunchListener(this);
}
@Override
protected IVMUpdatePolicy[] createUpdateModes() {
return new IVMUpdatePolicy[] {
new DelayedStackRefreshUpdatePolicy(new AutomaticUpdatePolicy()),
new DelayedStackRefreshUpdatePolicy(new ManualUpdatePolicy())
};
}
@Override
public void handleDebugEvents(final DebugEvent[] events) {
if (isDisposed()) return;
// We're in session's executor thread. Re-dispatch to our executor thread
// and then call root layout node.
try {
getExecutor().execute(new Runnable() {
@Override
public void run() {
if (isDisposed()) return;
for (final DebugEvent event : events) {
handleEvent(event);
}
}});
} catch (RejectedExecutionException e) {
// Ignore. This exception could be thrown if the provider is being
// shut down.
}
}
@Override
public void handleEvent(Object event, final RequestMonitor rm) {
if (event instanceof DoubleClickEvent && !isDisposed()) {
final ISelection selection= ((DoubleClickEvent) event).getSelection();
if (selection instanceof IStructuredSelection) {
Object element= ((IStructuredSelection) selection).getFirstElement();
if (element instanceof IncompleteStackVMContext) {
IncompleteStackVMContext incStackVmc = ((IncompleteStackVMContext) element);
IVMNode node = incStackVmc.getVMNode();
if (node instanceof StackFramesVMNode && node.getVMProvider() == this) {
IExecutionDMContext exeCtx= incStackVmc.getExecutionDMContext();
((StackFramesVMNode) node).incrementStackFrameLimit(exeCtx);
// replace double click event with expand stack event
final ExpandStackEvent expandStackEvent = new ExpandStackEvent(exeCtx);
getExecutor().execute(new DsfRunnable() {
@Override
public void run() {
handleEvent(expandStackEvent, null);
}
});
}
if (rm != null) {
rm.done();
}
return;
}
}
} else if (event instanceof IRunControl.ISuspendedDMEvent) {
final IRunControl.ISuspendedDMEvent suspendEvent = (IRunControl.ISuspendedDMEvent)event;
final IExecutionDMContext exeContext= suspendEvent.getDMContext();
ScheduledFuture<?> refreshStackFramesFuture = getRefreshFuture(exeContext);
// trigger delayed full stack frame update
if (refreshStackFramesFuture != null) {
// cancel previously scheduled frame update
refreshStackFramesFuture.cancel(false);
}
try {
refreshStackFramesFuture = getSession().getExecutor().schedule(
new DsfRunnable() {
@Override
public void run() {
if (getSession().isActive()) {
getExecutor().execute(new Runnable() {
@Override
public void run() {
// trigger full stack frame update
ScheduledFuture<?> future= fRefreshStackFramesFutures.get(exeContext);
if (future != null && !isDisposed()) {
fRefreshStackFramesFutures.remove(exeContext);
handleEvent(new FullStackRefreshEvent(exeContext, suspendEvent), null);
}
}});
}
}
},
FRAME_UPDATE_DELAY, TimeUnit.MILLISECONDS);
fRefreshStackFramesFutures.put(exeContext, refreshStackFramesFuture);
} catch (RejectedExecutionException e) {}
} else if (event instanceof IRunControl.IResumedDMEvent) {
IExecutionDMContext exeContext= ((IRunControl.IResumedDMEvent) event).getDMContext();
ScheduledFuture<?> refreshStackFramesFuture= fRefreshStackFramesFutures.get(exeContext);
if (refreshStackFramesFuture != null) {
// cancel previously scheduled frame update
refreshStackFramesFuture.cancel(false);
fRefreshStackFramesFutures.remove(exeContext);
}
}
super.handleEvent(event, rm);
}
/**
* Returns the future for the given execution context or for any child of the
* given execution context.
*/
private ScheduledFuture<?> getRefreshFuture(IExecutionDMContext execCtx) {
for (IExecutionDMContext refreshCtx : fRefreshStackFramesFutures.keySet()) {
if (refreshCtx.equals(execCtx) || DMContexts.isAncestorOf(refreshCtx, execCtx)) {
return fRefreshStackFramesFutures.remove(refreshCtx);
}
}
return null;
}
@Override
public void dispose() {
DebugPlugin.getDefault().removeDebugEventListener(this);
DebugPlugin.getDefault().getLaunchManager().removeLaunchListener(this);
final IPreferenceStore store= DsfUIPlugin.getDefault().getPreferenceStore();
store.removePropertyChangeListener(fPreferencesListener);
final IPreferenceStore cStore= CDebugUIPlugin.getDefault().getPreferenceStore();
cStore.removePropertyChangeListener(fPreferencesListener);
super.dispose();
}
@Override
public void launchesAdded(ILaunch[] launches) {
handleLaunchesEvent(new LaunchesEvent(launches, LaunchesEvent.Type.ADDED));
}
@Override
public void launchesRemoved(ILaunch[] launches) {
handleLaunchesEvent(new LaunchesEvent(launches, LaunchesEvent.Type.REMOVED));
}
@Override
public void launchesChanged(ILaunch[] launches) {
handleLaunchesEvent(new LaunchesEvent(launches, LaunchesEvent.Type.CHANGED));
}
@Override
public void launchesTerminated(ILaunch[] launches) {
handleLaunchesEvent(new LaunchesEvent(launches, LaunchesEvent.Type.TERMINATED));
}
private void handleLaunchesEvent(final LaunchesEvent event) {
if (isDisposed()) return;
// We're in session's executor thread. Re-dispach to our executor thread
// and then call root layout node.
try {
getExecutor().execute(new Runnable() {
@Override
public void run() {
if (isDisposed()) return;
IRootVMNode rootLayoutNode = getRootVMNode();
if (rootLayoutNode != null && rootLayoutNode.getDeltaFlags(event) != IModelDelta.NO_CHANGE) {
handleEvent(event);
}
}});
} catch (RejectedExecutionException e) {
// Ignore. This exception could be thrown if the provider is being
// shut down.
}
}
@Override
protected boolean canSkipHandlingEvent(Object newEvent, Object eventToSkip) {
// To optimize view performance when stepping rapidly, skip events that came
// before the last suspended events. However, the debug view can get suspended
// events for different threads, so make sure to skip only the events if they
// were in the same hierarchy as the last suspended event.
// Note: Avoid skipping thread started/exited events which require a larger
// scope refresh than some suspended events.
if (newEvent instanceof IStartedDMEvent || newEvent instanceof IExitedDMEvent) {
return false;
}
if (newEvent instanceof ISuspendedDMEvent && eventToSkip instanceof IDMEvent<?>) {
IDMContext newEventDmc = ((IDMEvent<?>)newEvent).getDMContext();
IDMContext eventToSkipDmc = ((IDMEvent<?>)eventToSkip).getDMContext();
if (newEventDmc.equals(eventToSkipDmc) || DMContexts.isAncestorOf(eventToSkipDmc, newEventDmc)) {
return true;
}
}
return false;
}
protected void handlePropertyChanged(final IPreferenceStore store, final PropertyChangeEvent event) {
String property = event.getProperty();
boolean processEvent = false;
if (IDsfDebugUIConstants.PREF_STACK_FRAME_LIMIT_ENABLE.equals(property)
|| IDsfDebugUIConstants.PREF_STACK_FRAME_LIMIT.equals(property)) {
if (store.getBoolean(IDsfDebugUIConstants.PREF_STACK_FRAME_LIMIT_ENABLE)) {
getPresentationContext().setProperty(IDsfDebugUIConstants.PREF_STACK_FRAME_LIMIT, store.getInt(IDsfDebugUIConstants.PREF_STACK_FRAME_LIMIT));
} else {
getPresentationContext().setProperty(IDsfDebugUIConstants.PREF_STACK_FRAME_LIMIT, null);
}
processEvent = true;
} else if (IDsfDebugUIConstants.DEBUG_VIEW_SHOW_FULL_PATH_PROPERTY.equals(property)) {
getPresentationContext().setProperty(IDsfDebugUIConstants.DEBUG_VIEW_SHOW_FULL_PATH_PROPERTY, event.getNewValue());
processEvent = true;
}
if (processEvent) {
getExecutor().execute(new DsfRunnable() {
@Override
public void run() {
handleEvent(event);
}
});
}
}
}