/*******************************************************************************
 * Copyright (c) 2007, 2018 Wind River 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:
 *     Wind River - Initial API and implementation
 *     Ericsson   - High-level breakpoints integration
 *     Ericsson   - Added breakpoint filter support
 *     Ericsson   - Re-factored the service and put a few comments
 *     Ericsson   - Added Action support
 *     Marc Khouzam (Ericsson) - Fix support for thread filter (Bug 355833)
 *     Marc Khouzam (Ericsson) - Generalize thread filtering logic (Bug 431986)
 *     Marc Khouzam (Ericsson) - Accept multiple calls to startTrackingBreakpoints (Bug 389945)
 *     Marc Khouzam (Ericsson) - Support for dynamic printf (Bug 400628)
 *     Alvaro Sanchez-Leon (Ericsson) - Sometimes breakpoints set and immediately deleted when debugging with GDB (Bug 442394)
 *     Alvaro Sanchez-Leon (Ericsson) - Breakpoint Enable does not work after restarting the application (Bug 456959)
 *******************************************************************************/

package org.eclipse.cdt.dsf.mi.service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.RejectedExecutionException;

import org.eclipse.cdt.core.model.ICModelMarker;
import org.eclipse.cdt.debug.core.CDebugCorePlugin;
import org.eclipse.cdt.debug.core.breakpointactions.BreakpointActionManager;
import org.eclipse.cdt.debug.core.model.ICAddressBreakpoint;
import org.eclipse.cdt.debug.core.model.ICBreakpoint;
import org.eclipse.cdt.debug.core.model.ICBreakpointExtension;
import org.eclipse.cdt.debug.core.model.ICBreakpointType;
import org.eclipse.cdt.debug.core.model.ICDynamicPrintf;
import org.eclipse.cdt.debug.core.model.ICEventBreakpoint;
import org.eclipse.cdt.debug.core.model.ICFunctionBreakpoint;
import org.eclipse.cdt.debug.core.model.ICLineBreakpoint;
import org.eclipse.cdt.debug.core.model.ICTracepoint;
import org.eclipse.cdt.debug.core.model.ICWatchpoint;
import org.eclipse.cdt.debug.core.model.ICWatchpoint2;
import org.eclipse.cdt.debug.internal.core.breakpoints.BreakpointProblems;
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.ImmediateExecutor;
import org.eclipse.cdt.dsf.concurrent.ImmediateRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.concurrent.ThreadSafe;
import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.datamodel.IDMContext;
import org.eclipse.cdt.dsf.debug.service.IBreakpoints;
import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointDMContext;
import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointDMData;
import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointsTargetDMContext;
import org.eclipse.cdt.dsf.debug.service.IDsfBreakpointExtension;
import org.eclipse.cdt.dsf.debug.service.IProcesses;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerDMContext;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IExitedDMEvent;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IStartedDMEvent;
import org.eclipse.cdt.dsf.debug.service.IRunControl.ISuspendedDMEvent;
import org.eclipse.cdt.dsf.debug.service.ISourceLookup;
import org.eclipse.cdt.dsf.debug.service.ISourceLookup.ISourceLookupDMContext;
import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService;
import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService.ICommandControlShutdownDMEvent;
import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin;
import org.eclipse.cdt.dsf.mi.service.MIBreakpoints.BreakpointAddedEvent;
import org.eclipse.cdt.dsf.mi.service.MIBreakpoints.BreakpointRemovedEvent;
import org.eclipse.cdt.dsf.mi.service.MIBreakpoints.BreakpointUpdatedEvent;
import org.eclipse.cdt.dsf.mi.service.MIBreakpoints.MIBreakpointDMContext;
import org.eclipse.cdt.dsf.mi.service.MIRunControl.SuspendedEvent;
import org.eclipse.cdt.dsf.mi.service.breakpoint.actions.BreakpointActionAdapter;
import org.eclipse.cdt.dsf.mi.service.command.events.IMIDMEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MIBreakpointHitEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MIWatchpointScopeEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MIWatchpointTriggerEvent;
import org.eclipse.cdt.dsf.mi.service.command.output.MIBreakpoint;
import org.eclipse.cdt.dsf.service.AbstractDsfService;
import org.eclipse.cdt.dsf.service.DsfServiceEventHandler;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.cdt.gdb.internal.eventbkpts.GdbCatchpoints;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IMarkerDelta;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.MultiRule;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.IBreakpointListener;
import org.eclipse.debug.core.IBreakpointManager;
import org.eclipse.debug.core.IBreakpointManagerListener;
import org.eclipse.debug.core.model.IBreakpoint;
import org.osgi.framework.BundleContext;

import com.ibm.icu.text.MessageFormat;

/**
 * Breakpoint service interface.  The breakpoint service tracks CDT breakpoint
 * objects, and based on those, it manages breakpoints in the debugger back end.
 *
 * It relies on MIBreakpoints for the actual back-end interface.
 */
