| /***************************************************************** |
| * Copyright (c) 2010, 2015 Texas Instruments and others |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Patrick Chuong (Texas Instruments) - Pin and Clone Supports (331781) |
| * Patrick Chuong (Texas Instruments) - Add support for icon overlay in the debug view (Bug 334566) |
| *****************************************************************/ |
| package org.eclipse.cdt.dsf.gdb.internal.ui; |
| |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| import java.util.concurrent.RejectedExecutionException; |
| import java.util.concurrent.TimeUnit; |
| |
| import org.eclipse.cdt.debug.ui.IPinProvider; |
| import org.eclipse.cdt.debug.ui.PinElementHandle; |
| import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor; |
| import org.eclipse.cdt.dsf.concurrent.ImmediateInDsfExecutor; |
| import org.eclipse.cdt.dsf.concurrent.Query; |
| 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.IProcesses; |
| import org.eclipse.cdt.dsf.debug.service.IProcesses.IProcessDMContext; |
| import org.eclipse.cdt.dsf.debug.service.IProcesses.IThreadDMContext; |
| import org.eclipse.cdt.dsf.debug.service.IProcesses.IThreadDMData; |
| import org.eclipse.cdt.dsf.debug.service.IRunControl.IExitedDMEvent; |
| import org.eclipse.cdt.dsf.debug.service.IRunControl.IResumedDMEvent; |
| import org.eclipse.cdt.dsf.debug.service.IRunControl.IStartedDMEvent; |
| import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService.ICommandControlDMContext; |
| import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService.ICommandControlShutdownDMEvent; |
| import org.eclipse.cdt.dsf.debug.ui.viewmodel.launch.StateChangedEvent; |
| import org.eclipse.cdt.dsf.mi.service.IMIContainerDMContext; |
| import org.eclipse.cdt.dsf.mi.service.IMIExecutionDMContext; |
| import org.eclipse.cdt.dsf.service.DsfServiceEventHandler; |
| import org.eclipse.cdt.dsf.service.DsfServicesTracker; |
| import org.eclipse.cdt.dsf.service.DsfSession; |
| import org.eclipse.core.runtime.IAdaptable; |
| 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.jface.resource.ImageDescriptor; |
| import org.eclipse.jface.viewers.ISelection; |
| import org.eclipse.ui.IWorkbenchPart; |
| |
| /** |
| * GDB pin provider implementation. |
| */ |
| public class GdbPinProvider implements IPinProvider { |
| private static class GdbPinElementColorDescriptor implements IPinElementColorDescriptor { |
| int fColor = GREEN; |
| |
| GdbPinElementColorDescriptor(int color) { |
| fColor = color; |
| } |
| |
| @Override |
| public int getOverlayColor() { |
| return fColor; |
| } |
| |
| @Override |
| public ImageDescriptor getToolbarIconDescriptor() { |
| return null; |
| } |
| } |
| |
| /** |
| * A set of pinned element handles and their callback. |
| */ |
| private static Map<IPinElementHandle, IPinModelListener> gsPinnedHandles = Collections |
| .synchronizedMap(new HashMap<IPinElementHandle, IPinModelListener>()); |
| |
| /** |
| * Dsf session. |
| */ |
| private final DsfSession fSession; |
| |
| /** |
| * Constructor. |
| * |
| * @param session |
| */ |
| public GdbPinProvider(DsfSession session) { |
| fSession = session; |
| |
| session.getExecutor().execute(() -> fSession.addServiceEventListener(GdbPinProvider.this, null)); |
| } |
| |
| /** |
| * Dispose all resources. |
| */ |
| public void dispose() { |
| try { |
| fSession.getExecutor().execute(() -> fSession.removeServiceEventListener(GdbPinProvider.this)); |
| } catch (RejectedExecutionException e) { |
| // Session already gone. |
| } |
| } |
| |
| /** |
| * Returns the pinned element handles. |
| * |
| * @return the element handles. |
| */ |
| public static Set<IPinElementHandle> getPinnedHandles() { |
| return gsPinnedHandles.keySet(); |
| } |
| |
| private static IMIExecutionDMContext getExecutionDmc(IDMContext dmc) { |
| return DMContexts.getAncestorOfType(dmc, IMIExecutionDMContext.class); |
| } |
| |
| private static IProcessDMContext getProcessDmc(IDMContext dmc) { |
| return DMContexts.getAncestorOfType(dmc, IProcessDMContext.class); |
| } |
| |
| private IThreadDMData getData(final IThreadDMContext threadDmc) { |
| if (threadDmc == null || !fSession.isActive()) |
| return null; |
| |
| IThreadDMData data = null; |
| final DsfServicesTracker tracker = new DsfServicesTracker(GdbUIPlugin.getBundleContext(), fSession.getId()); |
| try { |
| Query<IThreadDMData> query = new Query<>() { |
| @Override |
| protected void execute(final DataRequestMonitor<IThreadDMData> rm) { |
| final IProcesses processes = tracker.getService(IProcesses.class); |
| if (processes != null) { |
| processes.getExecutionData(threadDmc, rm); |
| } else { |
| rm.setData(null); |
| rm.done(); |
| } |
| } |
| }; |
| |
| ImmediateInDsfExecutor immediateExecutor = new ImmediateInDsfExecutor(fSession.getExecutor()); |
| immediateExecutor.execute(query); |
| data = query.get(2, TimeUnit.SECONDS); // timeout in 2 seconds, in case the call to execute got stuck |
| } catch (Exception e) { |
| GdbUIPlugin.log(e); |
| } finally { |
| if (tracker != null) |
| tracker.dispose(); |
| } |
| return data; |
| } |
| |
| private String getLabel(IThreadDMData data) { |
| String label = ""; //$NON-NLS-1$ |
| if (data != null) { |
| String name = data.getName(); |
| String id = data.getId(); |
| if (name != null && !name.isEmpty()) |
| label = name; |
| else if (id != null && !id.isEmpty()) |
| label = id; |
| } |
| return label; |
| } |
| |
| private String getCombinedLabels(IThreadDMContext processDmc, IMIExecutionDMContext execDmc) { |
| // get the process label |
| IThreadDMData processData = getData(processDmc); |
| String label = getLabel(processData); |
| |
| // get the execution (thread) context label |
| if (execDmc != null) { |
| String threadId = execDmc.getThreadId(); |
| label += !label.isEmpty() ? ": " : ""; //$NON-NLS-1$//$NON-NLS-2$ |
| label += "Thread [" + threadId + "]"; //$NON-NLS-1$//$NON-NLS-2$ |
| } |
| return label; |
| } |
| |
| @Override |
| public boolean isPinnable(IWorkbenchPart part, Object debugContext) { |
| if (debugContext instanceof IAdaptable) { |
| return ((IAdaptable) debugContext).getAdapter(IDMContext.class) != null; |
| } |
| return false; |
| } |
| |
| @Override |
| public IPinElementHandle pin(IWorkbenchPart part, Object debugContext, IPinModelListener listener) { |
| Object pinContext = debugContext; |
| String label = ""; //$NON-NLS-1$ |
| String sessionId = ""; //$NON-NLS-1$ |
| |
| IDMContext dmc = null; |
| if (debugContext instanceof IAdaptable) { |
| dmc = ((IAdaptable) debugContext).getAdapter(IDMContext.class); |
| |
| if (dmc != null) { |
| sessionId = dmc.getSessionId() + "."; //$NON-NLS-1$ |
| IMIExecutionDMContext execDmc = getExecutionDmc(dmc); |
| IProcessDMContext processDmc = getProcessDmc(dmc); |
| |
| label = getCombinedLabels(processDmc, execDmc); |
| |
| // set the pin context to a thread if it exist |
| if (execDmc != null) { |
| dmc = execDmc; |
| pinContext = execDmc; |
| |
| // otherwise, set it to the DM context |
| } else { |
| pinContext = dmc; |
| } |
| } |
| } |
| |
| IPinElementColorDescriptor colorDesc = new GdbPinElementColorDescriptor( |
| GdbPinColorTracker.INSTANCE.addRef(sessionId + label)); |
| PinElementHandle handle = new PinElementHandle(pinContext, label, colorDesc); |
| gsPinnedHandles.put(handle, listener); |
| dispatchChangedEvent(dmc); |
| |
| return handle; |
| } |
| |
| @Override |
| public void unpin(IWorkbenchPart part, IPinElementHandle handle) { |
| // remove the handle from the cache |
| gsPinnedHandles.remove(handle); |
| |
| // dispatch the event to update the handle DM context |
| Object debugContext = handle.getDebugContext(); |
| if (debugContext instanceof IAdaptable) { |
| IDMContext dmc = ((IAdaptable) debugContext).getAdapter(IDMContext.class); |
| GdbPinColorTracker.INSTANCE.removeRef(dmc.getSessionId() + "." + handle.getLabel()); //$NON-NLS-1$ |
| dispatchChangedEvent(dmc); |
| |
| } |
| } |
| |
| @Override |
| public boolean isPinnedTo(Object debugContext, IPinElementHandle handle) { |
| Object handleDebugContext = handle.getDebugContext(); |
| |
| if (debugContext instanceof IAdaptable && handleDebugContext instanceof IAdaptable) { |
| IDMContext dmc = ((IAdaptable) debugContext).getAdapter(IDMContext.class); |
| IDMContext hDmc = ((IAdaptable) handleDebugContext).getAdapter(IDMContext.class); |
| |
| if (dmc != null && hDmc != null) { |
| if (dmc.getSessionId().equals(hDmc.getSessionId())) { |
| IMIExecutionDMContext execDmc = getExecutionDmc(dmc); |
| IProcessDMContext processDmc = getProcessDmc(dmc); |
| |
| String label = getCombinedLabels(processDmc, execDmc); |
| return label.equals(handle.getLabel()); |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Dispatch the change event for the given DM context. |
| * |
| * @param dmc the DM context |
| */ |
| private void dispatchChangedEvent(IDMContext dmc) { |
| if (dmc == null) |
| return; |
| |
| try { |
| DsfSession session = DsfSession.getSession(dmc.getSessionId()); |
| if (session != null && session.isActive()) |
| session.dispatchEvent(new StateChangedEvent(dmc), null); |
| } catch (RejectedExecutionException e) { |
| // Session already gone. |
| } |
| } |
| |
| /** |
| * Handle start event and re-attach the DM context to the pinned handles. The DM context |
| * is used for dispatching event to update the element label. |
| */ |
| @DsfServiceEventHandler |
| public void handleEvent(final IStartedDMEvent event) { |
| final IDMContext eventDmc = event.getDMContext(); |
| final IMIExecutionDMContext eventExecDmc = getExecutionDmc(eventDmc); |
| final IProcessDMContext eventProcessDmc = getProcessDmc(eventDmc); |
| |
| if (eventProcessDmc != null) { |
| for (final IPinElementHandle h : getPinnedHandles()) { |
| new Job("Updating pin handler debug context") { //$NON-NLS-1$ |
| { |
| setPriority(INTERACTIVE); |
| } |
| |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| // only attach to the same pin handle if the session is not active |
| PinElementHandle handle = ((PinElementHandle) h); |
| Object handleDebugContext = handle.getDebugContext(); |
| if (handleDebugContext instanceof IAdaptable) { |
| IDMContext handleDmc = ((IAdaptable) handleDebugContext).getAdapter(IDMContext.class); |
| if (handleDmc != null) { |
| DsfSession session = DsfSession.getSession(handleDmc.getSessionId()); |
| if (session == null || !session.isActive()) { |
| String handleLabel = handle.getLabel(); |
| String label = getCombinedLabels(eventProcessDmc, eventExecDmc); |
| |
| if (label.equals(handleLabel)) { |
| IDMContext newDmc = eventExecDmc != null ? eventExecDmc : eventDmc; |
| handle.setDebugContext(newDmc); |
| dispatchChangedEvent(newDmc); |
| } |
| } |
| } |
| } |
| return Status.OK_STATUS; |
| } |
| }.schedule(); |
| } |
| } |
| } |
| |
| @DsfServiceEventHandler |
| public void handleEvent(final ICommandControlShutdownDMEvent event) { |
| handleInvalidModelContext(event); |
| } |
| |
| @DsfServiceEventHandler |
| public void handleEvent(final IExitedDMEvent event) { |
| handleInvalidModelContext(event); |
| } |
| |
| @DsfServiceEventHandler |
| public void handleEvent(final IResumedDMEvent event) { |
| handleInvalidModelContext(event); |
| } |
| |
| private void handleInvalidModelContext(IDMEvent<?> event) { |
| Set<Entry<IPinElementHandle, IPinModelListener>> entries = gsPinnedHandles.entrySet(); |
| for (final Entry<IPinElementHandle, IPinModelListener> e : entries) { |
| final IPinModelListener listener = e.getValue(); |
| if (listener != null) { |
| IPinElementHandle handle = e.getKey(); |
| |
| Object handleObject = handle.getDebugContext(); |
| if (handleObject instanceof IDMContext) { |
| IDMContext handleDmc = (IDMContext) handleObject; |
| IDMContext eventDmc = event.getDMContext(); |
| |
| // First check if we have a thread. We must use IMIExecutionDMContext and not |
| // IExecutionDMContext because IExecutionDMContext also represents a process |
| IMIExecutionDMContext execEventDmc = DMContexts.getAncestorOfType(eventDmc, |
| IMIExecutionDMContext.class); |
| IMIExecutionDMContext execHandleDmc = DMContexts.getAncestorOfType(handleDmc, |
| IMIExecutionDMContext.class); |
| |
| // Make sure both dmcs are not null to know if we should compare thread dmcs or container dmcs |
| if (execEventDmc != null && execHandleDmc != null) { |
| // It is a thread event, but is it the same as the pin handle? |
| if (execEventDmc.equals(execHandleDmc)) { |
| fireModleChangeEvent(listener, null); |
| } |
| continue; |
| } |
| |
| // If we weren't dealing with a thread for either the event or the handle, |
| // let's check for IMIContainerDMContext |
| IMIContainerDMContext procEventDmc = DMContexts.getAncestorOfType(eventDmc, |
| IMIContainerDMContext.class); |
| IMIContainerDMContext procHandleDmc = DMContexts.getAncestorOfType(handleDmc, |
| IMIContainerDMContext.class); |
| if (procEventDmc != null && procHandleDmc != null) { |
| if (procEventDmc.equals(procHandleDmc)) { |
| fireModleChangeEvent(listener, null); |
| } |
| continue; |
| } |
| |
| // If we got a shutdown event |
| if (eventDmc instanceof ICommandControlDMContext) { |
| fireModleChangeEvent(listener, null); |
| continue; |
| } |
| } |
| } |
| } |
| } |
| |
| private void fireModleChangeEvent(final IPinModelListener listener, final ISelection selection) { |
| new Job("Model Changed") { //$NON-NLS-1$ |
| { |
| setSystem(true); |
| } |
| |
| @Override |
| protected IStatus run(IProgressMonitor arg0) { |
| listener.modelChanged(selection); |
| return Status.OK_STATUS; |
| } |
| }.schedule(); |
| } |
| } |