/******************************************************************************* * Copyright (c) 2007, 2010 Wind River 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 - Initial API and implementation * Ericsson - Low-level breakpoints integration * Nokia - refactored to work for both GDB and EDC. Nov. 2009. *******************************************************************************/ package org.eclipse.cdt.dsf.debug.service; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.RejectedExecutionException; import org.eclipse.cdt.dsf.concurrent.CountingRequestMonitor; 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.RequestMonitor; import org.eclipse.cdt.dsf.concurrent.ThreadSafeAndProhibitedFromDsfExecutor; import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointDMContext; import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointsTargetDMContext; import org.eclipse.cdt.dsf.internal.DsfPlugin; import org.eclipse.cdt.dsf.service.AbstractDsfService; import org.eclipse.cdt.dsf.service.DsfSession; import org.eclipse.core.resources.IMarkerDelta; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Plugin; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.IBreakpointManager; import org.eclipse.debug.core.IBreakpointsListener; import org.eclipse.debug.core.model.IBreakpoint; import org.osgi.framework.BundleContext; /** /** * Breakpoints mediator is a DSF service which synchronizes breakpoints in the * IDE and breakpoints in the debugger. The IDE breakpoints are managed by the * {@link IBreakpointManager} while the debugger breakpoints are accessed * through the {@link IBreakpoints} service. * <p> * This class is not intended to be extended by clients. Instead clients should * implement the {@link IBreakpointAttributeTranslator2} interface which is used * to translate breakpoint attributes between the IDE and debugger breakpoints. * <p> * Note: This breakpoint mediator is a second generation implementation that * succeeds {@link BreakpointsMediator}. This new implementation includes * the following additional features: * <ul> * <li> support for multiple target breakpoints for each IDE breakpoint, </li> * <li> support for retrieving the mapping between IDE breakpoints and * debugger breakpoints,</li> * <li> support for updating IDE breakpoint status based on full target * breakpoint data. </li> * </ul> * * @see IBreakpointAttributeTranslator2 * @see BreakpointsMediator * * @since 2.1 */ public class BreakpointsMediator2 extends AbstractDsfService implements IBreakpointsListener { public enum BreakpointEventType {ADDED, REMOVED, MODIFIED}; /** * The attribute translator that this service will use to map the platform * breakpoint attributes to the corresponding target attributes, and vice * versa. */ private IBreakpointAttributeTranslator2 fAttributeTranslator2; /** * DSF Debug service for creating breakpoints. */ IBreakpoints fBreakpointsService; /** * Platform breakpoint manager */ IBreakpointManager fBreakpointManager; /** * Object describing the information about a single target breakpoint * corresponding to specific platform breakpoint and breakpoint target * context. */ public interface ITargetBreakpointInfo { /** * Returns the breakpoint attributes as returned by the attribute translator. */ public Map<String, Object> getAttributes(); /** * Returns the target breakpoint context. Returns <code>null</code> if the * breakpoint failed to install on target. */ public IBreakpointDMContext getTargetBreakpoint(); /** * Returns the status result of the last breakpoint operation (install/remove). */ public IStatus getStatus(); } private static class TargetBP implements ITargetBreakpointInfo { private Map<String, Object> fAttributes; private IBreakpointDMContext fTargetBPContext; private IStatus fStatus; public TargetBP(Map<String, Object> attrs) { fAttributes = attrs; } public Map<String, Object> getAttributes() { return fAttributes; } public IBreakpointDMContext getTargetBreakpoint() { return fTargetBPContext; } public IStatus getStatus() { return fStatus; } public void setTargetBreakpoint(IBreakpointDMContext fTargetBPContext) { this.fTargetBPContext = fTargetBPContext; } public void setStatus(IStatus status) { this.fStatus = status; } } private class PlatformBreakpointInfo { IBreakpoint breakpoint; boolean enabled; // All attributes available from UI, including standard and extended ones. Map<String, Object> attributes; public PlatformBreakpointInfo(IBreakpoint bp, boolean enabled, Map<String, Object> attributes) { super(); breakpoint = bp; this.enabled = enabled; this.attributes = attributes; } } /////////////////////////////////////////////////////////////////////////// // Breakpoints tracking /////////////////////////////////////////////////////////////////////////// /** * Holds the set of platform breakpoints with their breakpoint information * structures, per context (i.e. each platform breakpoint is * replicated for each execution context). * - Context entry added/removed on start/stopTrackingBreakpoints() * - Augmented on breakpointAdded() * - Modified on breakpointChanged() * - Diminished on breakpointRemoved() */ private Map<IBreakpointsTargetDMContext, Map<IBreakpoint, List<TargetBP>>> fPlatformBPs = new HashMap<IBreakpointsTargetDMContext, Map<IBreakpoint, List<TargetBP>>>(); /** * BreakpointsTargetDMContext's that are being removed from {@link #fPlatformBPs}. * See where this is used for more. */ private List<IBreakpointsTargetDMContext> fBPTargetDMCsBeingRemoved = new ArrayList<IBreakpoints.IBreakpointsTargetDMContext>(); /** * Mapping of platform breakpoints to all their attributes (standard ones and * extended ones) from UI. This will be used to check what attributes have * changed for a breakpoint when the breakpoint is changed. The map is <br> * 1. augmented in breakpointsAdded(); <br> * 2. updated in breakpointsChanged(); <br> * 3. diminished in breakpointsRemoved(); */ private Map<IBreakpoint, Map<String, Object>> fBreakpointAttributes = new HashMap<IBreakpoint, Map<String, Object>>(); /** * Hold info about a breakpoint events (added, removed, changed) for later * handling. */ private static class PendingEventInfo { PendingEventInfo(BreakpointEventType eventType, PlatformBreakpointInfo bpInfo, Collection<IBreakpointsTargetDMContext> bpsTargetDmc, RequestMonitor rm) { fEventType = eventType; fBPInfo = bpInfo; fBPTargetContexts = bpsTargetDmc; fRequestMonitor = rm; fAttributeDelta = null; } PendingEventInfo(BreakpointEventType eventType, Collection<IBreakpointsTargetDMContext> updateContexts, Map<String, Object> attrDelta) { fEventType = eventType; fBPTargetContexts = updateContexts; fAttributeDelta = attrDelta; fRequestMonitor = null; fBPInfo = null; } PlatformBreakpointInfo fBPInfo; RequestMonitor fRequestMonitor; BreakpointEventType fEventType; Collection<IBreakpointsTargetDMContext> fBPTargetContexts; Map<String, Object> fAttributeDelta; // for change event only } /** * Due to the very asynchronous nature of DSF, a new breakpoint request can * pop up at any time before an ongoing one is completed. The following set * is used to store requests until the ongoing operation completes. */ private Set<IBreakpoint> fRunningEvents = new HashSet<IBreakpoint>(); private Map<IBreakpoint, LinkedList<PendingEventInfo>> fPendingEvents = new HashMap<IBreakpoint, LinkedList<PendingEventInfo>>(); /////////////////////////////////////////////////////////////////////////// // AbstractDsfService /////////////////////////////////////////////////////////////////////////// /** * The service constructor * * @param session * @param debugModelId */ public BreakpointsMediator2(DsfSession session, IBreakpointAttributeTranslator2 attributeTranslator) { super(session); fAttributeTranslator2 = attributeTranslator; } @Override public void initialize(final RequestMonitor rm) { // - Collect references for the services we interact with // - Register to interesting events // - Obtain the list of platform breakpoints // - Register the service for interested parties super.initialize( new RequestMonitor(getExecutor(), rm) { @Override protected void handleSuccess() { doInitialize(rm); }}); } /** * Asynchronous service initialization * * @param requestMonitor */ private void doInitialize(RequestMonitor rm) { // Get the services references fBreakpointsService = getServicesTracker().getService(IBreakpoints.class); fBreakpointManager = DebugPlugin.getDefault().getBreakpointManager(); fAttributeTranslator2.initialize(this); // Register to the useful events fBreakpointManager.addBreakpointListener(this); // Register this service register(new String[] { BreakpointsMediator2.class.getName() }, new Hashtable<String, String>()); rm.done(); } @Override public void shutdown(final RequestMonitor rm) { // - Un-register the service // - Stop listening to events // - Remove the breakpoints installed by this service // // Since we are shutting down, there is no overwhelming need // to keep the maps coherent... // Stop accepting requests and events unregister(); fBreakpointManager.removeBreakpointListener(this); fAttributeTranslator2.dispose(); // Cleanup the breakpoints that are still installed by the service. // Use a counting monitor which will call mom to complete the shutdown // after the breakpoints are un-installed (successfully or not). CountingRequestMonitor countingRm = new CountingRequestMonitor(getExecutor(), rm) { @Override protected void handleCompleted() { BreakpointsMediator2.super.shutdown(rm); } }; // We have to make a copy of the fPlatformBPs keys because uninstallBreakpoints() // modifies the map as it walks through it. List<IBreakpointsTargetDMContext> platformBPKeysCopy = new ArrayList<IBreakpointsTargetDMContext>(fPlatformBPs.size()); platformBPKeysCopy.addAll(0, fPlatformBPs.keySet()); for (IBreakpointsTargetDMContext dmc : platformBPKeysCopy) { stopTrackingBreakpoints(dmc, countingRm); } countingRm.setDoneCount(platformBPKeysCopy.size()); } @Override protected BundleContext getBundleContext() { return DsfPlugin.getBundleContext(); } protected String getPluginID() { return DsfPlugin.PLUGIN_ID; } protected Plugin getPlugin() { return DsfPlugin.getDefault(); } /** * Install and begin tracking breakpoints for given context. The service * will keep installing new breakpoints that appear in the IDE for this * context until {@link #stopTrackingBreakpoints} is called for that * context. * @param dmc Context to start tracking breakpoints for. * @param rm Completion callback. */ public void startTrackingBreakpoints(final IBreakpointsTargetDMContext dmc, final RequestMonitor rm) { // - Augment the maps with the new execution context // - Install the platform breakpoints on the selected target // Make sure a mapping for this execution context does not already exist Map<IBreakpoint, List<TargetBP>> platformBPs = fPlatformBPs.get(dmc); if (platformBPs != null) { rm.setStatus(new Status(IStatus.ERROR, getPluginID(), INTERNAL_ERROR, "Context already initialized", null)); //$NON-NLS-1$ rm.done(); return; } // Create entries in the breakpoint tables for the new context. These entries should only // be removed when this service stops tracking breakpoints for the given context. fPlatformBPs.put(dmc, new HashMap<IBreakpoint, List<TargetBP>>()); // Install the platform breakpoints (stored in fPlatformBPs) on the target. // We need to use a background thread for this operation because we are // accessing the resources system to retrieve the breakpoint attributes. // Accessing the resources system potentially requires using global locks. // Also we will be calling some IBreakpointAttributeTranslator2 methods // that are prohibited from being called on the session executor thread. new Job("Install initial breakpoint list.") { //$NON-NLS-1$ { setSystem(true); } // Get the stored breakpoints from the platform BreakpointManager // and install them on the target @Override protected IStatus run(IProgressMonitor monitor) { doBreakpointsAdded(DebugPlugin.getDefault().getBreakpointManager().getBreakpoints(), dmc, rm); return Status.OK_STATUS; } }.schedule(); } /** * Remove and stop installing breakpoints for the given breakpoints target context. * @param dmc Context to stop tracking breakpoints for. * @param rm Completion callback. */ public void stopTrackingBreakpoints(final IBreakpointsTargetDMContext dmc, final RequestMonitor rm) { // - Remove the target breakpoints for the given DMC // - Remove the given DMC from the internal maps. // Map<IBreakpoint, List<TargetBP>> platformBPs = fPlatformBPs.get(dmc); if (platformBPs == null) { rm.setStatus(new Status(IStatus.INFO /* NOT error */, getPluginID(), INTERNAL_ERROR, "Breakpoints not installed for given context", null)); //$NON-NLS-1$ rm.done(); return; } if (platformBPs.size() == 0) { fPlatformBPs.remove(dmc); // dmc tracked but no bps installed for it. rm.done(); return; } // The stopTrackingBreakpoints() may be called twice for the same DMC // on debugger termination (one on process death and one on debugger shutdown). // This is to prevent double killing. if (fBPTargetDMCsBeingRemoved.contains(dmc)) { // "stop" is already underway rm.done(); return; } fBPTargetDMCsBeingRemoved.add(dmc); // Just remove the IBreakpoints installed for the "dmc". final IBreakpoint[] bps = platformBPs.keySet().toArray(new IBreakpoint[platformBPs.size()]); new Job("Uninstall target breakpoints list.") { //$NON-NLS-1$ { setSystem(true); } @Override protected IStatus run(IProgressMonitor monitor) { doBreakpointsRemoved(bps, dmc, new RequestMonitor(getExecutor(), rm){ @Override protected void handleCompleted() { // Regardless of success or failure in removing the breakpoints, // we should stop tracking breakpoints for the "dmc" by removing it // from the map. fPlatformBPs.remove(dmc); fBPTargetDMCsBeingRemoved.remove(dmc); super.handleCompleted(); }}); return Status.OK_STATUS; } }.schedule(); } /** * Find target breakpoints installed in the given context that are resolved * from the given platform breakpoint. * * @param dmc - context * @param platformBp - platform breakpoint * @return array of target breakpoints. */ public ITargetBreakpointInfo[] getTargetBreakpoints(IBreakpointsTargetDMContext dmc, IBreakpoint platformBp) { assert getExecutor().isInExecutorThread(); Map<IBreakpoint, List<TargetBP>> platformBPs = fPlatformBPs.get(dmc); if (platformBPs != null) { List<TargetBP> bpInfo = platformBPs.get(platformBp); if (bpInfo != null) { return bpInfo.toArray(new ITargetBreakpointInfo[bpInfo.size()]); } } return null; } /** * Find the platform breakpoint that's mapped to the given target breakpoint. * * @param dmc - context of the target breakpoint, can be null. * @param bp - target breakpoint * @return platform breakpoint. null if not found. */ public IBreakpoint getPlatformBreakpoint(IBreakpointsTargetDMContext dmc, IBreakpointDMContext bp) { assert getExecutor().isInExecutorThread(); for (IBreakpointsTargetDMContext bpContext : fPlatformBPs.keySet()) { if (dmc != null && !dmc.equals(bpContext)) continue; Map<IBreakpoint, List<TargetBP>> platformBPs = fPlatformBPs.get(bpContext); if (platformBPs != null && platformBPs.size() > 0) { for(Map.Entry<IBreakpoint, List<TargetBP>> e: platformBPs.entrySet()) { // Stop at the first occurrence for (TargetBP tbp : e.getValue()) if(tbp.getTargetBreakpoint().equals(bp)) return e.getKey(); } } } return null; } /////////////////////////////////////////////////////////////////////////// // Back-end interface functions /////////////////////////////////////////////////////////////////////////// /** * Install a new platform breakpoint on the back-end. A platform breakpoint * can resolve into multiple back-end breakpoints, e.g. when threads are taken * into account. * * @param dmc * @param breakpoint * @param attrsList - list of attribute map, each mapping to a potential target BP. * @param rm */ private void installBreakpoint(IBreakpointsTargetDMContext dmc, final IBreakpoint breakpoint, final List<Map<String, Object>> attrsList, final DataRequestMonitor<List<TargetBP>> rm) { // Retrieve the set of breakpoints for this context final Map<IBreakpoint, List<TargetBP>> platformBPs = fPlatformBPs.get(dmc); assert platformBPs != null; // Ensure the breakpoint is not already installed assert !platformBPs.containsKey(breakpoint); final ArrayList<TargetBP> targetBPsAttempted = new ArrayList<TargetBP>(attrsList.size()); for (int i = 0; i < attrsList.size(); i++) { targetBPsAttempted.add(new TargetBP(attrsList.get(i))); } final ArrayList<TargetBP> targetBPsInstalled = new ArrayList<TargetBP>(attrsList.size()); // Update the breakpoint status when all back-end breakpoints have been installed final CountingRequestMonitor installRM = new CountingRequestMonitor(getExecutor(), rm) { @Override protected void handleCompleted() { // Store successful targetBPs with the platform breakpoint if (targetBPsInstalled.size() > 0) platformBPs.put(breakpoint, targetBPsInstalled); // Store all targetBPs, success or failure, in the rm. rm.setData(targetBPsAttempted); rm.done(); } }; // A back-end breakpoint needs to be installed for each specified attributes map. installRM.setDoneCount(attrsList.size()); // Install the back-end breakpoint(s) for (int _i = 0; _i < attrsList.size(); _i++) { final int i = _i; fBreakpointsService.insertBreakpoint( dmc, attrsList.get(i), new DataRequestMonitor<IBreakpointDMContext>(getExecutor(), installRM) { @Override protected void handleCompleted() { TargetBP targetBP = targetBPsAttempted.get(i); if (isSuccess()) { // Add the breakpoint back-end mapping targetBP.setTargetBreakpoint(getData()); targetBPsInstalled.add(targetBP); } targetBP.setStatus(getStatus()); installRM.done(); } }); } } /** * Un-install an individual breakpoint on the back-end. For one platform * breakpoint, there could be multiple corresponding back-end breakpoints. * * @param dmc * the context for which to remove the breakpoint. * @param breakpoint * @param drm * contains list of Target breakpoints that are removed * regardless of success or failure in the removal. */ private void uninstallBreakpoint(final IBreakpointsTargetDMContext dmc, final IBreakpoint breakpoint, final DataRequestMonitor<List<TargetBP>> drm) { // Remove the back-end breakpoints final Map<IBreakpoint, List<TargetBP>> platformBPs = fPlatformBPs.get(dmc); if (platformBPs == null) { drm.setStatus(new Status(IStatus.ERROR, getPluginID(), INVALID_HANDLE, "Invalid breakpoint", null)); //$NON-NLS-1$ drm.done(); return; } final List<TargetBP> bpList = platformBPs.get(breakpoint); assert bpList != null; // Only try to remove those targetBPs that are successfully installed. // Remove completion monitor final CountingRequestMonitor countingRm = new CountingRequestMonitor(getExecutor(), drm) { @Override protected void handleCompleted() { platformBPs.remove(breakpoint); // Complete the request monitor. drm.setData(bpList); drm.done(); } }; int count = 0; for (int i = 0; i < bpList.size(); i++) { final TargetBP bp = bpList.get(i); if (bp.getTargetBreakpoint() != null) { fBreakpointsService.removeBreakpoint( bp.getTargetBreakpoint(), new RequestMonitor(getExecutor(), countingRm) { @Override protected void handleCompleted() { // Remember result of the removal, success or failure. bp.setStatus(getStatus()); if (isSuccess()) { bp.setTargetBreakpoint(null); } countingRm.done(); } }); count++; } else { bp.setStatus(Status.OK_STATUS); } } countingRm.setDoneCount(count); } /////////////////////////////////////////////////////////////////////////// // IBreakpointManagerListener implementation /////////////////////////////////////////////////////////////////////////// /** * @noreference This method is not intended to be referenced by clients. */ @ThreadSafeAndProhibitedFromDsfExecutor("getExecutor()") public void breakpointsAdded(final IBreakpoint[] bps) { doBreakpointsAdded(bps, null, null); } protected void doBreakpointsAdded(final IBreakpoint[] bps, final IBreakpointsTargetDMContext bpsTargetDmc, final RequestMonitor rm) { // Collect attributes (which will access system resource) // in non DSF dispatch thread. // final PlatformBreakpointInfo[] bpsInfo = collectBreakpointsInfo(bps); // Nothing to do if (bpsInfo.length == 0) { if (rm != null) { rm.done(); } return; } try { getExecutor().execute(new DsfRunnable() { public void run() { Collection<IBreakpointsTargetDMContext> dmcs = new ArrayList<IBreakpointsTargetDMContext>(); if (bpsTargetDmc == null) dmcs.addAll(fPlatformBPs.keySet()); else dmcs.add(bpsTargetDmc); doBreakpointsAddedInExecutor(bpsInfo, dmcs, rm); } }); } catch (RejectedExecutionException e) { IStatus status = new Status(IStatus.ERROR, getPluginID(), IDsfStatusConstants.INTERNAL_ERROR, "Request for monitor: '" + toString() + "' resulted in a rejected execution exception.", e);//$NON-NLS-1$ //$NON-NLS-2$ if (rm != null) { rm.setStatus(status); rm.done(); } else { getPlugin().getLog().log(status); } } } /** * Collect breakpoint info. This method must not be called in DSF dispatch thread. * @param bps * @return */ private PlatformBreakpointInfo[] collectBreakpointsInfo(IBreakpoint[] bps) { List<PlatformBreakpointInfo> bpsInfo = new ArrayList<PlatformBreakpointInfo>(bps.length); for (IBreakpoint bp : bps) { if (bp.getMarker() == null) continue; if (fAttributeTranslator2.supportsBreakpoint(bp)) { try { Map<String, Object> attrs = fAttributeTranslator2.getAllBreakpointAttributes(bp, fBreakpointManager.isEnabled()); boolean enabled = bp.isEnabled() && fBreakpointManager.isEnabled(); bpsInfo.add(new PlatformBreakpointInfo(bp, enabled, attrs)); } catch (CoreException e) { getPlugin().getLog().log(e.getStatus()); } } } return bpsInfo.toArray(new PlatformBreakpointInfo[bpsInfo.size()]); } private void doBreakpointsAddedInExecutor(PlatformBreakpointInfo[] bpsInfo, Collection<IBreakpointsTargetDMContext> bpTargetDMCs, final RequestMonitor rm) { final Map<IBreakpoint, Map<IBreakpointsTargetDMContext, ITargetBreakpointInfo[]>> eventBPs = new HashMap<IBreakpoint, Map<IBreakpointsTargetDMContext, ITargetBreakpointInfo[]>>(bpsInfo.length, 1); CountingRequestMonitor processPendingCountingRm = new CountingRequestMonitor(getExecutor(), rm) { @Override protected void handleCompleted() { processPendingRequests(); fireUpdateBreakpointsStatus(eventBPs, BreakpointEventType.ADDED); if (rm != null) // don't call this if "rm" is null as this will // log errors if any and pack Eclipse error // log view with errors meaningless to user. super.handleCompleted(); } }; int processPendingCountingRmCount = 0; for (final PlatformBreakpointInfo bpinfo : bpsInfo) { final Map<IBreakpointsTargetDMContext, ITargetBreakpointInfo[]> targetBPs = new HashMap<IBreakpointsTargetDMContext, ITargetBreakpointInfo[]>(fPlatformBPs.size(), 1); eventBPs.put(bpinfo.breakpoint, targetBPs); // Remember the new attributes of the bp in our global buffer, // even if we cannot or fail to install the bp. fBreakpointAttributes.put(bpinfo.breakpoint, bpinfo.attributes); if (fRunningEvents.contains(bpinfo.breakpoint)) { PendingEventInfo pendingEvent = new PendingEventInfo(BreakpointEventType.ADDED, bpinfo, bpTargetDMCs, processPendingCountingRm); processPendingCountingRmCount++; updatePendingRequest(bpinfo.breakpoint, pendingEvent); continue; } processPendingCountingRmCount++; // Mark the breakpoint as being updated and go fRunningEvents.add(bpinfo.breakpoint); final CountingRequestMonitor bpTargetsCountingRm = new CountingRequestMonitor(getExecutor(), processPendingCountingRm) { @Override protected void handleCompleted() { // Indicate that the running event has completed fRunningEvents.remove(bpinfo.breakpoint); super.handleCompleted(); } }; int bpTargetsCountingRmCount = 0; // Install the breakpoint in all the execution contexts for (final IBreakpointsTargetDMContext dmc : bpTargetDMCs) { // Now ask lower level to set the bp. // // if the breakpoint is disabled, ask back-end if it can set (and manage) // disabled breakpoint. If not, just bail out. // if (! bpinfo.enabled) { Map<String, Object> attr = new HashMap<String, Object>(1); attr.put(IBreakpoint.ENABLED, Boolean.FALSE); Map<String, Object> targetEnablementAttr = fAttributeTranslator2.convertAttributes(attr); if (! fAttributeTranslator2.canUpdateAttributes(bpinfo.breakpoint, dmc, targetEnablementAttr)) { // bail out. Continue with the next dmc & breakpoint. continue; } } // Now do the real work. // fAttributeTranslator2.resolveBreakpoint(dmc, bpinfo.breakpoint, bpinfo.attributes, new DataRequestMonitor<List<Map<String,Object>>>(getExecutor(), bpTargetsCountingRm){ @Override protected void handleSuccess() { installBreakpoint( dmc, bpinfo.breakpoint, getData(), new DataRequestMonitor<List<TargetBP>>(getExecutor(), bpTargetsCountingRm) { @Override protected void handleSuccess() { targetBPs.put(dmc, getData().toArray(new ITargetBreakpointInfo[getData().size()])); super.handleSuccess(); }; }); }}); bpTargetsCountingRmCount++; } bpTargetsCountingRm.setDoneCount(bpTargetsCountingRmCount); } processPendingCountingRm.setDoneCount(processPendingCountingRmCount); } /** * @noreference This method is not intended to be referenced by clients. */ @ThreadSafeAndProhibitedFromDsfExecutor("getExecutor()") public void breakpointsChanged(IBreakpoint[] bps, IMarkerDelta[] deltas) { if (fAttributeTranslator2 == null) return; final PlatformBreakpointInfo[] bpsInfo = collectBreakpointsInfo(bps); if (bpsInfo.length == 0) return; // nothing to do try { getExecutor().execute( new DsfRunnable() { public void run() { Map<String, Object> tmp = new HashMap<String, Object>(1); tmp.put(IBreakpoint.ENABLED, true); final String targetEnablementKey = fAttributeTranslator2.convertAttributes(tmp).keySet().iterator().next(); for (PlatformBreakpointInfo bpinfo : bpsInfo) { /* * We cannot depend on "deltas" for attribute change. * For instance, delta can be null when extended * attributes (e.g. breakpoint thread filter for GDB) * are changed. */ Map<String, Object> newAttrs = bpinfo.attributes; Map<String, Object> oldAttrs = fBreakpointAttributes.get(bpinfo.breakpoint); // remember the new attributes. fBreakpointAttributes.put(bpinfo.breakpoint, newAttrs); if (oldAttrs == null) continue; final Map<String, Object> attrDelta = getAttributesDelta(oldAttrs, newAttrs); if (attrDelta.size() == 0) continue; final List<IBreakpointsTargetDMContext> reinstallContexts = new ArrayList<IBreakpointsTargetDMContext>(); List<IBreakpointsTargetDMContext> updateContexts = new ArrayList<IBreakpointsTargetDMContext>(); // Now change the breakpoint for each known context. // for (final IBreakpointsTargetDMContext btContext : fPlatformBPs.keySet()) { if (! fAttributeTranslator2.canUpdateAttributes(bpinfo.breakpoint, btContext, attrDelta)) { // backend cannot handle at least one of the platform BP attribute change, // we'll re-install the bp. reinstallContexts.add(btContext); } else { // Backend claims it can handle the attributes change, let it do it. updateContexts.add(btContext); } } final PlatformBreakpointInfo[] oneBPInfo = new PlatformBreakpointInfo[] {bpinfo}; IBreakpoint[] oneBP = new IBreakpoint[] {bpinfo.breakpoint}; if (reinstallContexts.size() > 0) { // Check if it's only enablement change (user click enable/disable // button or "Skip all breakpoints" button), which is common operation. // if (attrDelta.size() == 1 && attrDelta.containsKey(targetEnablementKey)) { // only enablement changed. if (bpinfo.enabled) { // change from disable to enable. Install the bp. doBreakpointsAddedInExecutor(oneBPInfo, reinstallContexts, null); } else { // change from enable to disable. Remove the bp. doBreakpointsRemovedInExecutor(oneBP, reinstallContexts, null); } } else { doBreakpointsRemovedInExecutor(oneBP, reinstallContexts, new RequestMonitor(getExecutor(), null) { // What should we do if removal of some or all targetBP fails ? // Go on with the installation of new targetBPs and let clients (i.e. AttributeTranslators) // handle the errors. @Override protected void handleCompleted() { doBreakpointsAddedInExecutor(oneBPInfo, reinstallContexts, null); }}); } } if (updateContexts.size() > 0) modifyTargetBreakpoints(bpinfo.breakpoint, updateContexts, attrDelta); } } }); } catch (RejectedExecutionException e) { getPlugin().getLog().log(new Status(IStatus.ERROR, getPluginID(), IDsfStatusConstants.INTERNAL_ERROR, "Request for monitor: '" + toString() + "' resulted in a rejected execution exception.", e)); //$NON-NLS-1$ //$NON-NLS-2$ } } /** * For the given platform BP, ask the backend to modify all its target BPs * with the given attribute change. <br> * This must be called in DSF executor thread. * * @param bp * @param updateContexts * target contexts in which to do the modification. * @param targetAttrDelta * target-recognizable attribute(s) with new values. */ private void modifyTargetBreakpoints(final IBreakpoint bp, Collection<IBreakpointsTargetDMContext> updateContexts, Map<String, Object> targetAttrDelta) { // If the breakpoint is currently being updated, queue the request and exit if (fRunningEvents.contains(bp)) { PendingEventInfo pendingEvent = new PendingEventInfo(BreakpointEventType.MODIFIED, updateContexts, targetAttrDelta); updatePendingRequest(bp, pendingEvent); return; } CountingRequestMonitor modifyTargetBPCRM = new CountingRequestMonitor(getExecutor(), null) { @Override protected void handleCompleted() { fRunningEvents.remove(bp); }}; int targetBPCount = 0; fRunningEvents.add(bp); for (IBreakpointsTargetDMContext context : updateContexts) { List<TargetBP> targetBPs = fPlatformBPs.get(context).get(bp); if (targetBPs != null) { for (TargetBP tbp : targetBPs) { // this must be an installed breakpoint. assert (tbp.getTargetBreakpoint() != null); targetBPCount++; fBreakpointsService.updateBreakpoint(tbp.getTargetBreakpoint(), targetAttrDelta, modifyTargetBPCRM); } } } modifyTargetBPCRM.setDoneCount(targetBPCount); } /** * @noreference This method is not intended to be referenced by clients. */ @ThreadSafeAndProhibitedFromDsfExecutor("getExecutor()") public void breakpointsRemoved(final IBreakpoint[] bps, IMarkerDelta delta[]) { getExecutor().execute(new DsfRunnable() { public void run() { for (IBreakpoint bp : bps) fBreakpointAttributes.remove(bp); } }); doBreakpointsRemoved(bps, null, null); } private void doBreakpointsRemoved(final IBreakpoint[] bps, final IBreakpointsTargetDMContext bpsTargetDmc, final RequestMonitor rm) { final List<IBreakpoint> bpCandidates = new ArrayList<IBreakpoint>(); for (int i = 0; i < bps.length; i++) { IBreakpoint bp = bps[i]; if (fAttributeTranslator2.supportsBreakpoint(bp)) { bpCandidates.add(bp); } } if (bpCandidates.isEmpty()) { // nothing to do if (rm != null) rm.done(); return; } try { getExecutor().execute(new DsfRunnable() { public void run() { Collection<IBreakpointsTargetDMContext> contexts = new ArrayList<IBreakpointsTargetDMContext>(); if (bpsTargetDmc == null) contexts.addAll(fPlatformBPs.keySet()); else contexts.add(bpsTargetDmc); doBreakpointsRemovedInExecutor(bpCandidates.toArray(new IBreakpoint[bpCandidates.size()]), contexts, rm); } }); } catch (RejectedExecutionException e) { IStatus status = new Status(IStatus.ERROR, getPluginID(), IDsfStatusConstants.INTERNAL_ERROR, "Request for monitor: '" + toString() + "' resulted in a rejected execution exception.", e);//$NON-NLS-1$ //$NON-NLS-2$ if (rm != null) { rm.setStatus(status); rm.done(); } else { getPlugin().getLog().log(status); } } } private void doBreakpointsRemovedInExecutor(IBreakpoint[] bpCandidates, Collection<IBreakpointsTargetDMContext> targetContexts, final RequestMonitor rm) { final Map<IBreakpoint, Map<IBreakpointsTargetDMContext, ITargetBreakpointInfo[]>> eventBPs = new HashMap<IBreakpoint, Map<IBreakpointsTargetDMContext, ITargetBreakpointInfo[]>>(bpCandidates.length, 1); CountingRequestMonitor processPendingCountingRm = new CountingRequestMonitor(getExecutor(), rm) { @Override protected void handleCompleted() { processPendingRequests(); fireUpdateBreakpointsStatus(eventBPs, BreakpointEventType.REMOVED); if (rm != null) // don't call this if "rm" is null as this will // log errors if any and pack Eclipse error // log view with errors meaningless to user. super.handleCompleted(); } }; int processPendingCountingRmCount = 0; for (final IBreakpoint breakpoint : bpCandidates) { // If the breakpoint is currently being updated, queue the request and exit if (fRunningEvents.contains(breakpoint)) { PendingEventInfo pendingEvent = new PendingEventInfo(BreakpointEventType.REMOVED, null, targetContexts, processPendingCountingRm); processPendingCountingRmCount++; updatePendingRequest(breakpoint, pendingEvent); continue; // handle next breakpoint } processPendingCountingRmCount++; final Map<IBreakpointsTargetDMContext, ITargetBreakpointInfo[]> targetBPs = new HashMap<IBreakpointsTargetDMContext, ITargetBreakpointInfo[]>(fPlatformBPs.size(), 1); eventBPs.put(breakpoint, targetBPs); CountingRequestMonitor bpTargetsCountingRM = new CountingRequestMonitor(getExecutor(), processPendingCountingRm) { @Override protected void handleCompleted() { // Indicate that the running event has completed fRunningEvents.remove(breakpoint); super.handleCompleted(); } }; int bpTargetsCoutingRMCount = 0; // Mark the breakpoint as being updated and go fRunningEvents.add(breakpoint); // Remove the breakpoint in all the execution contexts for (final IBreakpointsTargetDMContext dmc : targetContexts) { if (fPlatformBPs.get(dmc).containsKey(breakpoint)) { // there are targetBPs installed // now do time-consuming part of the work. uninstallBreakpoint( dmc, breakpoint, new DataRequestMonitor<List<TargetBP>>(getExecutor(), bpTargetsCountingRM) { @Override protected void handleSuccess() { targetBPs.put(dmc, getData().toArray(new ITargetBreakpointInfo[getData().size()])); super.handleSuccess(); }; }); bpTargetsCoutingRMCount++; } else { // Breakpoint not installed for given context, do nothing. } } bpTargetsCountingRM.setDoneCount(bpTargetsCoutingRMCount); } processPendingCountingRm.setDoneCount(processPendingCountingRmCount); } private void updatePendingRequest(IBreakpoint breakpoint, PendingEventInfo pendingEvent) { LinkedList<PendingEventInfo> pendingEventsList = fPendingEvents.get(breakpoint); if (pendingEventsList == null) { pendingEventsList = new LinkedList<PendingEventInfo>(); fPendingEvents.put(breakpoint, pendingEventsList); } if (pendingEventsList.size() > 0 && pendingEventsList.getLast().fEventType == BreakpointEventType.MODIFIED) { pendingEventsList.removeLast(); } pendingEventsList.add(pendingEvent); } private void processPendingRequests() { /* * This will process only first pending request for each breakpoint, * whose RequestMonitor (see "processPendingCountingRm" in such methods as * doBreakpointsRemovedInExecutor()) will invoke this method again. */ if (fPendingEvents.isEmpty()) return; // Nothing to do // Make a copy to avoid ConcurrentModificationException // as we are deleting element in the loop. Set<IBreakpoint> bpsInPendingEvents = new HashSet<IBreakpoint>(fPendingEvents.keySet()); for (IBreakpoint bp : bpsInPendingEvents) { if (! fRunningEvents.contains(bp)) { LinkedList<PendingEventInfo> eventInfoList = fPendingEvents.get(bp); // Process the first pending request for this breakpoint PendingEventInfo eventInfo = eventInfoList.removeFirst(); if (eventInfoList.isEmpty()) fPendingEvents.remove(bp); switch (eventInfo.fEventType) { case ADDED: doBreakpointsAddedInExecutor(new PlatformBreakpointInfo[] {eventInfo.fBPInfo}, eventInfo.fBPTargetContexts, eventInfo.fRequestMonitor); break; case MODIFIED: modifyTargetBreakpoints(bp, eventInfo.fBPTargetContexts, eventInfo.fAttributeDelta); break; case REMOVED: doBreakpointsRemovedInExecutor(new IBreakpoint[]{bp}, eventInfo.fBPTargetContexts, eventInfo.fRequestMonitor); break; } } } } private void fireUpdateBreakpointsStatus(final Map<IBreakpoint, Map<IBreakpointsTargetDMContext, ITargetBreakpointInfo[]>> eventBPs, final BreakpointEventType eventType) { // Update breakpoint status new Job("Breakpoint status update") { //$NON-NLS-1$ { setSystem(true); } @Override protected IStatus run(IProgressMonitor monitor) { fAttributeTranslator2.updateBreakpointsStatus(eventBPs, eventType); return Status.OK_STATUS; }; }.schedule(); } /** * Determine the set of modified attributes. * * @param oldAttributes old map of attributes. * @param newAttributes new map of attributes. * @return new and changed attribute in the new map. May be empty indicating the two maps are equal. */ private Map<String, Object> getAttributesDelta(Map<String, Object> oldAttributes, Map<String, Object> newAttributes) { Map<String, Object> delta = new HashMap<String,Object>(); Set<String> oldKeySet = oldAttributes.keySet(); Set<String> newKeySet = newAttributes.keySet(); Set<String> commonKeys = new HashSet<String>(newKeySet); commonKeys.retainAll(oldKeySet); Set<String> addedKeys = new HashSet<String>(newKeySet); addedKeys.removeAll(oldKeySet); Set<String> removedKeys = new HashSet<String>(oldKeySet); removedKeys.removeAll(newKeySet); // Add the modified attributes for (String key : commonKeys) { if (!(oldAttributes.get(key).equals(newAttributes.get(key)))) delta.put(key, newAttributes.get(key)); } // Add the new attributes for (String key : addedKeys) { delta.put(key, newAttributes.get(key)); } // Remove the deleted attributes for (String key : removedKeys) { delta.put(key, null); } return delta; } }