public class MIBreakpointsManager extends AbstractDsfService
		implements IBreakpointManagerListener, IBreakpointListener {
	/**
	 * A listener is notified by {@link MIBreakpointsManager} when
	 * the breakpoints tracking starts or stops.
	 * @since 4.2
	 */
	public interface IMIBreakpointsTrackingListener {

		public void breakpointTrackingStarted(IBreakpointsTargetDMContext bpTargetDMC);

		public void breakpointTrackingStopped(IBreakpointsTargetDMContext bpTargetDMC);
	}

	// Note: Find a way to import this (careful of circular dependencies)
	public static final String GDB_DEBUG_MODEL_ID = "org.eclipse.cdt.dsf.gdb"; //$NON-NLS-1$

	// Extra breakpoint attributes
	private static final String ATTR_DEBUGGER_PATH = GdbPlugin.PLUGIN_ID + ".debuggerPath"; //$NON-NLS-1$
	private static final String ATTR_THREAD_FILTER = GdbPlugin.PLUGIN_ID + ".threadFilter"; //$NON-NLS-1$
	private static final String ATTR_THREAD_ID = GdbPlugin.PLUGIN_ID + ".threadID"; //$NON-NLS-1$

	// Services
	private ICommandControlService fConnection;
	private ISourceLookup fSourceLookup;
	private IProcesses fProcesses;
	private IBreakpoints fBreakpoints;
	private IBreakpointManager fBreakpointManager; // Platform breakpoint manager (not this!)
	private BreakpointActionManager fBreakpointActionManager;

	///////////////////////////////////////////////////////////////////////////
	// Breakpoints tracking
	///////////////////////////////////////////////////////////////////////////

	private String fDebugModelId;

	// Holds the set of platform breakpoints with their corresponding back-end
	// breakpoint attributes, 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<ICBreakpoint, Map<String, Object>>> fPlatformToAttributesMaps = new HashMap<>();

	/**
	 * Returns the structure that maps each breakpoint target to a map of platform breakpoints
	 * and their corresponding back-end attributes.
	 * @since 4.7
	 */
	protected Map<IBreakpointsTargetDMContext, Map<ICBreakpoint, Map<String, Object>>> getPlatformToAttributesMaps() {
		return fPlatformToAttributesMaps;
	}

	// Holds the set of target breakpoints, per execution context, and their
	// mapping to the corresponding platform breakpoint. In a given execution
	// context there can only be one platform breakpoint per target breakpoint.
	// Acts as a mapping from target (low-level) BP to the corresponding platform
	// (high-level) BP.
	// Updated when:
	// - We start/stop tracking an execution context
	// - A platform breakpoint is added/removed
	// - A thread filter is applied/removed
	private Map<IBreakpointsTargetDMContext, Map<IBreakpointDMContext, ICBreakpoint>> fBPToPlatformMaps = new HashMap<>();

	/**
	 * Returns the structure that maps each breakpoint target to a map of back-end breakpoints
	 * and their corresponding platform breakpoint.
	 * @since 4.7
	 */
	protected Map<IBreakpointsTargetDMContext, Map<IBreakpointDMContext, ICBreakpoint>> getBPToPlatformMaps() {
		return fBPToPlatformMaps;
	}

	// Holds the mapping from platform breakpoint to the corresponding target
	// breakpoint(s), per context. There can be multiple back-end BPs for a
	// single platform BP in the case of [1] multiple target contexts, and/or
	// [2] thread filtering.
	// Updated when:
	// - We start/stop tracking an execution context
	// - A platform breakpoint is added/removed
	// - A thread filter is applied/removed
	private Map<IBreakpointsTargetDMContext, Map<ICBreakpoint, Vector<IBreakpointDMContext>>> fPlatformToBPsMaps = new HashMap<>();

	/**
	 * Returns the structure that maps each breakpoint target to a map of platform breakpoints
	 * and their corresponding vector of back-end breakpoints.
	 * @since 4.7
	 */
	protected Map<IBreakpointsTargetDMContext, Map<ICBreakpoint, Vector<IBreakpointDMContext>>> getPlatformToBPsMaps() {
		return fPlatformToBPsMaps;
	}

	// Holds the mapping from platform breakpoint to the corresponding target
	// breakpoint threads, per context.
	// Updated when:
	// - We start/stop tracking an execution context
	// - A platform breakpoint is added/removed
	// - A thread filter is applied/removed
	private Map<IBreakpointsTargetDMContext, Map<ICBreakpoint, Set<String>>> fPlatformToBPThreadsMaps = new HashMap<>();

	/**
	 * Returns the structure that maps each breakpoint target to a map of platform breakpoints
	 * and their corresponding back-end breakpoint thread ids.
	 * @since 4.7
	 */
	protected Map<IBreakpointsTargetDMContext, Map<ICBreakpoint, Set<String>>> getPlatformToBPThreadsMaps() {
		return fPlatformToBPThreadsMaps;
	}

	// 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> fPendingRequests = new HashSet<>();
	private Set<IBreakpoint> fPendingBreakpoints = new HashSet<>();

	private Map<ICBreakpoint, IMarker> fBreakpointMarkerProblems = new HashMap<>();

	private ListenerList fTrackingListeners = new ListenerList();

	///////////////////////////////////////////////////////////////////////////
	// String constants
	///////////////////////////////////////////////////////////////////////////

	private static final String NULL_STRING = ""; //$NON-NLS-1$

	static final String CONTEXT_ALREADY_INITIALIZED = "Context already initialized"; //$NON-NLS-1$
	static final String INVALID_CONTEXT_TYPE = "Invalid context type"; //$NON-NLS-1$
	static final String INVALID_CONTEXT = "Invalid context"; //$NON-NLS-1$

	static final String UNABLE_TO_READ_BREAKPOINT = "Unable to read initial breakpoint attributes"; //$NON-NLS-1$
	static final String BREAKPOINT_NOT_INSTALLED = "Breakpoints not installed for given context"; //$NON-NLS-1$
	static final String BREAKPOINT_ALREADY_INSTALLED = "Breakpoint already installed"; //$NON-NLS-1$
	static final String BREAKPOINT_ALREADY_REMOVED = "Breakpoint already removed"; //$NON-NLS-1$

	static final String INVALID_BREAKPOINT = "Invalid breakpoint"; //$NON-NLS-1$
	static final String UNKNOWN_BREAKPOINT = "Unknown breakpoint"; //$NON-NLS-1$
	static final String INVALID_PARAMETER = "Invalid breakpoint parameter(s)"; //$NON-NLS-1$

	static final String NO_DEBUGGER_PATH = "No debugger path for breakpoint"; //$NON-NLS-1$
	static final String NO_MARKER_FOR_BREAKPOINT = "No marker associated with breakpoint"; //$NON-NLS-1$

	///////////////////////////////////////////////////////////////////////////
	// AbstractDsfService
	///////////////////////////////////////////////////////////////////////////

	/**
	 * The service constructor.
	 * Performs basic instantiation (method initialize() performs the real
	 * service initialization asynchronously).
	 *
	 * @param session       the debugging session
	 * @param debugModelId  the debugging model
	 */
	public MIBreakpointsManager(DsfSession session, String debugModelId) {
		super(session);
		fDebugModelId = debugModelId;
	}

	//-------------------------------------------------------------------------
	// initialize
	//-------------------------------------------------------------------------
	// - Collect references for the services we interact with
	// - Register to interesting events
	// - Obtain the list of platform breakpoints
	// - Register the service for interested parties
	//-------------------------------------------------------------------------

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.dsf.service.AbstractDsfService#initialize(org.eclipse.cdt.dsf.concurrent.RequestMonitor)
	 */
	@Override
	public void initialize(final RequestMonitor rm) {
		super.initialize(new ImmediateRequestMonitor(rm) {
			@Override
			protected void handleSuccess() {
				doInitialize(rm);
			}
		});
	}

	/**
	 * @param rm
	 */
	private void doInitialize(RequestMonitor rm) {

		// Get the required services references from central repository
		fConnection = getServicesTracker().getService(ICommandControlService.class);
		fSourceLookup = getServicesTracker().getService(ISourceLookup.class);
		fBreakpoints = getServicesTracker().getService(IBreakpoints.class);
		fProcesses = getServicesTracker().getService(IProcesses.class);
		fBreakpointManager = DebugPlugin.getDefault().getBreakpointManager();
		fBreakpointActionManager = CDebugCorePlugin.getDefault().getBreakpointActionManager();

		// Register to the useful events
		getSession().addServiceEventListener(this, null);
		fBreakpointManager.addBreakpointListener(this);
		fBreakpointManager.addBreakpointManagerListener(this);

		// And register this service
		register(new String[] { MIBreakpointsManager.class.getName() }, new Hashtable<String, String>());
		rm.done();
	}

	//-------------------------------------------------------------------------
	// shutdown
	//-------------------------------------------------------------------------
	// - 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...
	//-------------------------------------------------------------------------

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.dsf.service.AbstractDsfService#shutdown(org.eclipse.cdt.dsf.concurrent.RequestMonitor)
	 */
	@Override
	public void shutdown(final RequestMonitor rm) {

		// Stop accepting requests and events
		unregister();
		getSession().removeServiceEventListener(this);
		fBreakpointManager.removeBreakpointListener(this);
		fBreakpointManager.removeBreakpointManagerListener(this);
		fTrackingListeners.clear();

		// 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() {
				MIBreakpointsManager.super.shutdown(rm);
			}
		};

		List<IBreakpointsTargetDMContext> targetBPKeys = new ArrayList<>(fBPToPlatformMaps.size());
		targetBPKeys.addAll(0, fBPToPlatformMaps.keySet());
		for (IBreakpointsTargetDMContext dmc : targetBPKeys) {
			stopTrackingBreakpoints(dmc, countingRm);
		}
		countingRm.setDoneCount(targetBPKeys.size());
	}

	//-------------------------------------------------------------------------
	// getBundleContext
	//-------------------------------------------------------------------------

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.dsf.service.AbstractDsfService#getBundleContext()
	 */
	@Override
	protected BundleContext getBundleContext() {
		return GdbPlugin.getBundleContext();
	}

	///////////////////////////////////////////////////////////////////////////
	// IBreakpointsManager
	///////////////////////////////////////////////////////////////////////////

	/**
	 * Wrapper around startTrackingBreakpoints() which accepts a containerDmc and sets
	 * each breakpoints filter to include that container.  This method should be called
	 * instead of startTrackingBreakpoints()
	 *
	 * @param containerDmc The container to be added in the bp filter.  This container
	 *                     must have the proper IBreakpointsTargetDMContext in its hierarchy.
	 *
	 * @since 4.6
	 */
	public void startTrackingBpForProcess(final IContainerDMContext containerDmc, final RequestMonitor rm) {
		final IBreakpointsTargetDMContext targetBpDmc = DMContexts.getAncestorOfType(containerDmc,
				IBreakpointsTargetDMContext.class);

		IBreakpoint[] breakpoints = fBreakpointManager.getBreakpoints(fDebugModelId);
		for (IBreakpoint breakpoint : breakpoints) {
			if (breakpoint instanceof ICBreakpoint && supportsBreakpoint(breakpoint)) {
				setTargetFilter((ICBreakpoint) breakpoint, containerDmc);
			}
		}
		startTrackingBreakpoints(targetBpDmc, rm);
	}

	//-------------------------------------------------------------------------
	// startTrackingBreakpoints
	//-------------------------------------------------------------------------
	// - Augment the maps with the new execution context
	// - Install the platform breakpoints on the selected target
	//-------------------------------------------------------------------------

	/**
	 * 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 #uninstallBreakpoints(IDMContext)} 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) {

		// Validate the execution context
		if (dmc == null) {
			rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, REQUEST_FAILED, INVALID_CONTEXT, null));
			rm.done();
			return;
		}

		Map<ICBreakpoint, Map<String, Object>> platformBPs = fPlatformToAttributesMaps.get(dmc);
		Map<ICBreakpoint, Vector<IBreakpointDMContext>> breakpointIDs = fPlatformToBPsMaps.get(dmc);
		Map<IBreakpointDMContext, ICBreakpoint> targetIDs = fBPToPlatformMaps.get(dmc);
		Map<ICBreakpoint, Set<String>> threadIDs = fPlatformToBPThreadsMaps.get(dmc);
		if ((platformBPs != null) || (breakpointIDs != null) || (targetIDs != null) || (threadIDs != null)) {
			// If the maps already contains this context we can simply ignore this request.
			// This happens when we start or attach to another process with GDB >= 7.4
			assert platformBPs != null && breakpointIDs != null && targetIDs != null && threadIDs != null;
			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.
		fPlatformToAttributesMaps.put(dmc, new HashMap<ICBreakpoint, Map<String, Object>>());
		fPlatformToBPsMaps.put(dmc, new HashMap<ICBreakpoint, Vector<IBreakpointDMContext>>());
		fBPToPlatformMaps.put(dmc, new HashMap<IBreakpointDMContext, ICBreakpoint>());
		fPlatformToBPThreadsMaps.put(dmc, new HashMap<ICBreakpoint, Set<String>>());

		// Install the platform breakpoints (stored in fPlatformBPs) on the target.
		new Job("DSF BreakpointsManager: Install initial breakpoints on target") { //$NON-NLS-1$
			@Override
			protected IStatus run(IProgressMonitor monitor) {
				// Submit the runnable to plant the breakpoints on dispatch thread.
				getExecutor().submit(
						() -> installInitialBreakpoints(dmc, new RequestMonitor(ImmediateExecutor.getInstance(), rm) {
							@Override
							protected void handleSuccess() {
								// Notify breakpoints tracking listeners that the tracking is started.
								for (Object o : fTrackingListeners.getListeners()) {
									((IMIBreakpointsTrackingListener) o).breakpointTrackingStarted(dmc);
								}
								rm.done();
							}
						}));

				return Status.OK_STATUS;
			}
		}.schedule();
	}

	/**
	 * Installs the breakpoints that existed prior to the activation of this
	 * execution context.
	 *
	 * @param dmc
	 * @param initialPlatformBPs
	 * @param rm
	 */
	private void installInitialBreakpoints(final IBreakpointsTargetDMContext dmc, final RequestMonitor rm) {
		// Retrieve the set of platform breakpoints for this context
		final Map<ICBreakpoint, Map<String, Object>> platformBPs = fPlatformToAttributesMaps.get(dmc);
		if (platformBPs == null) {
			rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, INVALID_CONTEXT, null));
			rm.done();
			return;
		}

		// Read current breakpoints from platform and copy their augmented
		// attributes into the local reference map
		try {
			IBreakpoint[] breakpoints = fBreakpointManager.getBreakpoints(fDebugModelId);
			for (IBreakpoint breakpoint : breakpoints) {
				if (supportsBreakpoint(breakpoint)) {
					boolean filtered = isBreakpointEntirelyFiltered(dmc, (ICBreakpoint) breakpoint);
					if (!filtered) {
						Map<String, Object> attributes = breakpoint.getMarker().getAttributes();
						attributes.put(ATTR_DEBUGGER_PATH, NULL_STRING);
						attributes.put(ATTR_THREAD_FILTER, extractThreads(dmc, (ICBreakpoint) breakpoint));
						attributes.put(ATTR_THREAD_ID, NULL_STRING);
						platformBPs.put((ICBreakpoint) breakpoint, attributes);
					}
				}
			}
		} catch (CoreException e) {
			IStatus status = new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, REQUEST_FAILED, UNABLE_TO_READ_BREAKPOINT,
					e);
			rm.setStatus(status);
			rm.done();
		}

		// Install the individual breakpoints on the dispatcher thread
		// Requires a counting monitor to know when we are done
		final CountingRequestMonitor countingRm = new CountingRequestMonitor(getExecutor(), rm);
		countingRm.setDoneCount(platformBPs.size());

		for (final ICBreakpoint breakpoint : platformBPs.keySet()) {
			final Map<String, Object> attributes = platformBPs.get(breakpoint);
			// Upon determining the debuggerPath, the breakpoint is installed
			determineDebuggerPath(dmc, attributes, new RequestMonitor(getExecutor(), countingRm) {
				@Override
				protected void handleSuccess() {
					// Must install breakpoints right away, even if disabled, so that
					// we can find out if they apply to this target (Bug 389070)
					installBreakpoint(dmc, breakpoint, attributes, countingRm);
				}
			});
		}
	}

	//-------------------------------------------------------------------------
	// stopTrackingBreakpoints
	//-------------------------------------------------------------------------
	// - Remove the target breakpoints for the given execution context
	// - Update the maps
	//-------------------------------------------------------------------------

	/**
	 * Uninstall and stop tracking breakpoints for the given context.
	 * @param dmc Context to start tracking breakpoints for.
	 * @param rm Completion callback.
	 */
	public void stopTrackingBreakpoints(final IBreakpointsTargetDMContext dmc, final RequestMonitor rm) {

		// Validate the context
		if (dmc == null) {
			rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, INVALID_CONTEXT, null));
			rm.done();
			return;
		}

		// Retrieve the set of platform breakpoints for this context
		final Map<ICBreakpoint, Map<String, Object>> platformBPs = fPlatformToAttributesMaps.get(dmc);
		if (platformBPs == null) {
			rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, INVALID_CONTEXT, null));
			rm.done();
			return;
		}

		// Un-install the individual breakpoints on the dispatcher thread
		// (requires a counting monitor to know when we are done).
		// On completion (success or failure), update the maps.
		final CountingRequestMonitor countingRm = new CountingRequestMonitor(getExecutor(), rm) {
			@Override
			protected void handleCompleted() {
				fPlatformToAttributesMaps.remove(dmc);
				fPlatformToBPsMaps.remove(dmc);
				fBPToPlatformMaps.remove(dmc);
				fPlatformToBPThreadsMaps.remove(dmc);
				// Notify breakpoints tracking listeners that the tracking is stopped.
				for (Object o : fTrackingListeners.getListeners()) {
					((IMIBreakpointsTrackingListener) o).breakpointTrackingStopped(dmc);
				}
				rm.done();
			}
		};
		countingRm.setDoneCount(platformBPs.size());

		for (final ICBreakpoint breakpoint : platformBPs.keySet()) {
			uninstallBreakpoint(dmc, breakpoint, new RequestMonitor(getExecutor(), countingRm) {
				@Override
				protected void handleCompleted() {
					countingRm.done();
				}
			});
		}
	}

	/**
	 * Return the collection of tracked target breakpoint contexts. Use this method
	 * instead of implying the installed collection from the various maps contained
	 * in the manager.
	 *
	 * @since 5.5
	 */
	protected Collection<IBreakpointsTargetDMContext> getTrackedBreakpointTargetContexts() {
		return fPlatformToAttributesMaps.keySet();
	}

	///////////////////////////////////////////////////////////////////////////
	// Back-end interface functions
	///////////////////////////////////////////////////////////////////////////

	//-------------------------------------------------------------------------
	// installBreakpoint
	//-------------------------------------------------------------------------

	/**
	 * Install a platform breakpoint on the back-end. For a given context, a
	 * platform breakpoint can resolve into multiple back-end breakpoints when
	 * threads are taken into account or if multiple breakpoints are created
	 * on the target using the console.
	 *
	 * @param dmc
	 * @param breakpoint
	 * @param attributes
	 * @param rm
	 */
	private void installBreakpoint(IBreakpointsTargetDMContext dmc, final ICBreakpoint breakpoint,
			final Map<String, Object> attributes, final RequestMonitor rm) {
		// Retrieve the breakpoint maps
		final Map<ICBreakpoint, Map<String, Object>> platformBPs = fPlatformToAttributesMaps.get(dmc);
		assert platformBPs != null;

		final Map<ICBreakpoint, Vector<IBreakpointDMContext>> breakpointIDs = fPlatformToBPsMaps.get(dmc);
		assert breakpointIDs != null;

		final Map<IBreakpointDMContext, ICBreakpoint> targetBPs = fBPToPlatformMaps.get(dmc);
		assert targetBPs != null;

		final Map<ICBreakpoint, Set<String>> threadsIDs = fPlatformToBPThreadsMaps.get(dmc);
		assert threadsIDs != null;

		// Ensure the breakpoint has a valid debugger source path
		if (breakpoint instanceof ICLineBreakpoint && !(breakpoint instanceof ICAddressBreakpoint)
				&& !(breakpoint instanceof ICFunctionBreakpoint)) {
			String debuggerPath = (String) attributes.get(ATTR_DEBUGGER_PATH);
			if (debuggerPath == null || debuggerPath.equals(NULL_STRING)) {
				rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, REQUEST_FAILED, NO_DEBUGGER_PATH, null));
				rm.done();
				return;
			}
		}

		// A back-end breakpoint needs to be installed for each specified thread
		final Set<String> threads = getThreads(attributes);

		// Update the breakpoint state when all back-end breakpoints have been installed
		final CountingRequestMonitor installRM = new CountingRequestMonitor(getExecutor(), rm) {
			@Override
			protected void handleCompleted() {
				// Store the platform breakpoint
				platformBPs.put(breakpoint, attributes);
				rm.done();
			}
		};
		installRM.setDoneCount(threads.size());

		// Install the back-end breakpoint(s)
		for (final String thread : threads) {
			DataRequestMonitor<IBreakpointDMContext> drm = new DataRequestMonitor<IBreakpointDMContext>(getExecutor(),
					installRM) {
				@Override
				protected void handleSuccess() {
					// Add the new back-end breakpoint to the map
					Vector<IBreakpointDMContext> list = breakpointIDs.get(breakpoint);
					if (list == null) {
						list = new Vector<>();
					}

					IBreakpointDMContext targetBP = getData();
					list.add(targetBP);
					breakpointIDs.put(breakpoint, list);

					// Add the reverse mapping
					targetBPs.put(targetBP, breakpoint);

					// And update the corresponding thread list
					Set<String> thrds = threadsIDs.get(breakpoint);
					if (thrds == null) {
						thrds = new HashSet<>();
					}

					thrds.add(thread);
					threadsIDs.put(breakpoint, thrds);

					// Reset the thread (is it necessary?)
					attributes.put(ATTR_THREAD_ID, NULL_STRING);

					// Remove breakpoint problem marker (if any)
					removeBreakpointProblemMarker(breakpoint);

					// Check for a pending breakpoint before showing that it was properly installed
					fBreakpoints.getBreakpointDMData(targetBP,
							new DataRequestMonitor<IBreakpointDMData>(getExecutor(), null) {
								@Override
								protected void handleCompleted() {
									boolean pending = false;
									if (isSuccess()) {
										IBreakpointDMData data = getData();
										if (data instanceof MIBreakpointDMData) {
											pending = ((MIBreakpointDMData) data).isPending();
										}
									}
									// Finally, update the platform breakpoint to show it was installed, unless we have a pending breakpoint
									if (!pending) {
										try {
											breakpoint.incrementInstallCount();
										} catch (CoreException e) {
										}
									}
									installRM.done();
								}
							});
				}

				@Override
				protected void handleError() {
					String detailedMessage;
					if (getStatus().getException() != null && getStatus().getException().getMessage() != null) {
						detailedMessage = getStatus().getException().getMessage();
					} else {
						detailedMessage = getStatus().getMessage();
					}
					String description = (detailedMessage == null) ? Messages.Breakpoint_attribute_problem
							: MessageFormat.format(Messages.Breakpoint_attribute_detailed_problem,
									new Object[] { detailedMessage });

					addBreakpointProblemMarker(breakpoint, description, IMarker.SEVERITY_WARNING);
					installRM.done();
				}
			};

			// Convert the breakpoint attributes for the back-end
			attributes.put(ATTR_THREAD_ID, thread);
			Map<String, Object> targetAttributes = convertToTargetBreakpoint(breakpoint, attributes);
			// Must install breakpoint right away, even if disabled, so that
			// we can find out if it applies to this target (Bug 389070)
			fBreakpoints.insertBreakpoint(dmc, targetAttributes, drm);
		}
	}

	private void addBreakpointProblemMarker(final ICBreakpoint breakpoint, final String description,
			final int severity) {

		new Job("Add Breakpoint Problem Marker") { //$NON-NLS-1$
			@Override
			protected IStatus run(IProgressMonitor monitor) {

				if (breakpoint instanceof ICLineBreakpoint) {
					// If we have already have a problem marker on this breakpoint
					// we should remove it first.
					IMarker marker = fBreakpointMarkerProblems.remove(breakpoint);
					if (marker != null) {
						try {
							marker.delete();
						} catch (CoreException e) {
						}
					}

					ICLineBreakpoint lineBreakpoint = (ICLineBreakpoint) breakpoint;
					try {
						// Locate the workspace resource via the breakpoint marker
						IMarker breakpointMarker = lineBreakpoint.getMarker();
						IResource resource = breakpointMarker.getResource();

						// Add a problem marker to the resource
						IMarker problemMarker = resource.createMarker(BreakpointProblems.BREAKPOINT_PROBLEM_MARKER_ID);
						int lineNumber = lineBreakpoint.getLineNumber();
						String sourceHandle = lineBreakpoint.getSourceHandle();
						problemMarker.setAttribute(IMarker.LOCATION, String.valueOf(lineNumber));
						problemMarker.setAttribute(IMarker.MESSAGE, description);
						problemMarker.setAttribute(IMarker.SEVERITY, severity);
						problemMarker.setAttribute(IMarker.LINE_NUMBER, lineNumber);
						if (sourceHandle != null) {
							problemMarker.setAttribute(ICModelMarker.C_MODEL_MARKER_EXTERNAL_LOCATION, sourceHandle);
						}

						// And save the baby
						fBreakpointMarkerProblems.put(breakpoint, problemMarker);
					} catch (CoreException e) {
					}
				}
				return Status.OK_STATUS;
			}
		}.schedule();
	}

	private void removeBreakpointProblemMarker(final ICBreakpoint breakpoint) {

		new Job("Remove Breakpoint Problem Marker") { //$NON-NLS-1$
			@Override
			protected IStatus run(IProgressMonitor monitor) {

				IMarker marker = fBreakpointMarkerProblems.remove(breakpoint);
				if (marker != null) {
					try {
						marker.delete();
					} catch (CoreException e) {
					}
				}

				return Status.OK_STATUS;
			}
		}.schedule();
	}

	//-------------------------------------------------------------------------
	// uninstallBreakpoint
	//-------------------------------------------------------------------------

	/**
	 * Un-install an individual breakpoint on the back-end. For one platform
	 * breakpoint in a given execution context, there could be multiple
	 * corresponding back-end breakpoints (one per thread).
	 *
	 * @param dmc
	 * @param breakpoint
	 * @param rm
	 *
	 * @since 4.2
	 */
	public void uninstallBreakpoint(final IBreakpointsTargetDMContext dmc, final ICBreakpoint breakpoint,
			RequestMonitor rm) {
		// Remove all relevant target filters
		// Note that this call is important if a breakpoint is removed directly
		// from the gdb console, or else we will try to re-install it (bug 433044)
		removeAllTargetFilters(dmc, breakpoint);

		// Remove breakpoint problem marker (if any)
		removeBreakpointProblemMarker(breakpoint);

		doUninstallBreakpoint(dmc, breakpoint, new ImmediateRequestMonitor(rm) {
			@Override
			protected void handleSuccess() {
				Map<ICBreakpoint, Map<String, Object>> platformBPs = fPlatformToAttributesMaps.get(dmc);
				// Note: Protect against NPE. It looks like we just checked the "platformBPs" is not null,
				// in doUninstallBreakpoint(), but there is a race condition that can make it null
				// in the meantime, if the debug session is destroyed. See bug 511727
				if (platformBPs != null) {
					platformBPs.remove(breakpoint);
				}
				super.handleSuccess();
			}
		});
	}

	/*
	 * Un-install the target breakpoints associated with the given platform breakpoint.
	 * The information related to the platform breakpoints is not cleared from the session.
	 * This allows the target breakpoints to be re-installed when an attribute of the platform
	 * breakpoint is changed.
	 */
	private void doUninstallBreakpoint(final IBreakpointsTargetDMContext dmc, final ICBreakpoint breakpoint,
			final RequestMonitor rm) {
		// Retrieve the breakpoint maps
		final Map<ICBreakpoint, Map<String, Object>> platformBPs = fPlatformToAttributesMaps.get(dmc);
		assert platformBPs != null;

		final Map<ICBreakpoint, Vector<IBreakpointDMContext>> breakpointIDs = fPlatformToBPsMaps.get(dmc);
		assert breakpointIDs != null;

		final Map<IBreakpointDMContext, ICBreakpoint> targetBPs = fBPToPlatformMaps.get(dmc);
		assert targetBPs != null;

		final Map<ICBreakpoint, Set<String>> threadsIDs = fPlatformToBPThreadsMaps.get(dmc);
		assert threadsIDs != null;

		// Minimal validation
		if (!platformBPs.containsKey(breakpoint) || !breakpointIDs.containsKey(breakpoint)
				|| !targetBPs.containsValue(breakpoint)) {
			rm.setStatus(
					new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, BREAKPOINT_ALREADY_REMOVED, null));
			rm.done();
			return;
		}

		// Remove completion monitor
		// Upon completion, update the mappings
		final CountingRequestMonitor removeRM = new CountingRequestMonitor(getExecutor(), rm) {
			@Override
			protected void handleSuccess() {
				// Update the mappings
				threadsIDs.remove(breakpoint);
				fPendingRequests.remove(breakpoint);
				rm.done();
			}
		};

		// Remove the back-end breakpoints
		// Remove the entry from the breakpointIDs map right away to indicate that we have already
		// taken care of this breakpoint.  This avoids race conditions with the shutdown
		// which would also try to decrement the install count (bug 344635)
		Vector<IBreakpointDMContext> list = breakpointIDs.remove(breakpoint);
		int count = 0;
		if (list != null) {
			for (final IBreakpointDMContext bp : list) {
				targetBPs.remove(bp);
				decrementInstallCount(bp, breakpoint, new RequestMonitor(getExecutor(), removeRM) {
					@Override
					protected void handleCompleted() {
						fBreakpoints.removeBreakpoint(bp, removeRM);
					}
				});
			}
			count = list.size();
			list.clear(); // probably not necessary
		}
		removeRM.setDoneCount(count);
	}

	private void decrementInstallCount(IBreakpointDMContext targetDmc, final ICBreakpoint breakpoint,
			final RequestMonitor rm) {
		fBreakpoints.getBreakpointDMData(targetDmc, new DataRequestMonitor<IBreakpointDMData>(getExecutor(), rm) {
			@Override
			protected void handleCompleted() {
				boolean pending = false;
				if (isSuccess()) {
					IBreakpointDMData data = getData();
					if (data instanceof MIBreakpointDMData) {
						pending = ((MIBreakpointDMData) data).isPending();
					}
				}
				// Finally, update the platform breakpoint to show it was un-installed.
				// But we don't do this for pending breakpoints since they were
				// not marked as installed.
				if (!pending) {
					try {
						breakpoint.decrementInstallCount();
					} catch (CoreException e) {
					}
				}
				rm.done();
			}
		});
	}
	//-------------------------------------------------------------------------
	// modifyBreakpoint
	//-------------------------------------------------------------------------

	/**
	 * Modify a platform breakpoint which can translate to quite a few updates
	 * on the target...
	 *
	 * @param dmc
	 * @param breakpoint
	 * @param attributes
	 * @param oldValues
	 * @param rm
	 */
	private void modifyBreakpoint(final IBreakpointsTargetDMContext dmc, final ICBreakpoint breakpoint,
			final Map<String, Object> attributes, final IMarkerDelta oldValues, final RequestMonitor rm) {
		// Retrieve the breakpoint maps
		final Map<ICBreakpoint, Map<String, Object>> platformBPs = fPlatformToAttributesMaps.get(dmc);
		assert platformBPs != null;

		final Map<ICBreakpoint, Vector<IBreakpointDMContext>> breakpointIDs = fPlatformToBPsMaps.get(dmc);
		assert breakpointIDs != null;

		final Map<IBreakpointDMContext, ICBreakpoint> targetBPs = fBPToPlatformMaps.get(dmc);
		assert targetBPs != null;

		final Map<ICBreakpoint, Set<String>> threadsIDs = fPlatformToBPThreadsMaps.get(dmc);
		assert threadsIDs != null;

		boolean filtered = isBreakpointEntirelyFiltered(dmc, breakpoint);

		if (filtered && !platformBPs.containsKey(breakpoint)) {
			rm.done();
			return;
		}

		// Check if the breakpoint is installed:
		// the installation might have failed; in this case, we try to install it again because
		// some attribute might have changed which will make the install succeed.
		if (!breakpointIDs.containsKey(breakpoint) && !targetBPs.containsValue(breakpoint)) {
			if (!filtered) {
				// Do not try to re-install the breakpoint if the change event is the result of changes
				// in the breakpoint's install count.
				// Excluding ATTR_DEBUGGER_PATH from the comparison because it has been set just before
				// "modifyBreakpoint()" was called.
				// (Bugzilla 534309) Guard against NULL oldValues, which is legitimate, in which case use an empty Map.
				String[] diff = compareAttributes(
						oldValues == null ? Collections.emptyMap() : oldValues.getAttributes(), attributes,
						new String[] { ATTR_DEBUGGER_PATH });
				if (diff.length != 1 || !diff[0].equals(ICBreakpoint.INSTALL_COUNT)) {
					attributes.put(ATTR_DEBUGGER_PATH, NULL_STRING);
					attributes.put(ATTR_THREAD_FILTER, extractThreads(dmc, breakpoint));
					attributes.put(ATTR_THREAD_ID, NULL_STRING);
					determineDebuggerPath(dmc, attributes, new RequestMonitor(getExecutor(), rm) {
						@Override
						protected void handleSuccess() {
							installBreakpoint(dmc, breakpoint, attributes, rm);
						}
					});
				} else {
					rm.done();
				}
			} else {
				rm.done();
			}
			return;
		}

		if (filtered) {
			uninstallBreakpoint(dmc, breakpoint, rm);
			return;
		}

		// Get the original breakpoint attributes
		final Map<String, Object> originalAttributes = platformBPs.get(breakpoint);
		if (originalAttributes == null) {
			rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, INVALID_BREAKPOINT, null));
			rm.done();
			return;
		}

		// Determine the attributes delta
		final Map<String, Object> oldAttributes = new HashMap<>(originalAttributes);
		oldAttributes.put(ATTR_THREAD_FILTER, threadsIDs.get(breakpoint));

		final Set<String> newThreads = extractThreads(dmc, breakpoint);
		Map<String, Object> newAttributes = new HashMap<>(attributes);
		newAttributes.put(ATTR_THREAD_FILTER, newThreads);

		final Map<String, Object> attributesDelta = determineAttributesDelta(oldAttributes, newAttributes);

		// Get the list of back-end breakpoints
		final Vector<IBreakpointDMContext> oldTargetBPs = new Vector<>(breakpointIDs.get(breakpoint));
		if (oldTargetBPs.isEmpty()) {
			rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, INVALID_BREAKPOINT, null));
			rm.done();
			return;
		}

		// We're all set for the breakpoint update.
		//
		// The path for a simple update is straightforward:
		// - For each back-end BP corresponding to a platform BP
		//   - Send an update command to the back-end
		//   - If the operation succeeded, update the data structures
		//   - If the operation failed, try to roll-back
		//
		// In cases where the the back-end breakpoint cannot be
		// simply updated (e.g. thread filter modification), the old
		// breakpoint has to be removed and new one(s) inserted.
		//
		// The path for such an update is:
		// - Install the updated breakpoint
		// - In the operation succeeded
		//   - Remove the old breakpoint(s)
		//   - Perform any pending update

		// Update completion monitor
		final CountingRequestMonitor updateRM = new CountingRequestMonitor(getExecutor(), rm) {
			@Override
			protected void handleSuccess() {
				// Success: simply store the new attributes
				platformBPs.put(breakpoint, attributes);
				rm.done();
			}

			@Override
			protected void handleError() {
				// Store the error message to use in the problem marker
				final String errorMessage = getStatus().getException() != null
						? getStatus().getException().getLocalizedMessage()
						: getStatus().getMessage();
				// Try to uninstall the target breakpoints and add the problem marker
				// with the error message to the platform breakpoint.
				doUninstallBreakpoint(dmc, breakpoint, new ImmediateRequestMonitor(rm) {
					@Override
					protected void handleSuccess() {
						addBreakpointProblemMarker(breakpoint, errorMessage, IMarker.SEVERITY_WARNING);
						rm.done();
					}

					@Override
					protected void handleError() {
						// Reset the breakpoint attributes. This will trigger a
						// breakpoint change event and the correct delta will be
						// computed, resulting in a correctly restored breakpoint
						// at the back-end.
						rollbackAttributes(breakpoint, oldValues);
						platformBPs.put(breakpoint, attributes);

						rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, REQUEST_FAILED, INVALID_PARAMETER,
								getStatus().getException()));
						rm.done();
					}
				});
			}
		};

		// Everything OK: remove the old back-end breakpoints
		final Vector<IBreakpointDMContext> newTargetBPs = new Vector<>();
		final CountingRequestMonitor removeRM = new CountingRequestMonitor(getExecutor(), rm) {
			@Override
			protected void handleSuccess() {
				// All right! Save the new list and perform the final update
				Map<ICBreakpoint, Vector<IBreakpointDMContext>> breakpointIDs = fPlatformToBPsMaps.get(dmc);
				if (breakpointIDs == null) {
					rm.setStatus(
							new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, REQUEST_FAILED, INVALID_BREAKPOINT, null));
					rm.done();
					return;
				}
				breakpointIDs.put(breakpoint, newTargetBPs);
				for (IBreakpointDMContext ref : newTargetBPs) {
					fBreakpoints.updateBreakpoint(ref, attributesDelta, updateRM);
				}
				updateRM.setDoneCount(newTargetBPs.size());
			}
		};

		// New back-end breakpoints insertion monitor
		// Holds the list of new back-end breakpoint contexts of the platform breakpoint
		final DataRequestMonitor<Vector<IBreakpointDMContext>> insertRM = new DataRequestMonitor<Vector<IBreakpointDMContext>>(
				getExecutor(), null) {

			@Override
			// In theory, we could have had a partial success and the original threads
			// list would be invalid. We think it is highly unlikely so we assume that
			// either everything went fine or else everything failed.
			protected void handleSuccess() {
				// Get the list of new back-end breakpoints contexts
				newTargetBPs.addAll(getData());
				for (IBreakpointDMContext newRef : newTargetBPs) {
					targetBPs.put(newRef, breakpoint);
				}
				threadsIDs.put(breakpoint, newThreads);
				for (final IBreakpointDMContext ref : oldTargetBPs) {
					targetBPs.remove(ref);
					decrementInstallCount(ref, breakpoint, // A tad early but it should work...
							new RequestMonitor(getExecutor(), removeRM) {
								@Override
								protected void handleCompleted() {
									fBreakpoints.removeBreakpoint(ref, removeRM);
								}
							});
				}
				removeRM.setDoneCount(oldTargetBPs.size());
			}

			@Override
			protected void handleError() {
				// Store the error message to use in the problem marker
				final String errorMessage = getStatus().getException() != null
						? getStatus().getException().getLocalizedMessage()
						: getStatus().getMessage();
				doUninstallBreakpoint(dmc, breakpoint, new ImmediateRequestMonitor(updateRM) {
					@Override
					protected void handleSuccess() {
						addBreakpointProblemMarker(breakpoint, errorMessage, IMarker.SEVERITY_WARNING);
						updateRM.setDoneCount(0);
					}
				});
			}
		};

		// If the changes in the breakpoint attributes justify it, install a
		// new set of back-end breakpoint(s) and then update them
		if (needsResinstallation(attributesDelta)) {
			reinstallBreakpoint(dmc, breakpoint, attributes, newThreads, insertRM);
		} else {
			// Update the back-end breakpoint(s) state
			for (IBreakpointDMContext ref : oldTargetBPs) {
				fBreakpoints.updateBreakpoint(ref, attributesDelta, updateRM);
			}
			updateRM.setDoneCount(oldTargetBPs.size());
		}
	}

	/**
	 * Re-install the back-end breakpoints
	 *
	 * @param context       the target context
	 * @param breakpoint    the platform breakpoint
	 * @param attributes    breakpoint augmented attributes
	 * @param threads       list of threads where breakpoint is to be installed
	 * @param drm           will contain the list of successfully installed back-end breakpoints
	 */
	private void reinstallBreakpoint(final IBreakpointsTargetDMContext context, final ICBreakpoint breakpoint,
			final Map<String, Object> attributes, Set<String> threads,
			final DataRequestMonitor<Vector<IBreakpointDMContext>> drm) {
		// Our new list of back-end breakpoints. Built as we go.
		final Vector<IBreakpointDMContext> breakpointList = new Vector<>();

		// Counting monitor for the new back-end breakpoints to install
		// Once we're done, return the new list of back-end breakpoints contexts
		final CountingRequestMonitor installRM = new CountingRequestMonitor(getExecutor(), drm) {
			@Override
			protected void handleSuccess() {
				// Report whatever we have managed to install
				// It is very likely installation either succeeded or failed for all
				drm.setData(breakpointList);
				drm.done();
			}
		};
		installRM.setDoneCount(threads.size());

		// And install the new back-end breakpoints
		for (String thread : threads) {
			// Convert the breakpoint attributes for the back-end
			// Refresh the set of attributes at each iteration just in case...
			Map<String, Object> attrs = convertToTargetBreakpoint(breakpoint, attributes);
			// Tracepoints and dynamic printf are not affected by "skip-all"
			if (!(breakpoint instanceof ICTracepoint) && !(breakpoint instanceof ICDynamicPrintf)
					&& !fBreakpointManager.isEnabled()) {
				attrs.put(MIBreakpoints.IS_ENABLED, false);
			}

			attrs.put(MIBreakpointDMData.THREAD_ID, thread);

			// Then install the spiked breakpoint
			fBreakpoints.insertBreakpoint(context, attrs,
					new DataRequestMonitor<IBreakpointDMContext>(getExecutor(), installRM) {
						@Override
						protected void handleSuccess() {
							// Add the new back-end breakpoint context to the list
							breakpointList.add(getData());

							// Check for a pending breakpoint before showing that it was properly installed
							fBreakpoints.getBreakpointDMData(getData(),
									new DataRequestMonitor<IBreakpointDMData>(getExecutor(), null) {
										@Override
										protected void handleCompleted() {
											boolean pending = false;
											if (isSuccess()) {
												IBreakpointDMData data = getData();
												if (data instanceof MIBreakpointDMData) {
													pending = ((MIBreakpointDMData) data).isPending();
												}
											}
											// Finally, update the platform breakpoint to show it was installed, unless we have a pending breakpoint
											if (!pending) {
												try {
													breakpoint.incrementInstallCount();
												} catch (CoreException e) {
												}
											}
											installRM.done();
										}
									});
						}

						@Override
						protected void handleError() {
							String detailedMessage;
							if (getStatus().getException() != null && getStatus().getException().getMessage() != null) {
								detailedMessage = getStatus().getException().getMessage();
							} else {
								detailedMessage = getStatus().getMessage();
							}
							String description = (detailedMessage == null) ? Messages.Breakpoint_attribute_problem
									: MessageFormat.format(Messages.Breakpoint_attribute_detailed_problem,
											new Object[] { detailedMessage });
							// Add the new back-end breakpoint context to the list
							installRM.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, REQUEST_FAILED,
									description, getStatus().getException()));
							installRM.done();
						}
					});
		}
	}

	///////////////////////////////////////////////////////////////////////////
	// IBreakpointManagerListener implementation
	///////////////////////////////////////////////////////////////////////////

	/* (non-Javadoc)
	 * @see org.eclipse.debug.core.IBreakpointManagerListener#breakpointManagerEnablementChanged(boolean)
	 */
	@Override
	public void breakpointManagerEnablementChanged(boolean enabled) {

		// Only modify enabled breakpoints
		for (IBreakpointsTargetDMContext context : fPlatformToBPsMaps.keySet()) {
			for (ICBreakpoint breakpoint : fPlatformToBPsMaps.get(context).keySet()) {
				try {
					// Note that Tracepoints and dynamic printf are not affected by "skip-all"
					if (!(breakpoint instanceof ICTracepoint) && !(breakpoint instanceof ICDynamicPrintf)
							&& breakpoint.isEnabled()) {
						for (IBreakpointDMContext ref : fPlatformToBPsMaps.get(context).get(breakpoint)) {
							Map<String, Object> delta = new HashMap<>();
							delta.put(MIBreakpoints.IS_ENABLED, enabled);
							fBreakpoints.updateBreakpoint(ref, delta, new RequestMonitor(getExecutor(), null));
						}
					}
				} catch (CoreException e) {
				}
			}
		}
	}

	///////////////////////////////////////////////////////////////////////////
	// IBreakpointListener implementation
	///////////////////////////////////////////////////////////////////////////

	@ThreadSafe
	@Override
	public void breakpointAdded(final IBreakpoint breakpoint) {
		breakpointAdded(breakpoint, null, new RequestMonitor(getExecutor(), null));
	}

	/**
	 * Extension of {@link #breakpointAdded(IBreakpoint)}
	 *
	 * @param miBpt
	 *            the MIBreakpoint that initiated the breakpointAdded, or null
	 * @since 5.3
	 * @deprecated Use
	 *             {@link #breakpointAdded(IBreakpoint, MIBreakpoint, RequestMonitor)}
	 *             instead. See Bug 530377.
	 */
	@ThreadSafe
	@Deprecated
	public void breakpointAdded(final IBreakpoint breakpoint, MIBreakpoint miBpt) {
		breakpointAdded(breakpoint, miBpt, new RequestMonitor(getExecutor(), null));
	}

	/**
	 * Extension of {@link #breakpointAdded(IBreakpoint)} that can be monitored for
	 * completeness with a {@link RequestMonitor}.
	 *
	 * @param breakpoint
	 *            the added breakpoint
	 * @param miBpt
	 *            the MIBreakpoint that initiated the breakpointAdded, or null
	 * @param rm
	 * @since 5.5
	 */
	@ThreadSafe
	public void breakpointAdded(final IBreakpoint breakpoint, MIBreakpoint miBpt, RequestMonitor rm) {
		if (supportsBreakpoint(breakpoint)) {
			try {
				// Retrieve the breakpoint attributes
				final Map<String, Object> attrs = breakpoint.getMarker().getAttributes();

				getExecutor().execute(new DsfRunnable() {
					@Override
					public void run() {
						// For a new breakpoint, the first thing we do is set the target filter to all existing processes.
						// We will need this when it is time to install the breakpoint.
						// We fetch the processes from our IProcess service to be generic (bug 431986)
						fProcesses.getProcessesBeingDebugged(fConnection.getContext(),
								new DataRequestMonitor<IDMContext[]>(getExecutor(), rm) {
									@Override
									protected void handleCompleted() {
										if (isSuccess()) {
											try {
												IDsfBreakpointExtension filterExtension = getFilterExtension(
														(ICBreakpoint) breakpoint);
												for (IDMContext dmc : getData()) {
													IContainerDMContext containerDmc = DMContexts.getAncestorOfType(dmc,
															IContainerDMContext.class);
													assert containerDmc != null;
													if (filterExtension.getThreadFilters(containerDmc) == null) {
														// Do this only if there wasn't already an entry, or else we would
														// erase the content of that previous entry.
														// There can be an entry already when a thread-specific breakpoint is created
														// from the MIBreakpointsSynchronizer (through the gdb console).  In that case the
														// platform bp gets created, and the targetFilter gets set by MIBreakpointsSynchronizer
														// before the call to breakpointAdded() is made and we get to here.
														// Bug 433329
														filterExtension.setTargetFilter(containerDmc);
													}
												}
											} catch (CoreException e1) {
												// Error setting target filter, just skip altogether
											}
										}

										// Now we can install the bp for all target contexts
										final CountingRequestMonitor countingRm = new CountingRequestMonitor(
												getExecutor(), rm) {
											@Override
											protected void handleCompleted() {
												// Log any error when creating the breakpoint
												if (getStatus().getSeverity() == IStatus.ERROR) {
													GdbPlugin.getDefault().getLog().log(getStatus());
												}
												rm.done();
											}
										};
										countingRm.setDoneCount(getTrackedBreakpointTargetContexts().size());

										for (final IBreakpointsTargetDMContext dmc : getTrackedBreakpointTargetContexts()) {
											boolean filtered = isBreakpointEntirelyFiltered(dmc,
													(ICBreakpoint) breakpoint);
											if (!filtered) {
												determineDebuggerPath(dmc, attrs,
														new RequestMonitor(getExecutor(), countingRm) {
															@Override
															protected void handleSuccess() {
																installBreakpoint(dmc, (ICBreakpoint) breakpoint, attrs,
																		countingRm);
															}
														});
											} else {
												countingRm.done();
											}
										}
									}
								});
					}
				});

				// Normal return case, rm handling passed to runnable
				return;
			} catch (CoreException e) {
			} catch (RejectedExecutionException e) {
			}
		}

		// error/abnormal return case
		rm.done();
	}

	/**
	 * @param bp
	 * @return
	 * @throws CoreException
	 * @since 4.7
	 */
	protected IDsfBreakpointExtension getFilterExtension(ICBreakpoint bp) throws CoreException {
		return (IDsfBreakpointExtension) bp.getExtension(GDB_DEBUG_MODEL_ID, ICBreakpointExtension.class);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.debug.core.IBreakpointListener#breakpointChanged(org.eclipse.debug.core.model.IBreakpoint, org.eclipse.core.resources.IMarkerDelta)
	 */
	@Override
	public void breakpointChanged(final IBreakpoint breakpoint, final IMarkerDelta delta) {

		if (supportsBreakpoint(breakpoint)) {

			try {
				// Retrieve the breakpoint attributes
				final Map<String, Object> attrs = breakpoint.getMarker().getAttributes();
				// Tracepoints and dynamic printf are not affected by "skip-all"
				if (!(breakpoint instanceof ICTracepoint) && !(breakpoint instanceof ICDynamicPrintf)
						&& !fBreakpointManager.isEnabled()) {
					attrs.put(ICBreakpoint.ENABLED, false);
				}

				// Modify the breakpoint in all the target contexts
				getExecutor().execute(new DsfRunnable() {
					@Override
					public void run() {

						// If the breakpoint is currently being updated, queue the request and exit
						if (fPendingRequests.contains(breakpoint)) {
							fPendingBreakpoints.add(breakpoint);
							return;
						}

						// Keep track of the updates
						final CountingRequestMonitor countingRm = new CountingRequestMonitor(getExecutor(), null) {
							@Override
							protected void handleCompleted() {

								if (!isSuccess() && getStatus().getSeverity() == IStatus.ERROR) {
									GdbPlugin.getDefault().getLog().log(getStatus());
								}

								// Indicate that the pending request has completed
								fPendingRequests.remove(breakpoint);

								// Process the next pending update for this breakpoint
								if (fPendingBreakpoints.contains(breakpoint)) {
									fPendingBreakpoints.remove(breakpoint);
									breakpointChanged(breakpoint, delta);
								}
							}
						};
						countingRm.setDoneCount(getTrackedBreakpointTargetContexts().size());

						// Mark the breakpoint as being updated and go
						fPendingRequests.add(breakpoint);

						// Modify the breakpoint in all the execution contexts
						for (final IBreakpointsTargetDMContext dmc : getTrackedBreakpointTargetContexts()) {
							determineDebuggerPath(dmc, attrs, new RequestMonitor(getExecutor(), countingRm) {
								@Override
								protected void handleSuccess() {
									modifyBreakpoint(dmc, (ICBreakpoint) breakpoint, attrs, delta, countingRm);
								}
							});
						}
					}
				});

			} catch (CoreException e) {
			} catch (RejectedExecutionException e) {
			}
		}
	}

	/* (non-Javadoc)
	 * @see org.eclipse.debug.core.IBreakpointListener#breakpointRemoved(org.eclipse.debug.core.model.IBreakpoint, org.eclipse.core.resources.IMarkerDelta)
	 */
	@Override
	public void breakpointRemoved(final IBreakpoint breakpoint, IMarkerDelta delta) {

		if (supportsBreakpoint(breakpoint)) {
			try {
				getExecutor().execute(new DsfRunnable() {
					@Override
					public void run() {
						CountingRequestMonitor countingRm = new CountingRequestMonitor(getExecutor(), null) {
							@Override
							protected void handleError() {
								if (getStatus().getSeverity() == IStatus.ERROR) {
									GdbPlugin.getDefault().getLog().log(getStatus());
								}
							}
						};
						countingRm.setDoneCount(getTrackedBreakpointTargetContexts().size());

						// Remove the breakpoint in all the execution contexts
						for (IBreakpointsTargetDMContext dmc : getTrackedBreakpointTargetContexts()) {
							if (fPlatformToAttributesMaps.get(dmc).containsKey(breakpoint)) {
								uninstallBreakpoint(dmc, (ICBreakpoint) breakpoint, countingRm);
							}
						}
					}
				});
			} catch (RejectedExecutionException e) {
			}
		}
	}

	///////////////////////////////////////////////////////////////////////////
	// IServiceEventListener
	///////////////////////////////////////////////////////////////////////////

	//-------------------------------------------------------------------------
	// Breakpoints
	//-------------------------------------------------------------------------

	@DsfServiceEventHandler
	public void eventDispatched(BreakpointAddedEvent e) {
		// Nothing to do - already handled by breakpointAdded()
	}

	@DsfServiceEventHandler
	public void eventDispatched(BreakpointUpdatedEvent e) {
		// Nothing to do - already handled by breakpointChanged()
	}

	@DsfServiceEventHandler
	public void eventDispatched(BreakpointRemovedEvent e) {
		// Nothing to do - already handled by breakpointRemoved()
	}

	/*
	 * When a watchpoint goes out of scope, it is automatically removed from
	 * the back-end. To keep our internal state synchronized, we have to
	 * remove it from our breakpoints maps.
	 * Unfortunately, GDB doesn't generate the correct event...
	 */
	@DsfServiceEventHandler
	public void eventDispatched(MIWatchpointScopeEvent e) {
	}

	//-------------------------------------------------------------------------
	// Breakpoint actions
	//-------------------------------------------------------------------------

	/** @since 4.2 */
	@DsfServiceEventHandler
	public void eventDispatched(ISuspendedDMEvent e) {
		assert e instanceof IMIDMEvent;
		if (e instanceof IMIDMEvent) {
			Object miEvent = ((IMIDMEvent) e).getMIEvent();

			if (miEvent instanceof MIBreakpointHitEvent) {
				// This covers catchpoints, too
				MIBreakpointHitEvent evt = (MIBreakpointHitEvent) miEvent;
				performBreakpointAction(evt.getDMContext(), evt.getNumber());
				return;
			}

			if (miEvent instanceof MIWatchpointTriggerEvent) {
				MIWatchpointTriggerEvent evt = (MIWatchpointTriggerEvent) miEvent;
				performBreakpointAction(evt.getDMContext(), evt.getNumber());
				return;
			}
		}
	}

	/**
	 * @deprecated Replaced by the generic {@link #eventDispatched(ISuspendedDMEvent)}
	 */
	@Deprecated
	@DsfServiceEventHandler
	public void eventDispatched(SuspendedEvent e) {
	}

	private void performBreakpointAction(final IDMContext context, String number) {
		// Identify the platform breakpoint
		final ICBreakpoint breakpoint = findPlatformBreakpoint(number);

		if (breakpoint != null) {
			// Perform the actions asynchronously (otherwise we can have a deadlock...)
			new Job("Breakpoint action") { //$NON-NLS-1$
				{
					setSystem(true);
				}

				@Override
				protected IStatus run(IProgressMonitor monitor) {
					fBreakpointActionManager.executeActions(breakpoint,
							new BreakpointActionAdapter(getExecutor(), getServicesTracker(), context));
					return Status.OK_STATUS;
				}
			}.schedule();
		}
	}

	// Helper function to locate the platform breakpoint corresponding
	// to the target breakpoint/watchpoint that was just hit

	// FIXME: (Bug228703) Need a way to identify the correct context where the BP was hit
	private ICBreakpoint findPlatformBreakpoint(String targetBreakpointID) {
		Set<IBreakpointsTargetDMContext> targets = fBPToPlatformMaps.keySet();
		for (IBreakpointsTargetDMContext target : targets) {
			Map<IBreakpointDMContext, ICBreakpoint> bps = fBPToPlatformMaps.get(target);
			Set<IBreakpointDMContext> contexts = bps.keySet();
			for (IBreakpointDMContext context : contexts) {
				if (context instanceof MIBreakpointDMContext) {
					MIBreakpointDMContext ctx = (MIBreakpointDMContext) context;
					if (ctx.getReference().equals(targetBreakpointID)) {
						return bps.get(context);
					}
				}
			}
		}
		return null;
	}

	/**
	 * Returns a platform breakpoint corresponding to a given target breakpoint.
	 *
	 * @since 3.0
	 */
	public IBreakpoint findPlatformBreakpoint(IBreakpointDMContext bpContext) {
		if (bpContext instanceof MIBreakpointDMContext) {
			IBreakpointsTargetDMContext targetCtx = DMContexts.getAncestorOfType(bpContext,
					IBreakpointsTargetDMContext.class);
			if (targetCtx != null) {
				Map<IBreakpointDMContext, ICBreakpoint> bps = fBPToPlatformMaps.get(targetCtx);
				if (bps != null) {
					return bps.get(bpContext);
				}
			}
		}
		return null;
	}

	//-------------------------------------------------------------------------
	// Process/thread start/exit
	//-------------------------------------------------------------------------
	/**
	 * @noreference This method is not intended to be referenced by clients.
	 * @since 4.4
	 */
	@DsfServiceEventHandler
	public void eventDispatched(IStartedDMEvent e) {
	}

	private void setTargetFilter(ICBreakpoint breakpoint, IContainerDMContext containerDmc) {
		try {
			IDsfBreakpointExtension filterExt = getFilterExtension(breakpoint);
			if (filterExt.getThreadFilters(containerDmc) == null) {
				// Do this only if there wasn't already an entry, or else we would
				// erase the content of that previous entry.
				// This could theoretically happen if the targetFilter is set by
				// someone else, before this method is called.
				// Bug 433329
				filterExt.setTargetFilter(containerDmc);
			}

		} catch (CoreException e) {
		}
	}

	/**
	 * @noreference This method is not intended to be referenced by clients.
	 * @since 4.4
	 */
	@DsfServiceEventHandler
	public void eventDispatched(IExitedDMEvent e) {
		// original code moved to API removeTargetFilter (Bug 456959)
	}

	/**
	 * Remove process from the thread filtering of all breakpoints
	 * @since 4.6
	 */
	public void removeTargetFilter(IContainerDMContext containerDMC) {
		// We must get the list of breakpoints from the platform because our different
		// maps might already have been cleaned up
		IBreakpoint[] allBreakpoints = fBreakpointManager.getBreakpoints(fDebugModelId);
		for (IBreakpoint bp : allBreakpoints) {
			if (supportsBreakpoint(bp)) {
				removeTargetFilter((ICBreakpoint) bp, containerDMC);
			}
		}
	}

	private void removeTargetFilter(ICBreakpoint breakpoint, IContainerDMContext containerDmc) {
		try {
			IDsfBreakpointExtension filterExt = getFilterExtension(breakpoint);
			filterExt.removeTargetFilter(containerDmc);
		} catch (CoreException e) {
		}
	}

	private void removeAllTargetFilters(IBreakpointsTargetDMContext bpTargetDmc, ICBreakpoint breakpoint) {
		try {
			IDsfBreakpointExtension filterExt = getFilterExtension(breakpoint);
			IContainerDMContext[] targets = filterExt.getTargetFilters();
			for (IContainerDMContext target : targets) {
				if (bpTargetDmc.equals(target) || DMContexts.isAncestorOf(target, bpTargetDmc)) {
					filterExt.removeTargetFilter(target);
				}
			}
		} catch (CoreException e) {
		}
	}

	//-------------------------------------------------------------------------
	// Session exit
	//-------------------------------------------------------------------------

	/**
	 * @since 1.1
	 * @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(ICommandControlShutdownDMEvent e) {
		terminated();
	}

	private void terminated() {
		// Reset the breakpoint install count
		for (IBreakpointsTargetDMContext ctx : fPlatformToAttributesMaps.keySet()) {
			Map<ICBreakpoint, Map<String, Object>> breakpoints = fPlatformToAttributesMaps.get(ctx);
			clearBreakpointStatus(breakpoints.keySet().toArray(new ICBreakpoint[breakpoints.size()]), ctx);
		}
		// This will prevent Shutdown() from trying to remove bps from a
		// backend that has already shutdown
		fPlatformToAttributesMaps.clear();
	}

	/**
	 * @param bps
	 */
	private void clearBreakpointStatus(final ICBreakpoint[] bps, final IBreakpointsTargetDMContext ctx) {
		IWorkspaceRunnable wr = monitor -> {
			// For every platform breakpoint that has at least one target breakpoint installed
			// we must decrement the install count, for every target breakpoint.
			// Note that we cannot simply call resetInstallCount() because another
			// launch may be using the same platform breakpoint.
			Map<ICBreakpoint, Vector<IBreakpointDMContext>> breakpoints = fPlatformToBPsMaps.get(ctx);
			for (ICBreakpoint breakpoint : breakpoints.keySet()) {
				Vector<IBreakpointDMContext> targetBps = breakpoints.get(breakpoint);
				for (IBreakpointDMContext targetBp : targetBps) {
					decrementInstallCount(targetBp, breakpoint, new RequestMonitor(getExecutor(), null));
				}
			}
		};

		// Create the scheduling rule to clear all bp planted.
		ISchedulingRule rule = null;
		List<ISchedulingRule> markerRules = new ArrayList<>();
		for (ICBreakpoint bp : bps) {
			IMarker marker = bp.getMarker();
			if (marker != null) {
				ISchedulingRule markerRule = ResourcesPlugin.getWorkspace().getRuleFactory()
						.markerRule(marker.getResource());
				if (markerRule == null) {
					markerRules = null;
					break;
				} else {
					markerRules.add(markerRule);
				}
			}
		}
		if (markerRules != null) {
			rule = MultiRule.combine(markerRules.toArray(new ISchedulingRule[markerRules.size()]));
		}

		try {
			// Will run the workspace runnable on the current thread, which
			// is the DSF executor.
			ResourcesPlugin.getWorkspace().run(wr, rule, 0, null);
		} catch (CoreException e) {
			GdbPlugin.getDefault().getLog().log(e.getStatus());
		}

		new Job("Clear Breakpoints Status") { //$NON-NLS-1$
			@Override
			protected IStatus run(IProgressMonitor monitor) {
				// Clear any problem markers
				for (IMarker marker : fBreakpointMarkerProblems.values()) {
					if (marker != null) {
						try {
							marker.delete();
						} catch (CoreException e) {
						}
					}
				}
				fBreakpointMarkerProblems.clear();

				return Status.OK_STATUS;
			}
		}.schedule();
	}

	///////////////////////////////////////////////////////////////////////////
	// Support functions
	///////////////////////////////////////////////////////////////////////////

	/**
	 * Indicates if the platform breakpoint object [bp] is one we can deal with.
	 * For now, it boils down to whether it's a CDT Breakpoint (an
	 * ICBreakpoint). DSF can supports other (custom) types of breakpoints, but
	 * DSF-GDB is tied to ICBreakpoint.
	 *
	 * @param bp the platform breakpoint
	 * @return true if we support it; false otherwise
	 * @since 4.7
	 */
	protected boolean supportsBreakpoint(IBreakpoint bp) {
		if (bp instanceof ICBreakpoint && bp.getModelIdentifier().equals(fDebugModelId)) {
			IMarker marker = bp.getMarker();
			if (marker != null) {
				return true;
			}
		}
		return false;
	}

	/**
	 * determineDebuggerPath
	 *
	 * Adds the path to the source file to the set of attributes
	 * (for the debugger).
	 *
	 * @param dmc
	 * @param attributes
	 * @param rm
	 */
	private void determineDebuggerPath(IBreakpointsTargetDMContext dmc, final Map<String, Object> attributes,
			final RequestMonitor rm) {
		String hostPath = (String) attributes.get(ICBreakpoint.SOURCE_HANDLE);

		if (hostPath != null) {

			ISourceLookupDMContext srcDmc = DMContexts.getAncestorOfType(dmc, ISourceLookupDMContext.class);
			if (srcDmc != null) {
				fSourceLookup.getDebuggerPath(srcDmc, hostPath, new DataRequestMonitor<String>(getExecutor(), rm) {
					@Override
					protected void handleSuccess() {
						attributes.put(ATTR_DEBUGGER_PATH, adjustDebuggerPath(getData()));
						rm.done();
					}
				});
			} else {
				// Source lookup not available for given context, use the host
				// path for the debugger path.
				attributes.put(ATTR_DEBUGGER_PATH, adjustDebuggerPath(hostPath));
				rm.done();
			}
		} else {
			// Some types of breakpoints do not require a path
			// (e.g. watchpoints)
			rm.done();
		}
	}

	/**
	 * For some platforms (MinGW) the debugger path needs to be adjusted to work
	 * with earlier GDB versions.
	 * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=232415
	 *
	 * @param path
	 *            the absolute path to the source file
	 * @return the adjusted path provided by the breakpoints service.
	 */
	String adjustDebuggerPath(String path) {
		return (fBreakpoints instanceof IMIBreakpointPathAdjuster)
				? ((IMIBreakpointPathAdjuster) fBreakpoints).adjustDebuggerPath(path)
				: path;
	}

	/**
	 * Determine the set of modified attributes.
	 * Elementary set operations in full action :-)
	 *
	 * @param oldAttributes
	 * @param newAttributes
	 * @return
	 */
	private Map<String, Object> determineAttributesDelta(Map<String, Object> oldAttributes,
			Map<String, Object> newAttributes) {

		Map<String, Object> delta = new HashMap<>();

		Set<String> oldKeySet = oldAttributes.keySet();
		Set<String> newKeySet = newAttributes.keySet();

		Set<String> commonKeys = new HashSet<>(newKeySet);
		commonKeys.retainAll(oldKeySet);
		Set<String> addedKeys = new HashSet<>(newKeySet);
		addedKeys.removeAll(oldKeySet);
		Set<String> removedKeys = new HashSet<>(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 convertToPlatformAttributes(delta);
	}

	/**
	 * Converts ICBreakpoint attributes to IBreakpoints attributes.
	 *
	 * @param cdtAttributes
	 * @return
	 * @since 5.3
	 */
	protected Map<String, Object> convertToPlatformAttributes(Map<String, Object> cdtAttributes) {

		Map<String, Object> result = new HashMap<>();

		// IBreakpoint attributes
		if (cdtAttributes.containsKey(ATTR_DEBUGGER_PATH)) {
			result.put(MIBreakpoints.FILE_NAME, cdtAttributes.get(ATTR_DEBUGGER_PATH));
		}

		if (cdtAttributes.containsKey(IMarker.LINE_NUMBER)) {
			result.put(MIBreakpoints.LINE_NUMBER, cdtAttributes.get(IMarker.LINE_NUMBER));
		}

		if (cdtAttributes.containsKey(BreakpointActionManager.BREAKPOINT_ACTION_ATTRIBUTE)) {
			result.put(MIBreakpoints.COMMANDS, cdtAttributes.get(BreakpointActionManager.BREAKPOINT_ACTION_ATTRIBUTE));
		}

		// ICLineBreakpoint attributes
		if (cdtAttributes.containsKey(ICLineBreakpoint.FUNCTION)) {
			result.put(MIBreakpoints.FUNCTION, cdtAttributes.get(ICLineBreakpoint.FUNCTION));
		}

		if (cdtAttributes.containsKey(ICLineBreakpoint.ADDRESS)) {
			result.put(MIBreakpoints.ADDRESS, cdtAttributes.get(ICLineBreakpoint.ADDRESS));
		}

		// ICBreakpoint attributes
		if (cdtAttributes.containsKey(ICBreakpoint.CONDITION)) {
			result.put(MIBreakpoints.CONDITION, cdtAttributes.get(ICBreakpoint.CONDITION));
		}

		if (cdtAttributes.containsKey(ICBreakpoint.IGNORE_COUNT)) {
			result.put(MIBreakpoints.IGNORE_COUNT, cdtAttributes.get(ICBreakpoint.IGNORE_COUNT));
		}

		if (cdtAttributes.containsKey(ICTracepoint.PASS_COUNT)) {
			result.put(MIBreakpoints.PASS_COUNT, cdtAttributes.get(ICTracepoint.PASS_COUNT));
		}

		if (cdtAttributes.containsKey(ICBreakpoint.ENABLED)) {
			result.put(MIBreakpoints.IS_ENABLED, cdtAttributes.get(ICBreakpoint.ENABLED));
		}

		if (cdtAttributes.containsKey(ICBreakpointType.TYPE)) {
			result.put(MIBreakpoints.BREAKPOINT_TYPE, cdtAttributes.get(ICBreakpointType.TYPE));
		}

		// ICWatchpoint attributes
		if (cdtAttributes.containsKey(ICWatchpoint.EXPRESSION)) {
			result.put(MIBreakpoints.EXPRESSION, cdtAttributes.get(ICWatchpoint.EXPRESSION));
		}

		if (cdtAttributes.containsKey(ICWatchpoint.READ)) {
			result.put(MIBreakpoints.READ, cdtAttributes.get(ICWatchpoint.READ));
		}

		if (cdtAttributes.containsKey(ICWatchpoint.WRITE)) {
			result.put(MIBreakpoints.WRITE, cdtAttributes.get(ICWatchpoint.WRITE));
		}

		// Threads
		if (cdtAttributes.containsKey(ATTR_THREAD_FILTER)) {
			result.put(ATTR_THREAD_FILTER, cdtAttributes.get(ATTR_THREAD_FILTER));
		}

		// For IDynamicPrintf
		if (cdtAttributes.containsKey(ICDynamicPrintf.PRINTF_STRING)) {
			result.put(MIBreakpoints.PRINTF_STRING, cdtAttributes.get(ICDynamicPrintf.PRINTF_STRING));
		}

		return result;
	}

	/**
	 * Figure out the corresponding number of back-end breakpoints
	 * Even though the thread IDs are usually integers, they are
	 * stored as strings in CBreakpoints.
	 *
	 * @param attributes
	 * @return
	 */
	@SuppressWarnings("unchecked")
	private Set<String> getThreads(Map<String, Object> attributes) {
		Set<String> threads = (Set<String>) attributes.get(ATTR_THREAD_FILTER);
		if (threads == null) {
			threads = new HashSet<>();
			threads.add("0"); // Thread 0 means all threads //$NON-NLS-1$
		}
		return threads;
	}

	/**
	 * Get the list of threads from the breakpoint's thread filtering mechanism
	 *
	 * @param breakpoint
	 * @return
	 */
	private Set<String> extractThreads(IBreakpointsTargetDMContext bpTargetDmc, ICBreakpoint breakpoint) {
		Set<String> results = new HashSet<>();

		if (supportsThreads(breakpoint)) {
			List<IExecutionDMContext[]> threads = new ArrayList<>(1);

			try {
				// Retrieve all existing targets.
				// Note that these targets can be from different debugging sessions since
				// they are associated with the platform breakpoint.
				IDsfBreakpointExtension filterExtension = getFilterExtension(breakpoint);
				IContainerDMContext[] procTargets = filterExtension.getTargetFilters();

				// Extract the thread IDs
				for (IContainerDMContext procDmc : procTargets) {
					// Look for a target/process that belongs to our session
					if (procDmc.equals(bpTargetDmc) || DMContexts.isAncestorOf(procDmc, bpTargetDmc)) {
						IExecutionDMContext[] threadFilters = filterExtension.getThreadFilters(procDmc);
						if (threadFilters == null) {
							// The breakpoint applies to the entire process.
							// For GDB < 7.4, we set the thread to 0 to indicate that the breakpoint
							// is global for this process.
							// For GDB >= 7.4, things are more complicated.  There will be one bp for all
							// processes, so by setting the thread to 0, the breakpoint will apply
							// to all threads of all processes.  We don't have a choice as there is no
							// way to tell GDB to apply to all threads (including any new ones that will
							// be created) for a single process.
							// So, in this case, if the bp applies to all threads of one process, it will
							// automatically apply to all threads of all processes
							results.add("0"); //$NON-NLS-1$
							return results;
						} else {
							threads.add(threadFilters);
						}
					}
				}
			} catch (CoreException e) {
				// Error with the thread filtering.  Default to all threads.
				results.add("0"); //$NON-NLS-1$
				return results;
			}

			// If there are no threads to filter on, it means the bp applies to the entire process.
			if (threads.isEmpty()) {
				results.add("0"); //$NON-NLS-1$
				return results;
			}

			for (IExecutionDMContext[] targetThreads : threads) {
				if (targetThreads != null) {
					for (IExecutionDMContext thread : targetThreads) {
						if (thread instanceof IMIExecutionDMContext) {
							results.add(((IMIExecutionDMContext) thread).getThreadId());
						} else {
							// If any of the threads is not an IMIExecutionDMContext,
							// we don't support thread filters at all.
							results.clear();
							results.add("0"); //$NON-NLS-1$
							return results;
						}
					}
				} else {
					// Should not happen
					assert false;
				}
			}
		} else {
			results.add("0"); //$NON-NLS-1$
		}

		return results;
	}

	///////////////////////////////////////////////////////////////////////////
	// Non-generic (MI-specific) functions
	///////////////////////////////////////////////////////////////////////////

	/**
	 * Create a collection of DSF-GDB specific breakpoint properties given a
	 * platform/CDT breakpoint object and its properties. Basically, this
	 * determines the set of MI-specific properties to be used in installing the
	 * given breakpoint.
	 *
	 * @param breakpoint
	 *            the platform breakpoint object; was created by CDT
	 * @param attributes
	 *            the breakpoint's properties. By allowing this to be passed in
	 *            (rather than us calling
	 *            IBreakpoint#getMarker()#getProperties()), we allow the caller
	 *            to specify additional/modified properties.
	 * @return a property bag containing the corresponding DSF-GDB properties
	 */
	protected Map<String, Object> convertToTargetBreakpoint(ICBreakpoint breakpoint, Map<String, Object> attributes) {

		Map<String, Object> properties = new HashMap<>();

		if (breakpoint instanceof ICWatchpoint) {
			properties.put(MIBreakpoints.BREAKPOINT_TYPE, MIBreakpoints.WATCHPOINT);
			properties.put(MIBreakpoints.EXPRESSION, attributes.get(ICWatchpoint.EXPRESSION));
			properties.put(MIBreakpoints.READ, attributes.get(ICWatchpoint.READ));
			properties.put(MIBreakpoints.WRITE, attributes.get(ICWatchpoint.WRITE));
			properties.put(MIBreakpoints.RANGE, attributes.get(ICWatchpoint2.RANGE));
			properties.put(MIBreakpoints.MEMSPACE, attributes.get(ICWatchpoint2.MEMORYSPACE));
		} else if (breakpoint instanceof ICLineBreakpoint) {
			properties.put(MIBreakpoints.BREAKPOINT_TYPE, MIBreakpoints.BREAKPOINT);
			properties.put(MIBreakpoints.FILE_NAME, attributes.get(ATTR_DEBUGGER_PATH));
			properties.put(MIBreakpoints.LINE_NUMBER, attributes.get(IMarker.LINE_NUMBER));
			properties.put(MIBreakpoints.FUNCTION, attributes.get(ICLineBreakpoint.FUNCTION));
			properties.put(MIBreakpoints.ADDRESS, attributes.get(ICLineBreakpoint.ADDRESS));
			properties.put(MIBreakpoints.COMMANDS, attributes.get(BreakpointActionManager.BREAKPOINT_ACTION_ATTRIBUTE));

			if (breakpoint instanceof ICTracepoint) {
				// A tracepoint is a LineBreakpoint, but needs its own type
				properties.put(MIBreakpoints.BREAKPOINT_TYPE, MIBreakpoints.TRACEPOINT);
				properties.put(MIBreakpoints.PASS_COUNT, attributes.get(ICTracepoint.PASS_COUNT));
			} else if (breakpoint instanceof ICDynamicPrintf) {
				// A DynamicPrintf is a LineBreakpoint, but needs its own type
				properties.put(MIBreakpoints.BREAKPOINT_TYPE, MIBreakpoints.DYNAMICPRINTF);
				properties.put(MIBreakpoints.PRINTF_STRING, attributes.get(ICDynamicPrintf.PRINTF_STRING));
			}
		} else if (breakpoint instanceof ICEventBreakpoint) {
			properties.put(MIBreakpoints.BREAKPOINT_TYPE, MIBreakpoints.CATCHPOINT);
			properties.put(MIBreakpoints.CATCHPOINT_TYPE, GdbCatchpoints
					.eventToGdbCatchpointKeyword((String) attributes.get(ICEventBreakpoint.EVENT_TYPE_ID)));

			String arg = (String) attributes.get(ICEventBreakpoint.EVENT_ARG);
			String[] args;
			if ((arg != null) && (arg.length() != 0)) {
				args = new String[1];
				args[0] = arg;
			} else {
				args = new String[0];
			}
			properties.put(MIBreakpoints.CATCHPOINT_ARGS, args);
		} else {
			assert false : "platform breakpoint is of an unexpected type: " + breakpoint.getClass().getName(); //$NON-NLS-1$
		}

		// Common fields
		properties.put(MIBreakpoints.CONDITION, attributes.get(ICBreakpoint.CONDITION));
		properties.put(MIBreakpoints.IGNORE_COUNT, attributes.get(ICBreakpoint.IGNORE_COUNT));
		properties.put(MIBreakpoints.IS_ENABLED, attributes.get(ICBreakpoint.ENABLED));
		properties.put(MIBreakpointDMData.THREAD_ID, attributes.get(ATTR_THREAD_ID));

		// checks for the breakpoint type, and adds the hardware/temporary flags
		Object breakpointType = attributes.get(ICBreakpointType.TYPE);
		if (breakpointType instanceof Integer) {
			boolean isHardware = ((Integer) breakpointType & ICBreakpointType.HARDWARE) == ICBreakpointType.HARDWARE;
			boolean isTemporary = ((Integer) breakpointType & ICBreakpointType.TEMPORARY) == ICBreakpointType.TEMPORARY;
			properties.put(MIBreakpointDMData.IS_HARDWARE, isHardware);
			properties.put(MIBreakpointDMData.IS_TEMPORARY, isTemporary);
		}

		// Adjust for "skip-all"
		// Tracepoints and dynamic printf are not affected by "skip-all"
		if (!(breakpoint instanceof ICTracepoint) && !(breakpoint instanceof ICDynamicPrintf)
				&& !fBreakpointManager.isEnabled()) {
			properties.put(MIBreakpoints.IS_ENABLED, false);
		}

		return properties;
	}

	/**
	 * Determine if the modified attributes necessitate
	 * a breakpoint removal/re-installation
	 *
	 * @param delta
	 * @return
	 */
	protected boolean needsResinstallation(Map<String, Object> delta) {

		// Check if there is any modified attribute
		if (delta == null) {
			return false;
		}

		// Check the "critical" attributes
		if (delta.containsKey(ATTR_DEBUGGER_PATH) // File name
				|| delta.containsKey(MIBreakpoints.LINE_NUMBER) // Line number
				|| delta.containsKey(MIBreakpoints.BREAKPOINT_TYPE) // breakpoint type
				|| delta.containsKey(MIBreakpoints.FUNCTION) // Function name
				|| delta.containsKey(MIBreakpoints.ADDRESS) // Absolute address
				|| delta.containsKey(ATTR_THREAD_FILTER) // Thread ID
				|| delta.containsKey(MIBreakpoints.EXPRESSION) // Watchpoint expression
				|| delta.containsKey(MIBreakpoints.READ) // Watchpoint type
				|| delta.containsKey(MIBreakpoints.WRITE) // Watchpoint type
				|| delta.containsKey(MIBreakpoints.PRINTF_STRING)) {// Dprintf string
			return true;
		}

		return false;
	}

	/**
	 * @param breakpoint
	 * @param oldValues
	 */
	protected void rollbackAttributes(ICBreakpoint breakpoint, IMarkerDelta oldValues) {

		try {
			String newCondition = breakpoint.getCondition();
			if (newCondition == null) {
				newCondition = NULL_STRING;
			}
			String oldCondition = (oldValues != null) ? oldValues.getAttribute(ICBreakpoint.CONDITION, NULL_STRING)
					: NULL_STRING;
			if (!oldCondition.equals(newCondition)) {
				breakpoint.setCondition(oldCondition);
			} else {
				breakpoint.setCondition(NULL_STRING);
			}
		} catch (CoreException e) {
		}
	}

	/**
	 * Indicates if the back-end supports multiple threads for
	 * this type of breakpoint
	 *
	 * @param breakpoint
	 */
	protected boolean supportsThreads(ICBreakpoint breakpoint) {

		return !(breakpoint instanceof ICWatchpoint);
	}

	/**
	 * Returns whether the breakpoint is filtered for the given target.
	 */
	private boolean isBreakpointEntirelyFiltered(IBreakpointsTargetDMContext bpTargetDmc, ICBreakpoint breakpoint) {
		try {
			IContainerDMContext[] procTargets = getFilterExtension(breakpoint).getTargetFilters();
			for (IContainerDMContext procDmc : procTargets) {
				if (procDmc.equals(bpTargetDmc) || DMContexts.isAncestorOf(procDmc, bpTargetDmc)) {
					return false;
				}
			}
		} catch (CoreException e) {
		}
		return true;
	}

	/**
	 * @since 4.2
	 */
	public void addBreakpointsTrackingListener(IMIBreakpointsTrackingListener listener) {
		fTrackingListeners.add(listener);
	}

	/**
	 * @since 4.2
	 */
	public void removeBreakpointsTrackingListener(IMIBreakpointsTrackingListener listener) {
		fTrackingListeners.remove(listener);
	}

	private String[] compareAttributes(Map<String, Object> oldAttr, Map<String, Object> newAttr, String[] exclude) {
		List<String> list = new ArrayList<>();
		Set<String> names = new HashSet<>(oldAttr.keySet());
		names.addAll(newAttr.keySet());
		for (String n : names) {
			if (exclude != null && Arrays.asList(exclude).contains(n)) {
				continue;
			}
			Object oldValue = oldAttr.get(n);
			if (oldValue != null && !oldValue.equals(newAttr.get(n))) {
				list.add(n);
			} else if (oldValue == null) {
				if (newAttr.get(n) != null) {
					list.add(n);
				}
			}
		}
		return list.toArray(new String[list.size()]);
	}
}
