/******************************************************************************* * Copyright (c) 2008, 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.mi.service; import java.util.Map; import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor; import org.eclipse.cdt.dsf.concurrent.ImmediateExecutor; import org.eclipse.cdt.dsf.concurrent.Immutable; import org.eclipse.cdt.dsf.concurrent.RequestMonitor; import org.eclipse.cdt.dsf.datamodel.AbstractDMContext; import org.eclipse.cdt.dsf.datamodel.AbstractDMEvent; import org.eclipse.cdt.dsf.datamodel.DMContexts; import org.eclipse.cdt.dsf.datamodel.IDMContext; import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointsTargetDMContext; import org.eclipse.cdt.dsf.debug.service.ICachingService; import org.eclipse.cdt.dsf.debug.service.IDisassembly.IDisassemblyDMContext; import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerDMContext; import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerResumedDMEvent; import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerSuspendedDMEvent; 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.IResumedDMEvent; import org.eclipse.cdt.dsf.debug.service.IRunControl.IStartedDMEvent; import org.eclipse.cdt.dsf.debug.service.IRunControl.ISuspendedDMEvent; import org.eclipse.cdt.dsf.debug.service.command.BufferedCommandControl; import org.eclipse.cdt.dsf.debug.service.command.CommandCache; import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService; import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService.ICommandControlDMContext; import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin; import org.eclipse.cdt.dsf.gdb.service.IGDBBackend; import org.eclipse.cdt.dsf.mi.service.command.CommandFactory; import org.eclipse.cdt.dsf.mi.service.command.output.CLIInfoThreadsInfo; import org.eclipse.cdt.dsf.mi.service.command.output.MIInfo; import org.eclipse.cdt.dsf.mi.service.command.output.MIThreadListIdsInfo; import org.eclipse.cdt.dsf.service.AbstractDsfService; import org.eclipse.cdt.dsf.service.DsfServiceEventHandler; import org.eclipse.cdt.dsf.service.DsfSession; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.osgi.framework.BundleContext; /** * @since 1.1 */ public class MIProcesses extends AbstractDsfService implements IMIProcesses, ICachingService { // Below is the context hierarchy that is implemented between the // MIProcesses service and the MIRunControl service for the MI // implementation of DSF: // // MIControlDMContext (ICommandControlDMContext) // | // MIProcessDMC (IProcess) // / \ // / \ // MIContainerDMC MIThreadDMC (IThread) // (IContainer) / // \ / // MIExecutionDMC // (IExecution) // /** * Context representing a thread in GDB/MI */ @Immutable private static class MIExecutionDMC extends AbstractDMContext implements IMIExecutionDMContext { /** * String ID that is used to identify the thread in the GDB/MI protocol. */ private final String fThreadId; /** * Constructor for the context. It should not be called directly by clients. * Instead clients should call {@link IMIProcesses#createExecutionContext()} * to create instances of this context based on the thread ID. * <p/> * * @param sessionId Session that this context belongs to. * @param containerDmc The container that this context belongs to. * @param threadDmc The thread context parents of this context. * @param threadId GDB/MI thread identifier. */ protected MIExecutionDMC(String sessionId, IContainerDMContext containerDmc, IThreadDMContext threadDmc, String threadId) { super(sessionId, containerDmc == null && threadDmc == null ? new IDMContext[0] : containerDmc == null ? new IDMContext[] { threadDmc } : threadDmc == null ? new IDMContext[] { containerDmc } : new IDMContext[] { containerDmc, threadDmc }); fThreadId = threadId; } /** * Returns the GDB/MI thread identifier of this context. * @return */ public int getThreadId(){ try { return Integer.parseInt(fThreadId); } catch (NumberFormatException e) { } return 0; } // Enable if need arises // public String getId(){ // return fThreadId; // } @Override public String toString() { return baseToString() + ".thread[" + fThreadId + "]"; } //$NON-NLS-1$ //$NON-NLS-2$ @Override public boolean equals(Object obj) { return super.baseEquals(obj) && ((MIExecutionDMC)obj).fThreadId.equals(fThreadId); } @Override public int hashCode() { return super.baseHashCode() ^ fThreadId.hashCode(); } } /** * Context representing a thread group of GDB/MI. */ @Immutable protected static class MIContainerDMC extends AbstractDMContext implements IMIContainerDMContext, IBreakpointsTargetDMContext, IDisassemblyDMContext { /** * String ID that is used to identify the thread group in the GDB/MI protocol. */ private final String fId; /** * Constructor for the context. It should not be called directly by clients. * Instead clients should call {@link IMIProcesses#createContainerContext * to create instances of this context based on the group name. * * @param sessionId Session that this context belongs to. * @param processDmc The process context that is the parent of this context. * @param groupId GDB/MI thread group identifier. */ public MIContainerDMC(String sessionId, IProcessDMContext processDmc, String groupId) { super(sessionId, processDmc == null ? new IDMContext[0] : new IDMContext[] { processDmc }); fId = groupId; } /** * Returns the GDB/MI thread group identifier of this context. */ public String getGroupId(){ return fId; } @Override public String toString() { return baseToString() + ".threadGroup[" + fId + "]"; } //$NON-NLS-1$ //$NON-NLS-2$ @Override public boolean equals(Object obj) { return super.baseEquals(obj) && (((MIContainerDMC)obj).fId == null ? fId == null : ((MIContainerDMC)obj).fId.equals(fId)); } @Override public int hashCode() { return super.baseHashCode() ^ (fId == null ? 0 : fId.hashCode()); } } /** * Context representing a thread. */ @Immutable private static class MIThreadDMC extends AbstractDMContext implements IThreadDMContext { /** * ID used by GDB to refer to threads. * We use the same id as the one used in {@link MIProcesses#MIExecutionDMC} */ private final String fId; /** * Constructor for the context. It should not be called directly by clients. * Instead clients should call {@link IMIProcesses#createThreadContext} * to create instances of this context based on the thread ID. * <p/> * * @param sessionId Session that this context belongs to. * @param processDmc The process that this thread belongs to. * @param id thread identifier. */ public MIThreadDMC(String sessionId, IProcessDMContext processDmc, String id) { super(sessionId, processDmc == null ? new IDMContext[0] : new IDMContext[] { processDmc }); fId = id; } /** * Returns the thread identifier of this context. * @return */ public String getId(){ return fId; } @Override public String toString() { return baseToString() + ".OSthread[" + fId + "]"; } //$NON-NLS-1$ //$NON-NLS-2$ @Override public boolean equals(Object obj) { return super.baseEquals(obj) && (((MIThreadDMC)obj).fId == null ? fId == null : ((MIThreadDMC)obj).fId.equals(fId)); } @Override public int hashCode() { return super.baseHashCode() ^ (fId == null ? 0 : fId.hashCode()); } } @Immutable private static class MIProcessDMC extends AbstractDMContext implements IMIProcessDMContext { /** * ID given by the OS. * For practicality, we use the same id as the one used in {@link MIProcesses#MIContainerDMC} */ private final String fId; /** * Constructor for the context. It should not be called directly by clients. * Instead clients should call {@link IMIProcesses#createProcessContext} * to create instances of this context based on the PID. * <p/> * * @param sessionId Session that this context belongs to. * @param controlDmc The control context parent of this process. * @param id process identifier. */ public MIProcessDMC(String sessionId, ICommandControlDMContext controlDmc, String id) { super(sessionId, controlDmc == null ? new IDMContext[0] : new IDMContext[] { controlDmc }); fId = id; } public String getProcId() { return fId; } @Override public String toString() { return baseToString() + ".proc[" + fId + "]"; } //$NON-NLS-1$ //$NON-NLS-2$ @Override public boolean equals(Object obj) { // We treat the UNKNOWN_PROCESS_ID as a wildcard. Any processId (except null) will be considered // equal to the UNKNOWN_PROCESS_ID. This is important because before starting a process, we don't // have a pid yet, but we still need to create a process context, and we must use UNKNOWN_PROCESS_ID. // Bug 336890 if (!baseEquals(obj)) { return false; } MIProcessDMC other = (MIProcessDMC)obj; if (fId == null || other.fId == null) { return fId == null && other.fId == null; } // Now that we know neither is null, check for UNKNOWN_PROCESS_ID wildcard if (fId.equals(UNKNOWN_PROCESS_ID) || other.fId.equals(UNKNOWN_PROCESS_ID)) { return true; } return fId.equals(other.fId); } @Override public int hashCode() { // We cannot use fId in the hashCode. This is because we support // the wildCard MIProcesses.UNKNOWN_PROCESS_ID which is equal to any other fId. // But we also need the hashCode of the wildCard to be the same // as the one of all other fIds, which is why we need a constant hashCode // See bug 336890 return baseHashCode(); } } /* * The data of a corresponding thread or process. */ @Immutable protected static class MIThreadDMData implements IThreadDMData { final String fName; final String fId; public MIThreadDMData(String name, String id) { fName = name; fId = id; } public String getId() { return fId; } public String getName() { return fName; } public boolean isDebuggerAttached() { return true; } } /** * Event indicating that an execution group (debugged process) has started. This event * implements the {@link IStartedMDEvent} from the IRunControl service. */ public static class ContainerStartedDMEvent extends AbstractDMEvent<IExecutionDMContext> implements IStartedDMEvent { public ContainerStartedDMEvent(IContainerDMContext context) { super(context); } } /** * Event indicating that an execution group is no longer being debugged. This event * implements the {@link IExitedMDEvent} from the IRunControl service. */ public static class ContainerExitedDMEvent extends AbstractDMEvent<IExecutionDMContext> implements IExitedDMEvent { public ContainerExitedDMEvent(IContainerDMContext context) { super(context); } } private ICommandControlService fCommandControl; private CommandCache fContainerCommandCache; private CommandFactory fCommandFactory; private IGDBBackend fGdbBackend; private static final String FAKE_THREAD_ID = "0"; //$NON-NLS-1$ // The unique id should be an empty string so that the views know not to display the fake id public static final String UNIQUE_GROUP_ID = ""; //$NON-NLS-1$ /** @since 4.0 */ public static final String UNKNOWN_PROCESS_ID = UNIQUE_GROUP_ID; public MIProcesses(DsfSession session) { super(session); } /** * This method initializes this service. * * @param requestMonitor * The request monitor indicating the operation is finished */ @Override public void initialize(final RequestMonitor requestMonitor) { super.initialize(new RequestMonitor(ImmediateExecutor.getInstance(), requestMonitor) { @Override protected void handleSuccess() { doInitialize(requestMonitor); } }); } /** * This method initializes this service after our superclass's initialize() * method succeeds. * * @param requestMonitor * The call-back object to notify when this service's * initialization is done. */ private void doInitialize(RequestMonitor requestMonitor) { // // Register this service. // register(new String[] { IProcesses.class.getName(), // MIProcesses.class.getName() }, // new Hashtable<String, String>()); fCommandControl = getServicesTracker().getService(ICommandControlService.class); BufferedCommandControl bufferedCommandControl = new BufferedCommandControl(fCommandControl, getExecutor(), 2); fCommandFactory = getServicesTracker().getService(IMICommandControl.class).getCommandFactory(); fGdbBackend = getServicesTracker().getService(IGDBBackend.class); // This cache stores the result of a command when received; also, this cache // is manipulated when receiving events. Currently, events are received after // three scheduling of the executor, while command results after only one. This // can cause problems because command results might be processed before an event // that actually arrived before the command result. // To solve this, we use a bufferedCommandControl that will delay the command // result by two scheduling of the executor. // See bug 280461 fContainerCommandCache = new CommandCache(getSession(), bufferedCommandControl); fContainerCommandCache.setContextAvailable(fCommandControl.getContext(), true); getSession().addServiceEventListener(this, null); requestMonitor.done(); } /** * This method shuts down this service. It unregisters the service, stops * receiving service events, and calls the superclass shutdown() method to * finish the shutdown process. * * @return void */ @Override public void shutdown(RequestMonitor requestMonitor) { // unregister(); getSession().removeServiceEventListener(this); super.shutdown(requestMonitor); } /** * @return The bundle context of the plug-in to which this service belongs. */ @Override protected BundleContext getBundleContext() { return GdbPlugin.getBundleContext(); } public IThreadDMContext createThreadContext(IProcessDMContext processDmc, String threadId) { return new MIThreadDMC(getSession().getId(), processDmc, threadId); } public IProcessDMContext createProcessContext(ICommandControlDMContext controlDmc, String pid) { return new MIProcessDMC(getSession().getId(), controlDmc, pid); } public IMIExecutionDMContext createExecutionContext(IContainerDMContext containerDmc, IThreadDMContext threadDmc, String threadId) { return new MIExecutionDMC(getSession().getId(), containerDmc, threadDmc, threadId); } public IMIContainerDMContext createContainerContext(IProcessDMContext processDmc, String groupId) { return new MIContainerDMC(getSession().getId(), processDmc, groupId); } public IMIContainerDMContext createContainerContextFromThreadId(ICommandControlDMContext controlDmc, String threadId) { return createContainerContextFromGroupId(controlDmc, UNIQUE_GROUP_ID); } /** @since 4.0 */ public IMIContainerDMContext createContainerContextFromGroupId(ICommandControlDMContext controlDmc, String groupId) { IProcessDMContext processDmc = createProcessContext(controlDmc, UNKNOWN_PROCESS_ID); return createContainerContext(processDmc, groupId); } public void getExecutionData(IThreadDMContext dmc, final DataRequestMonitor<IThreadDMData> rm) { if (dmc instanceof MIProcessDMC) { rm.setData(new MIThreadDMData("", ((MIProcessDMC)dmc).getProcId())); //$NON-NLS-1$ rm.done(); } else if (dmc instanceof MIThreadDMC) { final MIThreadDMC threadDmc = (MIThreadDMC)dmc; IProcessDMContext procDmc = DMContexts.getAncestorOfType(dmc, IProcessDMContext.class); getDebuggingContext(procDmc, new DataRequestMonitor<IDMContext>(getExecutor(), rm) { @Override protected void handleSuccess() { if (getData() instanceof IMIContainerDMContext) { IMIContainerDMContext contDmc = (IMIContainerDMContext)getData(); fContainerCommandCache.execute(fCommandFactory.createCLIInfoThreads(contDmc), new DataRequestMonitor<CLIInfoThreadsInfo>(getExecutor(), rm) { @Override protected void handleSuccess() { IThreadDMData threadData = null; for (CLIInfoThreadsInfo.ThreadInfo thread : getData().getThreadInfo()) { if (thread.getId().equals(threadDmc.getId())) { threadData = new MIThreadDMData(thread.getName(), thread.getOsId()); break; } } if (threadData != null) { rm.setData(threadData); } else { rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, "Could not get thread info", null)); //$NON-NLS-1$ } rm.done(); } }); } else { rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, "Invalid DMC type", null)); //$NON-NLS-1$ rm.done(); } } }); } } public void getDebuggingContext(IThreadDMContext dmc, DataRequestMonitor<IDMContext> rm) { if (dmc instanceof MIProcessDMC) { MIProcessDMC procDmc = (MIProcessDMC)dmc; rm.setData(createContainerContext(procDmc, procDmc.getProcId())); } else if (dmc instanceof MIThreadDMC) { MIThreadDMC threadDmc = (MIThreadDMC)dmc; IMIProcessDMContext procDmc = DMContexts.getAncestorOfType(dmc, IMIProcessDMContext.class); IMIContainerDMContext containerDmc = createContainerContext(procDmc, procDmc.getProcId()); rm.setData(createExecutionContext(containerDmc, threadDmc, threadDmc.getId())); } else { rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Invalid thread context.", null)); //$NON-NLS-1$ } rm.done(); } public void isDebuggerAttachSupported(IDMContext dmc, DataRequestMonitor<Boolean> rm) { rm.setData(false); rm.done(); } public void attachDebuggerToProcess(final IProcessDMContext procCtx, final DataRequestMonitor<IDMContext> rm) { if (procCtx instanceof IMIProcessDMContext) { ICommandControlDMContext controlDmc = DMContexts.getAncestorOfType(procCtx, ICommandControlDMContext.class); fCommandControl.queueCommand( fCommandFactory.createCLIAttach(controlDmc, ((IMIProcessDMContext)procCtx).getProcId()), new DataRequestMonitor<MIInfo>(getExecutor(), rm) { @Override protected void handleSuccess() { IMIContainerDMContext containerDmc = createContainerContext(procCtx, MIProcesses.UNIQUE_GROUP_ID); getSession().dispatchEvent(new ContainerStartedDMEvent(containerDmc), getProperties()); rm.setData(containerDmc); rm.done(); } }); } else { rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Invalid process context.", null)); //$NON-NLS-1$ rm.done(); } } public void canDetachDebuggerFromProcess(IDMContext dmc, DataRequestMonitor<Boolean> rm) { rm.setData(false); rm.done(); } public void detachDebuggerFromProcess(final IDMContext dmc, final RequestMonitor rm) { ICommandControlDMContext controlDmc = DMContexts.getAncestorOfType(dmc, ICommandControlDMContext.class); if (controlDmc != null) { IMIRunControl runControl = getServicesTracker().getService(IMIRunControl.class); if (runControl != null && !runControl.isTargetAcceptingCommands()) { fGdbBackend.interrupt(); } // This service version cannot use -target-detach because it didn't exist // in versions of GDB up to and including GDB 6.8 fCommandControl.queueCommand( fCommandFactory.createCLIDetach(controlDmc), new DataRequestMonitor<MIInfo>(getExecutor(), rm) { @Override protected void handleSuccess() { IContainerDMContext containerDmc = DMContexts.getAncestorOfType(dmc, IContainerDMContext.class); if (containerDmc != null) { getSession().dispatchEvent(new ContainerExitedDMEvent(containerDmc), getProperties()); } rm.done(); } }); } else { rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Invalid context.", null)); //$NON-NLS-1$ rm.done(); } } public void canTerminate(IThreadDMContext thread, DataRequestMonitor<Boolean> rm) { rm.setData(true); rm.done(); } public void isDebugNewProcessSupported(IDMContext dmc, DataRequestMonitor<Boolean> rm) { rm.setData(false); rm.done(); } public void debugNewProcess(IDMContext dmc, String file, Map<String, Object> attributes, DataRequestMonitor<IDMContext> rm) { rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED, "Not supported", null)); //$NON-NLS-1$ rm.done(); } public void getProcessesBeingDebugged(IDMContext dmc, final DataRequestMonitor<IDMContext[]> rm) { final IMIContainerDMContext containerDmc = DMContexts.getAncestorOfType(dmc, IMIContainerDMContext.class); if (containerDmc != null) { fContainerCommandCache.execute( fCommandFactory.createMIThreadListIds(containerDmc), new DataRequestMonitor<MIThreadListIdsInfo>(getExecutor(), rm) { @Override protected void handleSuccess() { rm.setData(makeExecutionDMCs(containerDmc, getData())); rm.done(); } }); } else { // This service version only handles a single process to debug, therefore, we can simply // create the context describing this process ourselves. ICommandControlDMContext controlDmc = DMContexts.getAncestorOfType(dmc, ICommandControlDMContext.class); IMIContainerDMContext newContainerDmc = createContainerContextFromGroupId(controlDmc, UNIQUE_GROUP_ID); rm.setData(new IContainerDMContext[] {newContainerDmc}); rm.done(); } } private IExecutionDMContext[] makeExecutionDMCs(IContainerDMContext containerDmc, MIThreadListIdsInfo info) { final IProcessDMContext procDmc = DMContexts.getAncestorOfType(containerDmc, IProcessDMContext.class); if (info.getStrThreadIds().length == 0) { // Main thread always exist even if it is not reported by GDB. // So create thread-id = 0 when no thread is reported. // This hack is necessary to prevent AbstractMIControl from issuing a thread-select // because it doesn't work if the application was not compiled with pthread. return new IMIExecutionDMContext[]{createExecutionContext(containerDmc, createThreadContext(procDmc, FAKE_THREAD_ID), FAKE_THREAD_ID)}; } else { IExecutionDMContext[] executionDmcs = new IMIExecutionDMContext[info.getStrThreadIds().length]; for (int i = 0; i < info.getStrThreadIds().length; i++) { String threadId = info.getStrThreadIds()[i]; executionDmcs[i] = createExecutionContext(containerDmc, createThreadContext(procDmc, threadId), threadId); } return executionDmcs; } } public void getRunningProcesses(IDMContext dmc, final DataRequestMonitor<IProcessDMContext[]> rm) { rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED, "Not supported", null)); //$NON-NLS-1$ rm.done(); } public void isRunNewProcessSupported(IDMContext dmc, DataRequestMonitor<Boolean> rm) { rm.setData(false); rm.done(); } public void runNewProcess(IDMContext dmc, String file, Map<String, Object> attributes, DataRequestMonitor<IProcessDMContext> rm) { rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED, "Not supported", null)); //$NON-NLS-1$ rm.done(); } public void terminate(IThreadDMContext thread, RequestMonitor rm) { rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED, "Not supported", null)); //$NON-NLS-1$ rm.done(); } /** * @nooverride This method is not intended to be re-implemented or extended by clients. * @noreference This method is not intended to be referenced by clients. */ @DsfServiceEventHandler public void eventDispatched(IResumedDMEvent e) { if (e instanceof IContainerResumedDMEvent) { // This will happen in all-stop mode fContainerCommandCache.setContextAvailable(e.getDMContext(), false); } else { // This will happen in non-stop mode // Keep target available for Container commands } } /** * @nooverride This method is not intended to be re-implemented or extended by clients. * @noreference This method is not intended to be referenced by clients. */ @DsfServiceEventHandler public void eventDispatched(ISuspendedDMEvent e) { // This assert may turn out to be overzealous. Refer to // https://bugs.eclipse.org/bugs/show_bug.cgi?id=280631#c26 assert e instanceof IContainerSuspendedDMEvent : "Unexpected type of suspended event: " + e.getClass().toString(); //$NON-NLS-1$ fContainerCommandCache.setContextAvailable(e.getDMContext(), true); // If user is debugging a gdb target that doesn't send thread // creation events, make sure we don't use cached thread // information. Reset the cache after every suspend. See bugzilla // 280631 try { if (fGdbBackend.getUpdateThreadListOnSuspend()) { fContainerCommandCache.reset(e.getDMContext()); } } catch (CoreException exc) {} } /** * @nooverride This method is not intended to be re-implemented or extended by clients. * @noreference This method is not intended to be referenced by clients. */ @DsfServiceEventHandler public void eventDispatched(IStartedDMEvent e) { fContainerCommandCache.reset(); } /** * @nooverride This method is not intended to be re-implemented or extended by clients. * @noreference This method is not intended to be referenced by clients. */ @DsfServiceEventHandler public void eventDispatched(IExitedDMEvent e) { fContainerCommandCache.reset(); } public void flushCache(IDMContext context) { fContainerCommandCache.reset(context); } }