blob: 8725666b5ef16337d727208910ead4720f0baa83 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2020 IBM Corporation 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.debug.internal.core;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IMarkerDelta;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.debug.core.DebugException;
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.IBreakpointsListener;
import org.eclipse.debug.core.model.IBreakpoint;
import org.eclipse.debug.core.model.IBreakpointImportParticipant;
import org.eclipse.debug.core.model.ITriggerPoint;
/**
* The breakpoint manager manages all registered breakpoints for the Debug plug-in. It is
* instantiated by the Debug plug-in at startup.
* <p>
* <strong>Note:</strong> This manager is created while the Debug plug-in is started, but it
* will not automatically be initialized. Client code that expects markers and breakpoints to be
* initialized must call {@link #ensureInitialized()}.
* </p>
*
* @see IBreakpointManager
*/
public class BreakpointManager implements IBreakpointManager, IResourceChangeListener {
/**
* Constants for breakpoint add/remove/change updates
*/
private final static int ADDED = 0;
private final static int REMOVED = 1;
private final static int CHANGED = 2;
/**
* A collection of breakpoints registered with this manager.
*/
private Vector<IBreakpoint> fBreakpoints;
/**
* Map of breakpoint import participants.
* Map has the form:
* <pre>Map(String - marker_id, List of {@link IBreakpointImportParticipant})</pre>
*/
private HashMap<String, ArrayList<BreakpointImportParticipantDelegate>> fImportParticipants;
/**
* A system default import participant that performs legacy comparison support
* when no participants are provided for a given type.
*
* @since 3.5
*/
private IBreakpointImportParticipant fDefaultParticipant;
/**
* A collection of breakpoint markers that have received a POST_CHANGE notification
* that they have changed before a POST_BUILD notification of add. This allows us
* to tell if a marker has been created & changed since the breakpoint has been
* registered (see bug 138473).
*/
private final Set<IMarker> fPostChangMarkersChanged = new HashSet<>();
/**
* A collection of breakpoint markers that have received a POST_BUILD notification
* of being added.
*/
private final Set<IMarker> fPostBuildMarkersAdded = new HashSet<>();
/**
* Collection of breakpoints being added currently. Used to
* suppress change notification of "REGISTERED" attribute when
* being added.
*/
private final List<IBreakpoint> fSuppressChange = new ArrayList<>();
/**
* A table of breakpoint extension points, keyed by
* marker type
* key: a marker type
* value: the breakpoint extension which corresponds to that marker type
*/
private final HashMap<String, IConfigurationElement> fBreakpointExtensions;
/**
* Collection of markers that associates markers to breakpoints
* key: a marker
* value: the breakpoint which contains that marker
*/
private final HashMap<IMarker, IBreakpoint> fMarkersToBreakpoints;
/**
* Collection of breakpoint listeners.
*/
private final ListenerList<IBreakpointListener> fBreakpointListeners = new ListenerList<>();
/**
* Collection of (plural) breakpoint listeners.
*/
private ListenerList<IBreakpointsListener> fBreakpointsListeners= new ListenerList<>();
/**
* Singleton resource delta visitor which handles marker
* additions, changes, and removals.
*/
private static BreakpointManagerVisitor fgVisitor;
/**
* Collection of breakpoint manager listeners which are
* notified when this manager's state changes.
*/
private final ListenerList<IBreakpointManagerListener> fBreakpointManagerListeners = new ListenerList<>();
/**
* Breakpoint which acts a the triggering point in a workspace.
*/
private final Set<IBreakpoint> fTriggerPointBreakpointList = new LinkedHashSet<>();
/**
* Trigger points disabled by system after the first trigger point is
* enabled in a workspace.
*/
private final Set<IBreakpoint> fTriggerPointDisabledList = new LinkedHashSet<>();
/**
* Listens to POST_CHANGE notifications of breakpoint markers to detect when
* a breakpoint is added & changed before the POST_BUILD add notification is
* sent.
*/
class PostChangeListener implements IResourceChangeListener {
private PostChangeVisitor fVisitor = new PostChangeVisitor();
@Override
public void resourceChanged(IResourceChangeEvent event) {
IResourceDelta delta= event.getDelta();
if (delta != null) {
try {
delta.accept(fVisitor);
} catch (CoreException ce) {
DebugPlugin.log(ce);
}
}
}
}
/**
* Default implementation of a breakpoint import participant
*
* @since 3.5
*/
static class DefaultImportParticipant implements IBreakpointImportParticipant {
@Override
public boolean matches(Map<String, Object> attributes, IBreakpoint breakpoint) throws CoreException {
//perform legacy comparison
IMarker marker = breakpoint.getMarker();
String type = (String) attributes.get("type"); //$NON-NLS-1$
Integer line = (Integer) attributes.get(IMarker.LINE_NUMBER);
Object localline = marker.getAttribute(IMarker.LINE_NUMBER);
String localtype = marker.getType();
if (type.equals(localtype)) {
if(line != null && line.equals(localline)) {
return true;
}
else if(line == null) {
return true;
}
}
return false;
}
@Override
public void verify(IBreakpoint breakpoint) throws CoreException {}
}
/**
* The listener
*/
private PostChangeListener fPostChangeListener = new PostChangeListener();
class PostChangeVisitor implements IResourceDeltaVisitor {
@Override
public boolean visit(IResourceDelta delta) throws CoreException {
if (delta == null) {
return false;
}
for (IMarkerDelta markerDelta : delta.getMarkerDeltas()) {
if (markerDelta.isSubtypeOf(IBreakpoint.BREAKPOINT_MARKER)) {
switch (markerDelta.getKind()) {
case IResourceDelta.ADDED :
break;
case IResourceDelta.REMOVED :
break;
case IResourceDelta.CHANGED :
IMarker marker = markerDelta.getMarker();
synchronized (fPostChangMarkersChanged) {
if (!fPostBuildMarkersAdded.contains(marker)) {
fPostChangMarkersChanged.add(marker);
}
}
break;
default:
break;
}
}
}
return true;
}
}
/**
* Constructs a new breakpoint manager.
*/
public BreakpointManager() {
fMarkersToBreakpoints = new HashMap<>(10);
fBreakpointExtensions = new HashMap<>(15);
}
/**
* Loads all the breakpoints on the given resource.
*
* @param resource the resource which contains the breakpoints
* @param notify whether to notify of the breakpoint additions
* @throws CoreException if a problem is encountered
*/
private void loadBreakpoints(IResource resource, boolean notify) throws CoreException {
initBreakpointExtensions();
List<IBreakpoint> added = new ArrayList<>();
for (IMarker marker : getPersistedMarkers(resource)) {
try {
IBreakpoint breakpoint = createBreakpoint(marker);
synchronized (fPostChangMarkersChanged) {
fPostBuildMarkersAdded.add(marker);
}
if (breakpoint.isRegistered()) {
added.add(breakpoint);
}
if (breakpoint instanceof ITriggerPoint && ((ITriggerPoint) breakpoint).isTriggerPoint()) {
addTriggerPoint(breakpoint);
}
} catch (DebugException e) {
DebugPlugin.log(e);
}
}
addBreakpoints(added.toArray(new IBreakpoint[added.size()]), notify);
}
/**
* Returns the persisted markers associated with the given resource.
*
* Delete any invalid breakpoint markers. This is done at startup rather
* than shutdown, since the changes made at shutdown are not persisted as
* the workspace state has already been saved. See bug 7683.
*
* Since the <code>TRANSIENT</code> marker attribute/feature has been added,
* we no longer have to manually delete non-persisted markers - the platform
* does this for us (at shutdown, transient markers are not saved). However,
* the code is still present to delete non-persisted markers from old
* workspaces.
* @param resource the {@link IResource} to get markers for
* @return the complete listing of persisted markers for the given {@link IResource}
* @throws CoreException if a problem is encountered
*/
protected IMarker[] getPersistedMarkers(IResource resource) throws CoreException {
final List<IMarker> delete = new ArrayList<>();
List<IMarker> persisted = new ArrayList<>();
for (IMarker marker : resource.findMarkers(IBreakpoint.BREAKPOINT_MARKER, true, IResource.DEPTH_INFINITE)) {
// ensure the marker has a valid model identifier attribute
// and delete the breakpoint if not
String modelId = marker.getAttribute(IBreakpoint.ID, null);
if (modelId == null) {
// marker with old/invalid format - delete
delete.add(marker);
} else if (!marker.getAttribute(IBreakpoint.PERSISTED, true)) {
// the breakpoint is marked as not to be persisted,
// schedule for deletion
delete.add(marker);
} else {
persisted.add(marker);
}
}
// delete any markers that are not to be restored
if (!delete.isEmpty()) {
final IMarker[] delMarkers = delete.toArray(new IMarker[delete.size()]);
IWorkspaceRunnable wr = pm -> {
for (IMarker marker : delMarkers) {
marker.delete();
}
};
new BreakpointManagerJob(wr).schedule();
}
return persisted.toArray(new IMarker[persisted.size()]);
}
/**
* Removes this manager as a resource change listener
* and removes all breakpoint listeners.
*/
public void shutdown() {
getWorkspace().removeResourceChangeListener(this);
getWorkspace().removeResourceChangeListener(fPostChangeListener);
fBreakpointListeners.clear();
fBreakpointsListeners.clear();
fBreakpointManagerListeners.clear();
if(fImportParticipants != null) {
fImportParticipants.clear();
fImportParticipants = null;
fDefaultParticipant = null;
}
if(fBreakpoints != null) {
fBreakpoints.clear();
fBreakpoints = null;
}
if(fMarkersToBreakpoints != null) {
fMarkersToBreakpoints.clear();
}
}
/**
* Find the defined breakpoint extensions and cache them for use in recreating
* breakpoints from markers.
*/
private void initBreakpointExtensions() {
IExtensionPoint ep = Platform.getExtensionRegistry().getExtensionPoint(DebugPlugin.getUniqueIdentifier(), DebugPlugin.EXTENSION_POINT_BREAKPOINTS);
for (IConfigurationElement element : ep.getConfigurationElements()) {
String markerType = element.getAttribute(IConfigurationElementConstants.MARKER_TYPE);
String className = element.getAttribute(IConfigurationElementConstants.CLASS);
if (markerType == null) {
DebugPlugin.log(new Status(IStatus.ERROR, DebugPlugin.getUniqueIdentifier(), DebugPlugin.INTERNAL_ERROR, "Breakpoint extension " + element.getDeclaringExtension().getUniqueIdentifier() + " missing required attribute: markerType", null)); //$NON-NLS-1$ //$NON-NLS-2$
} else if (className == null){
DebugPlugin.log(new Status(IStatus.ERROR, DebugPlugin.getUniqueIdentifier(), DebugPlugin.INTERNAL_ERROR, "Breakpoint extension " + element.getDeclaringExtension().getUniqueIdentifier() + " missing required attribute: class", null)); //$NON-NLS-1$ //$NON-NLS-2$
} else {
fBreakpointExtensions.put(markerType, element);
}
}
}
/**
* Convenience method to get the workspace
* @return the default {@link IWorkspace}
*/
private IWorkspace getWorkspace() {
return ResourcesPlugin.getWorkspace();
}
/**
* @see IBreakpointManager#getBreakpoint(IMarker)
*/
@Override
public IBreakpoint getBreakpoint(IMarker marker) {
// ensure that breakpoints are initialized
getBreakpoints0();
return fMarkersToBreakpoints.get(marker);
}
@Override
public IBreakpoint[] getBreakpoints() {
IBreakpoint[] temp = new IBreakpoint[0];
Vector<IBreakpoint> breakpoints = getBreakpoints0();
synchronized (breakpoints) {
temp = new IBreakpoint[breakpoints.size()];
breakpoints.copyInto(temp);
}
return temp;
}
/**
* Ensures that this manager is initialized.
* <p>
* This manager is created while the Debug plug-in is started, but it will not automatically
* be initialized. Client code that expects markers and breakpoints to be initialized must call
* this method.
* </p>
*
* @since 3.8
*/
public void ensureInitialized() {
getBreakpoints0();
}
/**
* The BreakpointManager waits to load the breakpoints
* of the workspace until a request is made to retrieve the
* breakpoints.
* @return the underlying {@link Vector} of breakpoints
*/
private synchronized Vector<IBreakpoint> getBreakpoints0() {
if (fBreakpoints == null) {
initializeBreakpoints();
}
return fBreakpoints;
}
@Override
public IBreakpoint[] getBreakpoints(String modelIdentifier) {
Vector<IBreakpoint> allBreakpoints = getBreakpoints0();
synchronized (allBreakpoints) {
ArrayList<IBreakpoint> temp = new ArrayList<>(allBreakpoints.size());
for (IBreakpoint breakpoint : allBreakpoints) {
String id= breakpoint.getModelIdentifier();
if (id != null && id.equals(modelIdentifier)) {
temp.add(breakpoint);
}
}
return temp.toArray(new IBreakpoint[temp.size()]);
}
}
/**
* Loads the list of breakpoints from the breakpoint markers in the
* workspace. Start listening to resource deltas.
*/
private void initializeBreakpoints() {
setBreakpoints(new Vector<>(10));
try {
loadBreakpoints(getWorkspace().getRoot(), false);
getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.POST_BUILD);
getWorkspace().addResourceChangeListener(fPostChangeListener, IResourceChangeEvent.POST_CHANGE);
} catch (CoreException ce) {
DebugPlugin.log(ce);
setBreakpoints(new Vector<>(0));
}
}
/**
* @see IBreakpointManager#isRegistered(IBreakpoint)
*/
@Override
public boolean isRegistered(IBreakpoint breakpoint) {
return getBreakpoints0().contains(breakpoint);
}
/**
* @see IBreakpointManager#removeBreakpoint(IBreakpoint, boolean)
*/
@Override
public void removeBreakpoint(IBreakpoint breakpoint, boolean delete) throws CoreException {
removeBreakpoints(new IBreakpoint[]{breakpoint}, delete);
}
/**
* @see IBreakpointManager#removeBreakpoints(IBreakpoint[], boolean)
*/
@Override
public void removeBreakpoints(IBreakpoint[] breakpoints, final boolean delete) throws CoreException {
final List<IBreakpoint> remove = new ArrayList<>(breakpoints.length);
List<IBreakpoint> bps = getBreakpoints0();
for (IBreakpoint breakpoint : breakpoints) {
if (bps.contains(breakpoint)) {
remove.add(breakpoint);
}
}
if (!remove.isEmpty()) {
for (IBreakpoint breakpoint : remove) {
bps.remove(breakpoint);
fMarkersToBreakpoints.remove(breakpoint.getMarker());
// If the breakpoint is a trigger point, remove else do nothing.
removeTriggerPoint(breakpoint);
}
fireUpdate(remove, null, REMOVED);
refreshTriggerpointDisplay();
IWorkspaceRunnable r = monitor -> {
for (IBreakpoint breakpoint : remove) {
if (delete) {
breakpoint.delete();
} else {
// if the breakpoint is being removed from the manager
// because the project is closing, the breakpoint should
// remain as registered, otherwise, the breakpoint should
// be marked as unregistered
IMarker marker = breakpoint.getMarker();
if (marker.exists()) {
IProject project = breakpoint.getMarker().getResource().getProject();
if (project == null || project.isOpen()) {
breakpoint.setRegistered(false);
}
}
}
}
};
getWorkspace().run(r, null, 0, null);
}
}
/**
* Create a breakpoint for the given marker. The created breakpoint
* is of the type specified in the breakpoint extension associated
* with the given marker type.
*
* @param marker marker to create a breakpoint for
* @return a breakpoint on this marker
* @exception DebugException if breakpoint creation fails. Reasons for
* failure include:
* <ol>
* <li>The breakpoint manager cannot determine what kind of breakpoint
* to instantiate for the given marker type</li>
* <li>A lower level exception occurred while accessing the given marker</li>
* </ol>
*/
public IBreakpoint createBreakpoint(IMarker marker) throws DebugException {
IBreakpoint breakpoint= fMarkersToBreakpoints.get(marker);
if (breakpoint != null) {
return breakpoint;
}
try {
IConfigurationElement config = fBreakpointExtensions.get(marker.getType());
if (config == null) {
throw new DebugException(new Status(IStatus.ERROR, DebugPlugin.getUniqueIdentifier(),
DebugException.CONFIGURATION_INVALID, MessageFormat.format(DebugCoreMessages.BreakpointManager_Missing_breakpoint_definition, new Object[] { marker.getType() }), null));
}
Object object = config.createExecutableExtension(IConfigurationElementConstants.CLASS);
if (object instanceof IBreakpoint) {
breakpoint = (IBreakpoint)object;
breakpoint.setMarker(marker);
} else {
DebugPlugin.log(new Status(IStatus.ERROR, DebugPlugin.getUniqueIdentifier(), DebugPlugin.INTERNAL_ERROR, "Breakpoint extension " + config.getDeclaringExtension().getUniqueIdentifier() + " missing required attribute: class", null)); //$NON-NLS-1$ //$NON-NLS-2$
}
return breakpoint;
} catch (CoreException e) {
throw new DebugException(e.getStatus());
}
}
/**
* @see IBreakpointManager#addBreakpoint(IBreakpoint)
*/
@Override
public void addBreakpoint(IBreakpoint breakpoint) throws CoreException {
addBreakpoints(new IBreakpoint[]{breakpoint});
}
/**
* @see IBreakpointManager#addBreakpoints(IBreakpoint[])
*/
@Override
public void addBreakpoints(IBreakpoint[] breakpoints) throws CoreException {
addBreakpoints(breakpoints, true);
}
/**
* Registers the given breakpoints and notifies listeners if specified.
*
* @param breakpoints the breakpoints to register
* @param notify whether to notify listeners of the add
* @throws CoreException if a problem is encountered
*/
private void addBreakpoints(IBreakpoint[] breakpoints, boolean notify) throws CoreException {
List<IBreakpoint> added = new ArrayList<>(breakpoints.length);
final List<IBreakpoint> update = new ArrayList<>();
for (IBreakpoint breakpoint : breakpoints) {
if (!getBreakpoints0().contains(breakpoint)) {
verifyBreakpoint(breakpoint);
if (breakpoint.isRegistered()) {
// If notify == false, the breakpoints are just being added at startup
added.add(breakpoint);
getBreakpoints0().add(breakpoint);
fMarkersToBreakpoints.put(breakpoint.getMarker(), breakpoint);
if (breakpoint instanceof ITriggerPoint && ((ITriggerPoint) breakpoint).isTriggerPoint()) {
addTriggerPoint(breakpoint);
}
} else {
// need to update the 'registered' and/or 'group' attributes
update.add(breakpoint);
}
}
}
if (notify) {
fireUpdate(added, null, ADDED);
}
if (!update.isEmpty()) {
IWorkspaceRunnable r = monitor -> {
List<IBreakpoint> bps = getBreakpoints0();
for (IBreakpoint breakpoint : update) {
bps.add(breakpoint);
breakpoint.setRegistered(true);
fMarkersToBreakpoints.put(breakpoint.getMarker(), breakpoint);
}
};
// Need to suppress change notification, since this is really
// an add notification
fSuppressChange.addAll(update);
getWorkspace().run(r, null, 0, null);
fSuppressChange.removeAll(update);
if (notify) {
fireUpdate(update, null, ADDED);
}
}
}
/**
* Returns whether change notification is to be suppressed for the given breakpoint.
* Used when adding breakpoints and changing the "REGISTERED" attribute.
*
* @param breakpoint the breakpoint
* @return boolean whether change notification is suppressed
*/
protected boolean isChangeSuppressed(IBreakpoint breakpoint) {
return fSuppressChange.contains(breakpoint);
}
/**
* @see IBreakpointManager#fireBreakpointChanged(IBreakpoint)
*/
@Override
public void fireBreakpointChanged(IBreakpoint breakpoint) {
if (getBreakpoints0().contains(breakpoint)) {
List<IBreakpoint> changed = new ArrayList<>();
changed.add(breakpoint);
fireUpdate(changed, null, CHANGED);
}
}
/**
* Verifies that the breakpoint marker has the minimal required attributes,
* and throws a debug exception if not.
* @param breakpoint the {@link IBreakpoint} to verify
* @throws DebugException if a problem is encountered
*/
private void verifyBreakpoint(IBreakpoint breakpoint) throws DebugException {
try {
String id= breakpoint.getModelIdentifier();
if (id == null) {
throw new DebugException(new Status(IStatus.ERROR, DebugPlugin.getUniqueIdentifier(),
DebugException.CONFIGURATION_INVALID, DebugCoreMessages.BreakpointManager_Missing_model_identifier, null));
}
} catch (CoreException e) {
throw new DebugException(e.getStatus());
}
}
/**
* A resource has changed. Traverses the delta for breakpoint changes.
*
* @param event resource change event
*/
@Override
public void resourceChanged(IResourceChangeEvent event) {
IResourceDelta delta= event.getDelta();
if (delta != null) {
try {
if (fgVisitor == null) {
fgVisitor= new BreakpointManagerVisitor();
}
delta.accept(fgVisitor);
fgVisitor.update();
} catch (CoreException ce) {
DebugPlugin.log(ce);
}
}
}
/**
* Visitor for handling resource deltas
*/
class BreakpointManagerVisitor implements IResourceDeltaVisitor {
/**
* Moved markers
*/
private List<IMarker> fMoved = new ArrayList<>();
/**
* Removed breakpoints
*/
private List<IBreakpoint> fRemoved = new ArrayList<>();
/**
* Added breakpoints.
* @since 3.7
*/
private List<IBreakpoint> fAdded = new ArrayList<>();
/**
* Changed breakpoints and associated marker deltas
*/
private List<IBreakpoint> fChanged = new ArrayList<>();
private List<IMarkerDelta> fChangedDeltas = new ArrayList<>();
/**
* Resets the visitor for a delta traversal - empties
* collections of removed/changed breakpoints.
*/
protected void reset() {
fMoved.clear();
fRemoved.clear();
fAdded.clear();
fChanged.clear();
fChangedDeltas.clear();
}
/**
* Performs updates on accumulated changes, and fires change notification after
* a traversal. Accumulated updates are reset.
*/
public void update() {
if (!fMoved.isEmpty()) {
// delete moved markers
IWorkspaceRunnable wRunnable= monitor -> {
for (IMarker marker : fMoved) {
marker.delete();
}
};
try {
getWorkspace().run(wRunnable, null, 0, null);
} catch (CoreException e) {
}
}
if (!fRemoved.isEmpty()) {
try {
removeBreakpoints(fRemoved.toArray(new IBreakpoint[fRemoved.size()]), false);
} catch (CoreException e) {
DebugPlugin.log(e);
}
}
if (!fAdded.isEmpty()) {
try {
IWorkspaceRunnable runnable= monitor -> {
for (IBreakpoint breakpoint : fAdded) {
breakpoint.getMarker().setAttribute(DebugPlugin.ATTR_BREAKPOINT_IS_DELETED, false);
breakpoint.setRegistered(true);
}
};
getWorkspace().run(runnable, null, 0, null);
addBreakpoints(fAdded.toArray(new IBreakpoint[fAdded.size()]), true);
} catch (CoreException e) {
DebugPlugin.log(e);
}
}
if (!fChanged.isEmpty()) {
fireUpdate(fChanged, fChangedDeltas, CHANGED);
}
reset();
}
/**
* @see IResourceDeltaVisitor#visit(IResourceDelta)
*/
@Override
public boolean visit(IResourceDelta delta) {
if (delta == null) {
return false;
}
if (0 != (delta.getFlags() & IResourceDelta.OPEN) && 0 == (delta.getFlags() & IResourceDelta.MOVED_FROM)) {
handleProjectResourceOpenStateChange(delta.getResource());
return false;
}
for (IMarkerDelta markerDelta : delta.getMarkerDeltas()) {
if (markerDelta.isSubtypeOf(IBreakpoint.BREAKPOINT_MARKER)) {
switch (markerDelta.getKind()) {
case IResourceDelta.ADDED :
handleAddBreakpoint(delta, markerDelta.getMarker(), markerDelta);
break;
case IResourceDelta.REMOVED :
handleRemoveBreakpoint(markerDelta.getMarker());
break;
case IResourceDelta.CHANGED :
handleChangeBreakpoint(markerDelta.getMarker(), markerDelta);
break;
default:
break;
}
}
}
return true;
}
/**
* Wrapper for handling adds
* @param rDelta the {@link IResourceDelta}
* @param marker the new {@link IMarker}
* @param mDelta the accompanying {@link IMarkerDelta}
*/
protected void handleAddBreakpoint(IResourceDelta rDelta, IMarker marker, IMarkerDelta mDelta) {
if (0 != (rDelta.getFlags() & IResourceDelta.MOVED_FROM)) {
// This breakpoint has actually been moved - already removed
// from the Breakpoint manager during the remove callback.
// Schedule the marker associated with the new resource for deletion.
if (getBreakpoint(marker) == null) {
fMoved.add(marker);
}
} else {
// check if the an add & change have be combined into one add notification
synchronized (fPostChangMarkersChanged) {
if (fPostChangMarkersChanged.contains(marker)) {
handleChangeBreakpoint(marker, mDelta);
fPostChangMarkersChanged.remove(marker);
} else if (marker.getAttribute(DebugPlugin.ATTR_BREAKPOINT_IS_DELETED, false) && getBreakpoint(marker) == null) {
try { /*
* There may be breakpoints with matching resource
* and same line number
*/
IBreakpoint breakpoint = findMatchingBreakpoint(marker);
if (breakpoint != null) {
removeBreakpoint(breakpoint, true);
}
fAdded.add(createBreakpoint(marker));
} catch (CoreException e) {
DebugPlugin.log(e);
}
}
fPostBuildMarkersAdded.add(marker);
}
}
}
/**
* To find a breakpoint with matching marker resources and line number.
*
* @param marker the {@link IMarker} for which existing breakpoint is
* retrieved
* @return matching breakpoint if exists else return <code>null</code>
*/
private IBreakpoint findMatchingBreakpoint(IMarker marker) {
try {
Integer line = (Integer) marker.getAttribute(IMarker.LINE_NUMBER);
for (IBreakpoint breakpoint : getBreakpoints0()) {
IMarker bpMarker = breakpoint.getMarker();
if (bpMarker != null && marker.getResource().equals(bpMarker.getResource()) && bpMarker.getAttribute(IMarker.LINE_NUMBER, -1) == (line == null ? -1 : line.intValue())) {
return breakpoint;
}
}
} catch (CoreException e) {
// ignore
}
return null;
}
/**
* Wrapper for handling removes
* @param marker the {@link IMarker}
*/
protected void handleRemoveBreakpoint(IMarker marker) {
synchronized (fPostChangMarkersChanged) {
fPostChangMarkersChanged.remove(marker);
fPostBuildMarkersAdded.remove(marker);
}
IBreakpoint breakpoint= getBreakpoint(marker);
if (breakpoint != null) {
fRemoved.add(breakpoint);
}
}
/**
* Wrapper for handling changes
* @param marker the {@link IMarker} that was changed
* @param delta the {@link IMarkerDelta}
*/
protected void handleChangeBreakpoint(IMarker marker, IMarkerDelta delta) {
IBreakpoint breakpoint= getBreakpoint(marker);
if (breakpoint != null && isRegistered(breakpoint) && !isChangeSuppressed(breakpoint)) {
fChanged.add(breakpoint);
fChangedDeltas.add(delta);
}
}
/**
* A project has been opened or closed. Updates the breakpoints for
* that project
* @param project the {@link IProject} that was changed
*/
private void handleProjectResourceOpenStateChange(final IResource project) {
if (!project.isAccessible()) {
//closed
for (@SuppressWarnings("unchecked") IBreakpoint breakpoint : (Vector<IBreakpoint>) getBreakpoints0().clone()) {
IResource markerResource= breakpoint.getMarker().getResource();
if (project.getFullPath().isPrefixOf(markerResource.getFullPath())) {
fRemoved.add(breakpoint);
}
}
return;
}
try {
loadBreakpoints(project, true);
} catch (CoreException e) {
DebugPlugin.log(e);
}
}
}
/**
* @see IBreakpointManager#addBreakpointListener(IBreakpointListener)
*/
@Override
public void addBreakpointListener(IBreakpointListener listener) {
fBreakpointListeners.add(listener);
}
/**
* @see IBreakpointManager#removeBreakpointListener(IBreakpointListener)
*/
@Override
public void removeBreakpointListener(IBreakpointListener listener) {
fBreakpointListeners.remove(listener);
}
/**
* Notifies listeners of the adds/removes/changes
*
* @param breakpoints associated breakpoints
* @param deltas or <code>null</code>
* @param update type of change
*/
private void fireUpdate(List<IBreakpoint> breakpoints, List<IMarkerDelta> deltas, int update) {
if (breakpoints.isEmpty()) {
return;
}
IBreakpoint[] bpArray = breakpoints.toArray(new IBreakpoint[breakpoints.size()]);
IMarkerDelta[] deltaArray = new IMarkerDelta[bpArray.length];
if (deltas != null) {
deltaArray = deltas.toArray(deltaArray);
}
// single listeners
getBreakpointNotifier().notify(bpArray, deltaArray, update);
// plural listeners
getBreakpointsNotifier().notify(bpArray, deltaArray, update);
}
protected void setBreakpoints(Vector<IBreakpoint> breakpoints) {
fBreakpoints = breakpoints;
}
/**
* @see IBreakpointManager#hasBreakpoints()
*/
@Override
public boolean hasBreakpoints() {
return !getBreakpoints0().isEmpty();
}
/**
* @see org.eclipse.debug.core.IBreakpointManager#addBreakpointListener(org.eclipse.debug.core.IBreakpointsListener)
*/
@Override
public void addBreakpointListener(IBreakpointsListener listener) {
fBreakpointsListeners.add(listener);
}
/**
* @see org.eclipse.debug.core.IBreakpointManager#removeBreakpointListener(org.eclipse.debug.core.IBreakpointsListener)
*/
@Override
public void removeBreakpointListener(IBreakpointsListener listener) {
fBreakpointsListeners.remove(listener);
}
private BreakpointNotifier getBreakpointNotifier() {
return new BreakpointNotifier();
}
/**
* Notifies breakpoint listener (single breakpoint) in a safe runnable to
* handle exceptions.
*/
class BreakpointNotifier implements ISafeRunnable {
private IBreakpointListener fListener;
private int fType;
private IMarkerDelta fDelta;
private IBreakpoint fBreakpoint;
/**
* @see org.eclipse.core.runtime.ISafeRunnable#handleException(java.lang.Throwable)
*/
@Override
public void handleException(Throwable exception) {
IStatus status = new Status(IStatus.ERROR, DebugPlugin.getUniqueIdentifier(), DebugPlugin.INTERNAL_ERROR, "An exception occurred during breakpoint change notification.", exception); //$NON-NLS-1$
DebugPlugin.log(status);
}
/**
* @see org.eclipse.core.runtime.ISafeRunnable#run()
*/
@Override
public void run() throws Exception {
switch (fType) {
case ADDED:
fListener.breakpointAdded(fBreakpoint);
break;
case REMOVED:
fListener.breakpointRemoved(fBreakpoint, fDelta);
break;
case CHANGED:
fListener.breakpointChanged(fBreakpoint, fDelta);
break;
default:
break;
}
}
/**
* Notifies the listeners of the add/change/remove
*
* @param breakpoints the breakpoints that changed
* @param deltas the deltas associated with the change
* @param update the type of change
*/
public void notify(IBreakpoint[] breakpoints, IMarkerDelta[] deltas, int update) {
fType = update;
for (IBreakpointListener iBreakpointListener : fBreakpointListeners) {
fListener = iBreakpointListener;
for (int j = 0; j < breakpoints.length; j++) {
fBreakpoint = breakpoints[j];
fDelta = deltas[j];
SafeRunner.run(this);
}
}
fListener = null;
fDelta = null;
fBreakpoint = null;
}
}
private BreakpointsNotifier getBreakpointsNotifier() {
return new BreakpointsNotifier();
}
/**
* Notifies breakpoint listener (multiple breakpoints) in a safe runnable to
* handle exceptions.
*/
class BreakpointsNotifier implements ISafeRunnable {
private IBreakpointsListener fListener;
private int fType;
private IMarkerDelta[] fDeltas;
private IBreakpoint[] fNotifierBreakpoints;
@Override
public void handleException(Throwable exception) {
IStatus status = new Status(IStatus.ERROR, DebugPlugin.getUniqueIdentifier(), DebugPlugin.INTERNAL_ERROR, "An exception occurred during breakpoint change notification.", exception); //$NON-NLS-1$
DebugPlugin.log(status);
}
@Override
public void run() throws Exception {
switch (fType) {
case ADDED:
fListener.breakpointsAdded(fNotifierBreakpoints);
break;
case REMOVED:
fListener.breakpointsRemoved(fNotifierBreakpoints, fDeltas);
break;
case CHANGED:
fListener.breakpointsChanged(fNotifierBreakpoints, fDeltas);
break;
default:
break;
}
}
/**
* Notifies the listeners of the adds/changes/removes
*
* @param breakpoints the breakpoints that changed
* @param deltas the deltas associated with the changed breakpoints
* @param update the type of change
*/
public void notify(IBreakpoint[] breakpoints, IMarkerDelta[] deltas, int update) {
fType = update;
fNotifierBreakpoints = breakpoints;
fDeltas = deltas;
for (IBreakpointsListener iBreakpointsListener : fBreakpointsListeners) {
fListener = iBreakpointsListener;
SafeRunner.run(this);
}
fDeltas = null;
fNotifierBreakpoints = null;
fListener = null;
}
}
@Override
public boolean isEnabled() {
return Platform.getPreferencesService().getBoolean(DebugPlugin.getUniqueIdentifier(), IInternalDebugCoreConstants.PREF_BREAKPOINT_MANAGER_ENABLED_STATE, true, null);
}
@Override
public void setEnabled(final boolean enabled) {
if (isEnabled() != enabled) {
Preferences.setBoolean(DebugPlugin.getUniqueIdentifier(), IInternalDebugCoreConstants.PREF_BREAKPOINT_MANAGER_ENABLED_STATE, enabled, null);
touchAllBreakpoints();
new BreakpointManagerNotifier().notify(enabled);
}
}
@Override
public void addBreakpointManagerListener(IBreakpointManagerListener listener) {
fBreakpointManagerListeners.add(listener);
}
@Override
public void removeBreakpointManagerListener(IBreakpointManagerListener listener) {
fBreakpointManagerListeners.remove(listener);
}
/**
* Notifies breakpoint manager listeners in a safe runnable to
* handle exceptions.
*/
class BreakpointManagerNotifier implements ISafeRunnable {
private IBreakpointManagerListener fListener;
private boolean fManagerEnabled;
@Override
public void handleException(Throwable exception) {
IStatus status = new Status(IStatus.ERROR, DebugPlugin.getUniqueIdentifier(), DebugPlugin.INTERNAL_ERROR, "An exception occurred during breakpoint change notification.", exception); //$NON-NLS-1$
DebugPlugin.log(status);
}
@Override
public void run() throws Exception {
fListener.breakpointManagerEnablementChanged(fManagerEnabled);
}
/**
* Notifies the listeners of the enabled state change
*
* @param enabled whether the manager is enabled
*/
public void notify(boolean enabled) {
fManagerEnabled= enabled;
for (IBreakpointManagerListener iBreakpointManagerListener : fBreakpointManagerListeners) {
fListener = iBreakpointManagerListener;
SafeRunner.run(this);
}
fListener = null;
}
}
/**
* Notifies breakpoint manager listeners in a safe runnable to handle
* exceptions.
*/
class BreakpointManagerTriggerPointNotifier implements ISafeRunnable {
private IBreakpointManagerListener fListener;
private IBreakpoint fManagerTriggerPoint;
@Override
public void handleException(Throwable exception) {
IStatus status = new Status(IStatus.ERROR, DebugPlugin.getUniqueIdentifier(), DebugPlugin.INTERNAL_ERROR, "An exception occurred during breakpoint change notification.", exception); //$NON-NLS-1$
DebugPlugin.log(status);
}
@Override
public void run() throws Exception {
fListener.breakpointManagerTriggerPointChanged(fManagerTriggerPoint);
}
/**
* Notifies the listeners of the enabled state change
*
* @param triggerBreakpoint new breakpoint as trigger point
*/
public void notify(IBreakpoint triggerBreakpoint) {
fManagerTriggerPoint = triggerBreakpoint;
for (IBreakpointManagerListener iBreakpointManagerListener : fBreakpointManagerListeners) {
fListener = iBreakpointManagerListener;
SafeRunner.run(this);
}
fListener = null;
}
}
class BreakpointManagerJob extends Job {
private final IWorkspaceRunnable fRunnable;
public BreakpointManagerJob (IWorkspaceRunnable wRunnable) {
super("breakpoint manager job"); //$NON-NLS-1$
fRunnable= wRunnable;
setSystem(true);
}
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
getWorkspace().run(fRunnable, null, 0, null);
} catch (CoreException ce) {
DebugPlugin.log(ce);
}
return new Status(IStatus.OK, DebugPlugin.getUniqueIdentifier(), IStatus.OK, "", null); //$NON-NLS-1$
}
}
@Override
public String getTypeName(IBreakpoint breakpoint) {
String typeName= null;
IMarker marker = breakpoint.getMarker();
if (marker != null) {
try {
IConfigurationElement element = fBreakpointExtensions.get(marker.getType());
if (element != null) {
typeName= element.getAttribute(IConfigurationElementConstants.NAME);
}
}
catch (CoreException e) {}
}
return typeName;
}
@Override
public IBreakpointImportParticipant[] getImportParticipants(String markertype) throws CoreException {
initializeImportParticipants();
ArrayList<BreakpointImportParticipantDelegate> list = fImportParticipants.get(markertype);
if(list == null) {
return new IBreakpointImportParticipant[] {fDefaultParticipant};
}
IBreakpointImportParticipant[] participants = new IBreakpointImportParticipant[list.size()];
BreakpointImportParticipantDelegate delegate = null;
for(int i = 0; i < list.size(); i++) {
delegate = list.get(i);
participants[i] = delegate.getDelegate();
}
if(participants.length == 0) {
return new IBreakpointImportParticipant[] {fDefaultParticipant};
}
return participants;
}
/**
* Initializes the cache of breakpoint import participants. Does no work if the cache
* has already been initialized
*/
private synchronized void initializeImportParticipants() {
if(fImportParticipants == null) {
fImportParticipants = new HashMap<>();
fDefaultParticipant = new DefaultImportParticipant();
IExtensionPoint ep = Platform.getExtensionRegistry().getExtensionPoint(DebugPlugin.getUniqueIdentifier(), DebugPlugin.EXTENSION_POINT_BREAKPOINT_IMPORT_PARTICIPANTS);
String type = null;
ArrayList<BreakpointImportParticipantDelegate> list = null;
for (IConfigurationElement element : ep.getConfigurationElements()) {
type = element.getAttribute(IConfigurationElementConstants.TYPE);
if(type != null) {
list = fImportParticipants.get(type);
if(list == null) {
list = new ArrayList<>();
fImportParticipants.put(type, list);
}
list.add(new BreakpointImportParticipantDelegate(element));
}
}
}
}
@Override
public IBreakpoint[] getTriggerPoints() {
return fTriggerPointBreakpointList.toArray(new IBreakpoint[0]);
}
@Override
public void addTriggerPoint(IBreakpoint triggerPoint) throws CoreException {
if (triggerPoint == null) {
return;
}
fTriggerPointBreakpointList.add(triggerPoint);
new BreakpointManagerTriggerPointNotifier().notify(triggerPoint);
}
@Override
public void removeTriggerPoint(IBreakpoint breakpoint) throws CoreException {
if (breakpoint != null) {
fTriggerPointBreakpointList.remove(breakpoint);
}
}
@Override
public void removeAllTriggerPoints() throws CoreException {
IBreakpoint[] triggerPointBreakpointList = getTriggerPoints();
for (IBreakpoint iBreakpoint : triggerPointBreakpointList) {
if (iBreakpoint instanceof ITriggerPoint) {
((ITriggerPoint) iBreakpoint).setTriggerPoint(false);
}
}
refreshTriggerpointDisplay();
}
@Override
public boolean hasActiveTriggerPoints() {
if (fTriggerPointBreakpointList.isEmpty()) {
return false;
}
for (IBreakpoint iBreakpoint : fTriggerPointBreakpointList) {
try {
if (iBreakpoint.isEnabled()) {
return true;
}
} catch (CoreException e) {
// ignore
}
}
return false;
}
@Override
public void enableTriggerPoints(IBreakpoint[] triggerPoints, boolean enable) {
IBreakpoint[] triggerPointArray = triggerPoints;
if (triggerPoints == null) {
if (enable) {
synchronized (fTriggerPointDisabledList) {
triggerPointArray = fTriggerPointDisabledList.toArray(new IBreakpoint[0]);
}
} else {
triggerPointArray = getTriggerPoints();
}
}
List<IBreakpoint> toDisable = new ArrayList<>();
for (IBreakpoint iBreakpoint : triggerPointArray) {
try {
IMarker m = iBreakpoint.getMarker();
if (m != null && m.exists()) {
if (!enable && iBreakpoint.isEnabled()) {
toDisable.add(iBreakpoint);
}
iBreakpoint.setEnabled(enable);
}
} catch (CoreException e) {
// ignore
}
}
synchronized (fTriggerPointDisabledList) {
fTriggerPointDisabledList.clear();
if (!enable) {
fTriggerPointDisabledList.addAll(toDisable);
}
}
}
@Override
public void refreshTriggerpointDisplay() {
touchAllBreakpoints();
}
/*
* Touch and refresh display of all breakpoints
*/
private void touchAllBreakpoints() {
IWorkspaceRunnable runnable = monitor -> {
for (IBreakpoint breakpoint : getBreakpoints()) {
// Touch the marker (but don't actually change anything) so
// that the icon in
// the editor ruler will be updated (editors listen to
// marker changes).
try {
breakpoint.getMarker().setAttribute(IBreakpoint.ENABLED, breakpoint.isEnabled());
} catch (CoreException e) {
// don't care if marker was already deleted
}
}
};
try {
ResourcesPlugin.getWorkspace().run(runnable, null, IWorkspace.AVOID_UPDATE, null);
} catch (CoreException e) {
DebugPlugin.log(e);
}
}
}