/*******************************************************************************
* Copyright (c) 2010 Ericsson 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:
* Ericsson - initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.dsf.gdb.internal.ui.tracepoints;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.DsfRunnable;
import org.eclipse.cdt.dsf.concurrent.IDsfStatusConstants;
import org.eclipse.cdt.dsf.concurrent.ImmediateExecutor;
import org.eclipse.cdt.dsf.concurrent.Query;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.datamodel.IDMContext;
import org.eclipse.cdt.dsf.debug.service.IRunControl.ISuspendedDMEvent;
import org.eclipse.cdt.dsf.gdb.internal.ui.GdbUIPlugin;
import org.eclipse.cdt.dsf.gdb.service.IGDBTraceControl;
import org.eclipse.cdt.dsf.gdb.service.IGDBTraceControl.ITraceRecordDMContext;
import org.eclipse.cdt.dsf.gdb.service.IGDBTraceControl.ITraceRecordSelectedChangedDMEvent;
import org.eclipse.cdt.dsf.gdb.service.IGDBTraceControl.ITraceStatusDMData;
import org.eclipse.cdt.dsf.gdb.service.IGDBTraceControl.ITraceTargetDMContext;
import org.eclipse.cdt.dsf.gdb.service.IGDBTraceControl.ITraceVariableDMData;
import org.eclipse.cdt.dsf.gdb.service.IGDBTraceControl.ITracingStartedDMEvent;
import org.eclipse.cdt.dsf.gdb.service.IGDBTraceControl.ITracingStoppedDMEvent;
import org.eclipse.cdt.dsf.gdb.service.IGDBTraceControl.ITracingSupportedChangeDMEvent;
import org.eclipse.cdt.dsf.service.DsfServiceEventHandler;
import org.eclipse.cdt.dsf.service.DsfServicesTracker;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.cdt.dsf.service.DsfSession.SessionEndedListener;
import org.eclipse.cdt.dsf.ui.viewmodel.datamodel.IDMVMContext;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.debug.ui.IDebugUIConstants;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.part.ViewPart;
/**
* TraceControlView Part
*
* This view is used to control Tracing.
*
* @since 2.1
*/
public class TraceControlView extends ViewPart implements IViewPart, SessionEndedListener {
private static final String EMPTY_STRING = ""; //$NON-NLS-1$
public class FailedTraceVariableCreationException extends Exception {
private static final long serialVersionUID = -3042693455630687285L;
FailedTraceVariableCreationException() {}
FailedTraceVariableCreationException(String errorMessage) {
super(errorMessage);
}
}
/**
* Action to refresh the content of the view.
*/
private final class ActionRefreshView extends Action {
public ActionRefreshView() {
setText(TracepointsMessages.TraceControlView_action_Refresh_label);
setImageDescriptor(TracepointImageRegistry.getImageDescriptor(TracepointImageRegistry.ICON_Refresh_enabled));
setDisabledImageDescriptor(TracepointImageRegistry.getImageDescriptor(TracepointImageRegistry.ICON_Refresh_disabled));
}
@Override
public void run() {
updateContent();
}
}
private final class ActionOpenTraceVarDetails extends Action {
public ActionOpenTraceVarDetails() {
setText(TracepointsMessages.TraceControlView_action_trace_variable_details);
setImageDescriptor(TracepointImageRegistry.getImageDescriptor(TracepointImageRegistry.ICON_Trace_Variables));
}
@Override
public void run() {
Shell shell = Display.getDefault().getActiveShell();
TraceVarDetailsDialog dialog = new TraceVarDetailsDialog(shell, TraceControlView.this);
dialog.open();
}
}
private final class ActionExitVisualizationModeDetails extends Action {
public ActionExitVisualizationModeDetails() {
setText(TracepointsMessages.TraceControlView_action_exit_visualization_mode);
setImageDescriptor(TracepointImageRegistry.getImageDescriptor(TracepointImageRegistry.ICON_Exit_Visualization));
}
@Override
public void run() {
asyncExec(new Runnable() {
public void run() {
exitVisualizationMode();
updateActionEnablement();
}});
}
}
private ISelectionListener fDebugViewListener;
private String fDebugSessionId;
private DsfServicesTracker fServicesTracker;
private volatile ITraceTargetDMContext fTargetContext;
private StyledText fStatusText;
protected Action fActionRefreshView;
protected Action fOpenTraceVarDetails;
protected Action fActionExitVisualization;
private boolean fTracingSupported;
private boolean fTraceVisualization;
public TraceControlView() {
}
@Override
public void init(IViewSite site) throws PartInitException {
super.init(site);
site.getPage().addSelectionListener(IDebugUIConstants.ID_DEBUG_VIEW,
fDebugViewListener = new ISelectionListener() {
public void selectionChanged(IWorkbenchPart part, ISelection selection) {
updateDebugContext();
}});
}
@Override
public void init(IViewSite site, IMemento memento) throws PartInitException {
init(site);
}
@Override
public void createPartControl(Composite parent) {
Composite composite = new Composite(parent, SWT.NONE);
GridLayout layout = new GridLayout(1, false);
composite.setLayout(layout);
// Let's just create a place to put a text status for now.
// A fancier display would be nicer though
fStatusText = new StyledText(composite, SWT.MULTI);
GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true);
fStatusText.setLayoutData(gd);
fStatusText.setEditable(false);
fStatusText.setCaret(null);
createActions();
if (fDebugSessionId != null) {
debugSessionChanged();
} else {
updateDebugContext();
}
DsfSession.addSessionEndedListener(this);
}
protected void createActions() {
IActionBars bars = getViewSite().getActionBars();
IToolBarManager manager = bars.getToolBarManager();
// Create the action to refresh the view
fActionRefreshView = new ActionRefreshView();
bars.setGlobalActionHandler(ActionFactory.REFRESH.getId(), fActionRefreshView);
manager.add(fActionRefreshView);
// Create the action to open the trace variable details
fOpenTraceVarDetails = new ActionOpenTraceVarDetails();
manager.add(fOpenTraceVarDetails);
// Create the action to exit visualization mode
fActionExitVisualization = new ActionExitVisualizationModeDetails();
manager.add(fActionExitVisualization);
bars.updateActionBars();
updateActionEnablement();
}
@Override
public void dispose() {
getSite().getPage().removeSelectionListener(IDebugUIConstants.ID_DEBUG_VIEW, fDebugViewListener);
fStatusText = null; // Indicate that we have been disposed
setDebugContext(null);
super.dispose();
}
protected void updateContent() {
if (fDebugSessionId != null && getSession() != null) {
final ITraceTargetDMContext ctx = DMContexts.getAncestorOfType(fTargetContext, ITraceTargetDMContext.class);
if (ctx != null) {
getSession().getExecutor().execute(
new DsfRunnable() {
public void run() {
final IGDBTraceControl traceControl = getService(IGDBTraceControl.class);
if (traceControl != null) {
traceControl.getTraceStatus(
ctx, new DataRequestMonitor<ITraceStatusDMData>(getSession().getExecutor(), null) {
@Override
protected void handleCompleted() {
String traceStatus = EMPTY_STRING;
if (isSuccess() && getData() != null) {
fTracingSupported = getData().isTracingSupported();
if (fTracingSupported) {
traceStatus = getData().toString();
if (traceStatus.length() > 0) {
Calendar cal = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); //$NON-NLS-1$
traceStatus = TracepointsMessages.TraceControlView_trace_view_content_updated_label +
sdf.format(cal.getTime()) + "\n" + traceStatus; //$NON-NLS-1$
}
}
} else {
fTracingSupported = false;
}
final String finalStatus = traceStatus;
asyncExec(new Runnable() {
public void run() {
fStatusText.setText(finalStatus);
updateActionEnablement();
}});
}
});
} else {
fTracingSupported = false;
asyncExec(new Runnable() {
public void run() {
fStatusText.setText(EMPTY_STRING);
updateActionEnablement();
}});
}
}
});
return;
}
}
if (fStatusText != null) {
fStatusText.setText(EMPTY_STRING);
}
updateActionEnablement();
}
protected void exitVisualizationMode() {
if (fDebugSessionId == null || getSession() == null) {
return;
}
final ITraceTargetDMContext ctx = DMContexts.getAncestorOfType(fTargetContext, ITraceTargetDMContext.class);
if (ctx == null) {
return;
}
getSession().getExecutor().execute(
new DsfRunnable() {
public void run() {
final IGDBTraceControl traceControl = getService(IGDBTraceControl.class);
if (traceControl != null) {
ITraceRecordDMContext emptyDmc = traceControl.createTraceRecordContext(ctx, "-1"); //$NON-NLS-1$
traceControl.selectTraceRecord(emptyDmc, new RequestMonitor(ImmediateExecutor.getInstance(), null));
}
}
});
}
protected void updateDebugContext() {
IAdaptable debugContext = DebugUITools.getDebugContext();
if (debugContext instanceof IDMVMContext) {
setDebugContext((IDMVMContext)debugContext);
} else {
setDebugContext(null);
}
}
protected void setDebugContext(IDMVMContext vmContext) {
if (vmContext != null) {
IDMContext dmContext = vmContext.getDMContext();
String sessionId = dmContext.getSessionId();
fTargetContext = DMContexts.getAncestorOfType(dmContext, ITraceTargetDMContext.class);
if (!sessionId.equals(fDebugSessionId)) {
if (fDebugSessionId != null && getSession() != null) {
try {
final DsfSession session = getSession();
session.getExecutor().execute(new DsfRunnable() {
public void run() {
session.removeServiceEventListener(TraceControlView.this);
}
});
} catch (RejectedExecutionException e) {
// Session is shut down.
}
}
fDebugSessionId = sessionId;
if (fServicesTracker != null) {
fServicesTracker.dispose();
}
fServicesTracker = new DsfServicesTracker(GdbUIPlugin.getBundleContext(), sessionId);
debugSessionChanged();
}
} else if (fDebugSessionId != null) {
if (getSession() != null) {
try {
final DsfSession session = getSession();
session.getExecutor().execute(new DsfRunnable() {
public void run() {
session.removeServiceEventListener(TraceControlView.this);
}
});
} catch (RejectedExecutionException e) {
// Session is shut down.
}
}
fDebugSessionId = null;
fTargetContext = null;
if (fServicesTracker != null) {
fServicesTracker.dispose();
fServicesTracker = null;
}
debugSessionChanged();
}
}
private void debugSessionChanged() {
// When dealing with a new debug session, assume tracing is not supported.
// updateContent() will fix it
fTracingSupported = false;
if (fDebugSessionId != null && getSession() != null) {
try {
final DsfSession session = getSession();
session.getExecutor().execute(new DsfRunnable() {
public void run() {
session.addServiceEventListener(TraceControlView.this, null);
}
});
} catch (RejectedExecutionException e) {
// Session is shut down.
}
}
updateContent();
}
protected void updateActionEnablement() {
fOpenTraceVarDetails.setEnabled(fTracingSupported);
fActionRefreshView.setEnabled(fTracingSupported);
// This hack is to avoid adding an API late in the release.
// For the next release, we should have a proper call to know if
// we can stop visualization or not
if (fStatusText != null && fStatusText.getText().toLowerCase().indexOf("off-line") != -1) { //$NON-NLS-1$
fActionExitVisualization.setEnabled(false);
} else {
fActionExitVisualization.setEnabled(fTraceVisualization);
}
}
private void asyncExec(Runnable runnable) {
if (fStatusText != null) {
fStatusText.getDisplay().asyncExec(runnable);
}
}
public void sessionEnded(DsfSession session) {
if (session.getId().equals(fDebugSessionId)) {
asyncExec(new Runnable() {
public void run() {
setDebugContext(null);
}});
}
}
/*
* When tracing starts, we know the status has changed
*/
@DsfServiceEventHandler
public void handleEvent(ITracingStartedDMEvent event) {
updateContent();
}
/*
* When tracing stops, we know the status has changed
*/
@DsfServiceEventHandler
public void handleEvent(ITracingStoppedDMEvent event) {
updateContent();
}
@DsfServiceEventHandler
public void handleEvent(ITraceRecordSelectedChangedDMEvent event) {
if (event.isVisualizationModeEnabled()) {
fTraceVisualization = true;
} else {
fTraceVisualization = false;
}
updateContent();
}
/*
* Since something suspended, might as well refresh our status
* to show the latest.
*/
@DsfServiceEventHandler
public void handleEvent(ISuspendedDMEvent event) {
updateContent();
}
/*
* Tracing support has changed, update view
*/
@DsfServiceEventHandler
public void handleEvent(ITracingSupportedChangeDMEvent event) {
updateContent();
}
@Override
public void setFocus() {
if (fStatusText != null) {
fStatusText.setFocus();
}
}
private DsfSession getSession() {
return DsfSession.getSession(fDebugSessionId);
}
private <V> V getService(Class<V> serviceClass) {
if (fServicesTracker != null) {
return fServicesTracker.getService(serviceClass);
}
return null;
}
/**
* Get the list of trace variables from the backend.
*
* @return null when the list cannot be obtained.
*/
public ITraceVariableDMData[] getTraceVarList() {
if (fDebugSessionId == null || getSession() == null) {
return null;
}
final ITraceTargetDMContext ctx = DMContexts.getAncestorOfType(fTargetContext, ITraceTargetDMContext.class);
if (ctx == null) {
return null;
}
Query<ITraceVariableDMData[]> query = new Query<ITraceVariableDMData[]>() {
@Override
protected void execute(final DataRequestMonitor<ITraceVariableDMData[]> rm) {
final IGDBTraceControl traceControl = getService(IGDBTraceControl.class);
if (traceControl != null) {
traceControl.getTraceVariables(ctx,
new DataRequestMonitor<ITraceVariableDMData[]>(getSession().getExecutor(), rm) {
@Override
protected void handleCompleted() {
if (isSuccess()) {
rm.setData(getData());
} else {
rm.setData(null);
}
rm.done();
};
});
} else {
rm.setData(null);
rm.done();
}
}
};
try {
getSession().getExecutor().execute(query);
return query.get(1, TimeUnit.SECONDS);
} catch (InterruptedException exc) {
} catch (ExecutionException exc) {
} catch (TimeoutException e) {
}
return null;
}
/**
* Create a new trace variable in the backend.
*
* @throws FailedTraceVariableCreationException when the creation fails. The exception
* will contain the error message to display to the user.
*/
protected void createVariable(final String name, final String value) throws FailedTraceVariableCreationException {
if (fDebugSessionId == null || getSession() == null) {
throw new FailedTraceVariableCreationException(TracepointsMessages.TraceControlView_create_variable_error);
}
final ITraceTargetDMContext ctx = DMContexts.getAncestorOfType(fTargetContext, ITraceTargetDMContext.class);
if (ctx == null) {
throw new FailedTraceVariableCreationException(TracepointsMessages.TraceControlView_create_variable_error);
}
Query<String> query = new Query<String>() {
@Override
protected void execute(final DataRequestMonitor<String> rm) {
final IGDBTraceControl traceControl = getService(IGDBTraceControl.class);
if (traceControl != null) {
traceControl.createTraceVariable(ctx, name, value,
new RequestMonitor(getSession().getExecutor(), rm) {
@Override
protected void handleFailure() {
String message = TracepointsMessages.TraceControlView_create_variable_error;
Throwable t = getStatus().getException();
if (t != null) {
message = t.getMessage();
}
FailedTraceVariableCreationException e =
new FailedTraceVariableCreationException(message);
rm.setStatus(new Status(IStatus.ERROR, GdbUIPlugin.PLUGIN_ID, IDsfStatusConstants.INVALID_STATE, "Backend error", e)); //$NON-NLS-1$
rm.done();
};
});
} else {
FailedTraceVariableCreationException e =
new FailedTraceVariableCreationException(TracepointsMessages.TraceControlView_trace_variable_tracing_unavailable);
rm.setStatus(new Status(IStatus.ERROR, GdbUIPlugin.PLUGIN_ID, IDsfStatusConstants.INVALID_STATE, "Tracing unavailable", e)); //$NON-NLS-1$
rm.done();
}
}
};
try {
getSession().getExecutor().execute(query);
query.get();
} catch (InterruptedException e) {
// Session terminated
} catch (ExecutionException e) {
Throwable t = e.getCause();
if (t instanceof CoreException) {
t = ((CoreException)t).getStatus().getException();
if (t instanceof FailedTraceVariableCreationException) {
throw (FailedTraceVariableCreationException)t;
}
}
throw new FailedTraceVariableCreationException(TracepointsMessages.TraceControlView_create_variable_error);
}
}
}