blob: 7f2ee5f4a93a996293c582901bac64f68c63f2c7 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2018 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.jdt.internal.debug.core.breakpoints;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.debug.core.DebugEvent;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.IDebugEventSetListener;
import org.eclipse.debug.core.model.Breakpoint;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.jdt.debug.core.IJavaBreakpoint;
import org.eclipse.jdt.debug.core.IJavaBreakpointListener;
import org.eclipse.jdt.debug.core.IJavaDebugTarget;
import org.eclipse.jdt.debug.core.IJavaObject;
import org.eclipse.jdt.debug.core.IJavaThread;
import org.eclipse.jdt.debug.core.IJavaType;
import org.eclipse.jdt.debug.core.JDIDebugModel;
import org.eclipse.jdt.internal.debug.core.IJDIEventListener;
import org.eclipse.jdt.internal.debug.core.JDIDebugPlugin;
import org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget;
import org.eclipse.jdt.internal.debug.core.model.JDIObjectValue;
import org.eclipse.jdt.internal.debug.core.model.JDIThread;
import org.eclipse.jdt.internal.debug.core.model.JDIType;
import com.ibm.icu.text.MessageFormat;
import com.sun.jdi.ObjectReference;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.ThreadReference;
import com.sun.jdi.VMDisconnectedException;
import com.sun.jdi.event.ClassPrepareEvent;
import com.sun.jdi.event.Event;
import com.sun.jdi.event.EventSet;
import com.sun.jdi.event.LocatableEvent;
import com.sun.jdi.request.ClassPrepareRequest;
import com.sun.jdi.request.EventRequest;
import com.sun.jdi.request.EventRequestManager;
public abstract class JavaBreakpoint extends Breakpoint implements IJavaBreakpoint, IJDIEventListener, IDebugEventSetListener {
/**
* Breakpoint attribute storing the expired value (value
* <code>"org.eclipse.jdt.debug.core.expired"</code>). This attribute is
* stored as a <code>boolean</code>. Once a hit count has been reached, a
* breakpoint is considered to be "expired".
*/
protected static final String EXPIRED = "org.eclipse.jdt.debug.core.expired"; //$NON-NLS-1$
/**
* Breakpoint attribute storing a breakpoint's hit count value (value
* <code>"org.eclipse.jdt.debug.core.hitCount"</code>). This attribute is
* stored as an <code>int</code>.
*/
protected static final String HIT_COUNT = "org.eclipse.jdt.debug.core.hitCount"; //$NON-NLS-1$
/**
* Breakpoint attribute storing the number of debug targets a breakpoint is
* installed in (value
* <code>"org.eclipse.jdt.debug.core.installCount"</code>). This attribute
* is a <code>int</code>.
*/
protected static final String INSTALL_COUNT = "org.eclipse.jdt.debug.core.installCount"; //$NON-NLS-1$
/**
* Breakpoint attribute storing the fully qualified name of the type this
* breakpoint is located in. (value
* <code>"org.eclipse.jdt.debug.core.typeName"</code>). This attribute is a
* <code>String</code>.
*/
protected static final String TYPE_NAME = "org.eclipse.jdt.debug.core.typeName"; //$NON-NLS-1$
/**
* Breakpoint attribute storing suspend policy code for this breakpoint.
* (value <code>"org.eclipse.jdt.debug.core.suspendPolicy</code>). This
* attribute is an <code>int</code> corresponding to
* <code>IJavaBreakpoint.SUSPEND_VM</code> or
* <code>IJavaBreakpoint.SUSPEND_THREAD</code>.
*/
protected static final String SUSPEND_POLICY = "org.eclipse.jdt.debug.core.suspendPolicy"; //$NON-NLS-1$
/**
* Breakpoint attribute storing a comma delimited list of extension
* identifiers of breakpoint listeners. The listeners will be notified in
* the order specified in the list.
*
* @since 3.5
*/
public static final String BREAKPOINT_LISTENERS = JDIDebugPlugin.EXTENSION_POINT_JAVA_BREAKPOINT_LISTENERS;
/**
* Breakpoint attribute storing the expired value of trigger point (value
* <code>"org.eclipse.jdt.debug.core.expiredTriggerPoint"</code>). This attribute is
* stored as a <code>boolean</code>. Once a trigger point is hit, a
* breakpoint is considered to be "expired" as trigger point for the session.
*
* @since 3.11
*/
public static final String EXPIRED_TRIGGER_POINT = "org.eclipse.jdt.debug.core.expiredTriggerPoint"; //$NON-NLS-1$
/**
* Stores the collection of requests that this breakpoint has installed in
* debug targets. key: a debug target value: the requests this breakpoint
* has installed in that target
*/
protected HashMap<JDIDebugTarget, List<EventRequest>> fRequestsByTarget;
/**
* The list of threads (ThreadReference objects) in which this breakpoint
* will suspend, associated with the target in which each thread exists
* (JDIDebugTarget). key: targets the debug targets (IJavaDebugTarget)
* value: thread the filtered thread (IJavaThread) in the given target
*/
protected Map<JDIDebugTarget, IJavaThread> fFilteredThreadsByTarget;
/**
* Stores the type name that this breakpoint was last installed in. When a
* breakpoint is created, the TYPE_NAME attribute assigned to it is that of
* its top level enclosing type. When installed, the type may actually be an
* inner type. We need to keep track of the type type the breakpoint was
* installed in, in case we need to re-install the breakpoint for HCR (i.e.
* in case an inner type is HCR'd).
*/
protected String fInstalledTypeName = null;
/**
* List of targets in which this breakpoint is installed. Used to prevent
* firing of more than one install notification when a breakpoint's requests
* are re-created.
*/
protected Set<IJavaDebugTarget> fInstalledTargets = null;
/**
* List of active instance filters for this breakpoint (list of
* <code>IJavaObject</code>).
*/
protected List<IJavaObject> fInstanceFilters = null;
/**
* List of breakpoint listener identifiers corresponding to breakpoint
* listener extensions. Listeners are cached with the breakpoint object such
* that they can be notified when a breakpoint is removed.
*/
private List<String> fBreakpointListenerIds = null;
/**
* Empty instance filters array.
*/
protected static final IJavaObject[] fgEmptyInstanceFilters = new IJavaObject[0];
/**
* Property identifier for a breakpoint object on an event request
*/
public static final String JAVA_BREAKPOINT_PROPERTY = "org.eclipse.jdt.debug.breakpoint"; //$NON-NLS-1$
/**
* JavaBreakpoint attributes
*/
protected static final String[] fgExpiredEnabledAttributes = new String[] {
EXPIRED, ENABLED };
public JavaBreakpoint() {
fRequestsByTarget = new HashMap<>(1);
fFilteredThreadsByTarget = new HashMap<>(1);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.IBreakpoint#getModelIdentifier()
*/
@Override
public String getModelIdentifier() {
return JDIDebugModel.getPluginIdentifier();
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.debug.core.model.Breakpoint#setMarker(org.eclipse.core.resources
* .IMarker)
*/
@Override
public void setMarker(IMarker marker) throws CoreException {
super.setMarker(marker);
configureAtStartup();
}
/**
* Add this breakpoint to the breakpoint manager, or sets it as
* unregistered.
*/
protected void register(boolean register) throws CoreException {
DebugPlugin plugin = DebugPlugin.getDefault();
if (plugin != null && register) {
plugin.getBreakpointManager().addBreakpoint(this);
} else {
setRegistered(false);
}
}
/**
* Add the given event request to the given debug target. If the request is
* the breakpoint request associated with this breakpoint, increment the
* install count.
*/
protected void registerRequest(EventRequest request, JDIDebugTarget target)
throws CoreException {
if (request == null) {
return;
}
List<EventRequest> reqs = getRequests(target);
if (reqs.isEmpty()) {
fRequestsByTarget.put(target, reqs);
}
reqs.add(request);
target.addJDIEventListener(this, request);
// update the install attribute on the breakpoint
if (!(request instanceof ClassPrepareRequest)) {
incrementInstallCount();
// notification
fireInstalled(target);
}
}
/**
* Returns a String corresponding to the reference type name to the top
* enclosing type in which this breakpoint is located or <code>null</code>
* if no reference type could be found.
*/
protected String getEnclosingReferenceTypeName() throws CoreException {
String name = getTypeName();
if (name != null) {
int index = name.indexOf('$');
if (index == -1) {
return name;
}
return name.substring(0, index);
}
return null;
}
/**
* Returns the requests that this breakpoint has installed in the given
* target.
*/
protected ArrayList<EventRequest> getRequests(JDIDebugTarget target) {
ArrayList<EventRequest> list = (ArrayList<EventRequest>) fRequestsByTarget.get(target);
if (list == null) {
list = new ArrayList<>(2);
}
return list;
}
/**
* Remove the given request from the given target. If the request is the
* breakpoint request associated with this breakpoint, decrement the install
* count.
*/
protected void deregisterRequest(EventRequest request, JDIDebugTarget target)
throws CoreException {
target.removeJDIEventListener(this, request);
// A request may be getting de-registered because the breakpoint has
// been deleted. It may be that this occurred because of a marker
// deletion.
// Don't try updating the marker (decrementing the install count) if
// it no longer exists.
if (!(request instanceof ClassPrepareRequest) && getMarker().exists()) {
decrementInstallCount();
}
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jdt.internal.debug.core.IJDIEventListener#handleEvent(com
* .sun.jdi.event.Event,
* org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget)
*/
@Override
public boolean handleEvent(Event event, JDIDebugTarget target,
boolean suspendVote, EventSet eventSet) {
if (event instanceof ClassPrepareEvent) {
return handleClassPrepareEvent((ClassPrepareEvent) event, target,
suspendVote);
}
ThreadReference threadRef = ((LocatableEvent) event).thread();
JDIThread thread = target.findThread(threadRef);
if (thread == null) {
thread = target.findThread(threadRef);
}
if (thread == null || thread.isIgnoringBreakpoints()) {
return true;
}
return handleBreakpointEvent(event, thread, suspendVote);
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jdt.internal.debug.core.IJDIEventListener#eventSetComplete
* (com.sun.jdi.event.Event,
* org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget, boolean)
*/
@Override
public void eventSetComplete(Event event, JDIDebugTarget target,
boolean suspend, EventSet eventSet) {
ThreadReference threadRef = null;
if (event instanceof ClassPrepareEvent) {
threadRef = ((ClassPrepareEvent) event).thread();
} else if (event instanceof LocatableEvent) {
threadRef = ((LocatableEvent) event).thread();
}
if (threadRef == null) {
return;
}
JDIThread thread = target.findThread(threadRef);
if (thread == null || thread.isIgnoringBreakpoints()) {
return;
}
if (event instanceof ClassPrepareEvent) {
classPrepareComplete(event, thread, suspend, eventSet);
} else {
thread.completeBreakpointHandling(this, suspend, true, eventSet);
}
}
/**
* Call-back that the class prepare event has completed
* @param event the event
* @param thread the thread that sent the event
* @param suspend if the the thread was suspended
* @param eventSet the event set context
*/
protected void classPrepareComplete(Event event, JDIThread thread, boolean suspend, EventSet eventSet) {
// resume the thread if this is a class load event to install a deferred
// breakpoint (and the vote is to resume)
if (thread != null && !suspend) {
thread.resumedFromClassPrepare();
}
}
/**
* Handle the given class prepare event, which was generated by the class
* prepare event installed in the given target by this breakpoint.
*
* If the class which has been loaded is a class in which this breakpoint
* should install, create a breakpoint request for that class.
* @param event the event
* @param target the target
* @param suspendVote the current suspend vote
* @return is the thread should suspend or not
*/
public boolean handleClassPrepareEvent(ClassPrepareEvent event, JDIDebugTarget target, boolean suspendVote) {
try {
if (!installableReferenceType(event.referenceType(), target)) {
// Don't install this breakpoint in an
// inappropriate type
return true;
}
createRequest(target, event.referenceType());
} catch (CoreException e) {
JDIDebugPlugin.log(e);
}
return true;
}
/**
* @see IJDIEventListener#handleEvent(Event, JDIDebugTarget)
*
* Handle the given event, which was generated by the breakpoint
* request installed in the given target by this breakpoint.
*/
public boolean handleBreakpointEvent(Event event, JDIThread thread,
boolean suspendVote) {
expireHitCount(event);
disableTriggerPoint(event);
return !suspend(thread, suspendVote); // Resume if suspend fails
}
/**
* Delegates to the given thread to suspend, and returns whether the thread
* suspended It is possible that the thread will not suspend as directed by
* a Java breakpoint listener.
*
* @see IJavaBreakpointListener#breakpointHit(IJavaThread, IJavaBreakpoint)
*/
protected boolean suspend(JDIThread thread, boolean suspendVote) {
return thread.handleSuspendForBreakpoint(this, suspendVote);
}
/**
* Returns whether the given reference type is appropriate for this
* breakpoint to be installed in the given target. Query registered
* breakpoint listeners.
*/
protected boolean installableReferenceType(ReferenceType type,
JDIDebugTarget target) throws CoreException {
String installableType = getTypeName();
if (installableType == null ) {
return false;
}
String queriedType = type.name();
if( queriedType == null) {
return false;
}
int index = queriedType.indexOf('<');
if (index != -1) {
queriedType = queriedType.substring(0, index);
}
if (installableType.equals(queriedType)) {
return queryInstallListeners(target, type);
}
index = queriedType.indexOf('$', 0);
if (index == -1) {
return false;
}
if (installableType.regionMatches(0, queriedType, 0, index)) {
return queryInstallListeners(target, type);
}
return false;
}
/**
* Called when a breakpoint event is encountered. Expires the hit count in
* the event's request and updates the marker.
*
* @param event
* the event whose request should have its hit count expired or
* <code>null</code> to only update the breakpoint marker.
*/
protected void expireHitCount(Event event) {
Integer requestCount = null;
EventRequest request = null;
if (event != null) {
request = event.request();
requestCount = (Integer) request.getProperty(HIT_COUNT);
}
if (requestCount != null) {
if (request != null) {
request.putProperty(EXPIRED, Boolean.TRUE);
}
try {
setAttributes(fgExpiredEnabledAttributes, new Object[] {
Boolean.TRUE, Boolean.FALSE });
// make a note that we auto-disabled this breakpoint.
} catch (CoreException ce) {
JDIDebugPlugin.log(ce);
}
}
}
protected void disableTriggerPoint(Event event) {
try{
if (isTriggerPoint() && isEnabled()) {
if (this instanceof JavaLineBreakpoint) {
JavaLineBreakpoint lbp = (JavaLineBreakpoint) this;
if (lbp.hasCondition()) {
return;
}
}
DebugPlugin.getDefault().getBreakpointManager().enableTriggerPoints(null, false);
// make a note that we auto-disabled the trigger point for this breakpoint.
// we re enable it at cleanup of JDITarget
}
}catch (CoreException ce) {
JDIDebugPlugin.log(ce);
}
}
/**
* Returns whether this breakpoint should be "skipped". Breakpoints are
* skipped if the breakpoint manager is disabled and the breakpoint is
* registered with the manager
*
* @return whether this breakpoint should be skipped
*/
public boolean shouldSkipBreakpoint() throws CoreException {
DebugPlugin plugin = DebugPlugin.getDefault();
return plugin != null && isRegistered()
&& !plugin.getBreakpointManager().isEnabled();
}
/**
* Attempts to create a breakpoint request for this breakpoint in the given
* reference type in the given target.
*
* @return Whether a request was created
*/
protected boolean createRequest(JDIDebugTarget target, ReferenceType type)
throws CoreException {
if (shouldSkipBreakpoint()) {
return false;
}
EventRequest[] requests = newRequests(target, type);
if (requests == null) {
return false;
}
fInstalledTypeName = type.name();
for (EventRequest request : requests) {
registerRequest(request, target);
}
return true;
}
/**
* Configure a breakpoint request with common properties:
* <ul>
* <li><code>JAVA_BREAKPOINT_PROPERTY</code></li>
* <li><code>HIT_COUNT</code></li>
* <li><code>EXPIRED</code></li>
* </ul>
* and sets the suspend policy of the request to suspend the event thread.
*/
protected void configureRequest(EventRequest request, JDIDebugTarget target)
throws CoreException {
request.setSuspendPolicy(getJDISuspendPolicy());
request.putProperty(JAVA_BREAKPOINT_PROPERTY, this);
configureRequestThreadFilter(request, target);
configureRequestHitCount(request);
configureInstanceFilters(request, target);
// Important: only enable a request after it has been configured
updateEnabledState(request, target);
}
/**
* Adds an instance filter to the given request. Since the implementation is
* request specific, subclasses must override.
*
* @param request
* @param object
* instance filter
*/
protected abstract void addInstanceFilter(EventRequest request,
ObjectReference object);
/**
* Configure the thread filter property of the given request.
*/
protected void configureRequestThreadFilter(EventRequest request,
JDIDebugTarget target) {
IJavaThread thread = fFilteredThreadsByTarget.get(target);
if (thread == null || (!(thread instanceof JDIThread))) {
return;
}
setRequestThreadFilter(request,
((JDIThread) thread).getUnderlyingThread());
}
/**
* Configure the given request's hit count
*/
protected void configureRequestHitCount(EventRequest request)
throws CoreException {
int hitCount = getHitCount();
if (hitCount > 0) {
request.addCountFilter(hitCount);
request.putProperty(HIT_COUNT, new Integer(hitCount));
}
}
protected void configureInstanceFilters(EventRequest request,
JDIDebugTarget target) {
if (fInstanceFilters != null && !fInstanceFilters.isEmpty()) {
Iterator<IJavaObject> iter = fInstanceFilters.iterator();
while (iter.hasNext()) {
IJavaObject object = iter.next();
if (object.getDebugTarget().equals(target)) {
addInstanceFilter(request,
((JDIObjectValue) object).getUnderlyingObject());
}
}
}
}
/**
* Creates, installs, and returns all event requests for this breakpoint in
* the given reference type and and target.
*
* @return the event requests created or <code>null</code> if creation
* failed
*/
protected abstract EventRequest[] newRequests(JDIDebugTarget target,
ReferenceType type) throws CoreException;
/**
* Add this breakpoint to the given target. After it has been added to the
* given target, this breakpoint will suspend execution of that target as
* appropriate.
*/
public void addToTarget(JDIDebugTarget target) throws CoreException {
fireAdding(target);
createRequests(target);
}
/**
* Creates event requests for the given target
*/
protected void createRequests(JDIDebugTarget target) throws CoreException {
if (target.isTerminated() || shouldSkipBreakpoint()) {
return;
}
String referenceTypeName = getTypeName();
String enclosingTypeName = getEnclosingReferenceTypeName();
if (referenceTypeName == null || enclosingTypeName == null) {
return;
}
// create request to listen to class loads
if (referenceTypeName.indexOf('$') == -1) {
registerRequest(
target.createClassPrepareRequest(enclosingTypeName), target);
// register to ensure we hear about local and anonymous inner
// classes
registerRequest(
target.createClassPrepareRequest(enclosingTypeName + "$*"), target); //$NON-NLS-1$
} else {
registerRequest(
target.createClassPrepareRequest(referenceTypeName), target);
// register to ensure we hear about local and anonymous inner
// classes
registerRequest(target.createClassPrepareRequest(enclosingTypeName
+ "$*", referenceTypeName), target); //$NON-NLS-1$
}
// create breakpoint requests for each class currently loaded
List<ReferenceType> classes = target.jdiClassesByName(referenceTypeName);
if (classes.isEmpty() && enclosingTypeName.equals(referenceTypeName)) {
return;
}
boolean success = false;
Iterator<ReferenceType> iter = classes.iterator();
while (iter.hasNext()) {
ReferenceType type = iter.next();
if (createRequest(target, type)) {
success = true;
}
}
if (!success) {
addToTargetForLocalType(target, enclosingTypeName);
}
}
/**
* Local types (types defined in methods) are handled specially due to the
* different types that the local type is associated with as well as the
* performance problems of using ReferenceType#nestedTypes. From the Java
* model perspective a local type is defined within a method of a type.
* Therefore the type of a breakpoint placed in a local type is the type
* that encloses the method where the local type was defined. The local type
* is enclosed within the top level type according to the VM. So if "normal"
* attempts to create a request when a breakpoint is being added to a target
* fail, we must be dealing with a local type and therefore resort to
* looking up all of the nested types of the top level enclosing type.
*
* @param target the target
* @param enclosingTypeName the type name of the enclosing type
* @throws CoreException if something bad happens
*/
protected void addToTargetForLocalType(JDIDebugTarget target, String enclosingTypeName) throws CoreException {
List<ReferenceType> classes = target.jdiClassesByName(enclosingTypeName);
for(ReferenceType type : classes) {
for(ReferenceType nestedType : type.nestedTypes()) {
if (createRequest(target, nestedType)) {
break;
}
}
}
}
/**
* Returns the JDI suspend policy that corresponds to this breakpoint's
* suspend policy
*
* @return the JDI suspend policy that corresponds to this breakpoint's
* suspend policy
* @exception CoreException
* if unable to access this breakpoint's suspend policy
* setting
*/
protected int getJDISuspendPolicy() throws CoreException {
int breakpointPolicy = getSuspendPolicy();
if (breakpointPolicy == IJavaBreakpoint.SUSPEND_THREAD) {
return EventRequest.SUSPEND_EVENT_THREAD;
}
return EventRequest.SUSPEND_ALL;
}
/**
* returns the default suspend policy based on the pref setting on the
* Java-Debug pref page
*
* @return the default suspend policy
* @since 3.2
*/
protected int getDefaultSuspendPolicy() {
return Platform.getPreferencesService().getInt(
JDIDebugPlugin.getUniqueIdentifier(),
JDIDebugPlugin.PREF_DEFAULT_BREAKPOINT_SUSPEND_POLICY,
IJavaBreakpoint.SUSPEND_THREAD,
null);
}
/**
* Returns whether the hitCount of this breakpoint is equal to the hitCount
* of the associated request.
*/
protected boolean hasHitCountChanged(EventRequest request)
throws CoreException {
int hitCount = getHitCount();
Integer requestCount = (Integer) request.getProperty(HIT_COUNT);
int oldCount = -1;
if (requestCount != null) {
oldCount = requestCount.intValue();
}
return hitCount != oldCount;
}
/**
* Removes this breakpoint from the given target.
*/
public void removeFromTarget(final JDIDebugTarget target)
throws CoreException {
removeRequests(target);
Object removed = fFilteredThreadsByTarget.remove(target);
boolean changed = removed != null;
boolean markerExists = markerExists();
if (!markerExists || (markerExists && getInstallCount() == 0)) {
fInstalledTypeName = null;
}
// remove instance filters
if (fInstanceFilters != null && !fInstanceFilters.isEmpty()) {
for (int i = 0; i < fInstanceFilters.size(); i++) {
IJavaObject object = fInstanceFilters.get(i);
if (object.getDebugTarget().equals(target)) {
fInstanceFilters.remove(i);
changed = true;
}
}
}
// fire change notification if required
if (changed) {
fireChanged();
}
// notification
fireRemoved(target);
}
/**
* Remove all requests that this breakpoint has installed in the given debug
* target.
*/
protected void removeRequests(final JDIDebugTarget target) throws CoreException {
// removing was previously done is a workspace runnable, but that is
// not possible since it can be a resource callback (marker deletion)
// that causes a breakpoint to be removed
ArrayList<EventRequest> requests = new ArrayList<>(getRequests(target));
// Iterate over a copy of the requests since this list of requests
// can be changed in other threads which would cause an
// ConcurrentModificationException
Iterator<EventRequest> iter = requests.iterator();
EventRequest req;
while (iter.hasNext()) {
req = iter.next();
try {
if (target.isAvailable() && !isExpired(req)) {
EventRequestManager manager = target
.getEventRequestManager();
if (manager != null) {
manager.deleteEventRequest(req); // disable & remove
}
}
} catch (VMDisconnectedException e) {
if (target.isAvailable()) {
JDIDebugPlugin.log(e);
}
} catch (RuntimeException e) {
target.internalError(e);
} finally {
deregisterRequest(req, target);
}
}
fRequestsByTarget.remove(target);
}
/**
* Update the enabled state of the given request in the given target, which
* is associated with this breakpoint. Set the enabled state of the request
* to the enabled state of this breakpoint.
*/
protected void updateEnabledState(EventRequest request,
JDIDebugTarget target) throws CoreException {
internalUpdateEnabledState(request, isEnabled(), target);
}
/**
* Set the enabled state of the given request to the given value, also
* taking into account instance filters.
*/
protected void internalUpdateEnabledState(EventRequest request,
boolean enabled, JDIDebugTarget target) {
if (request.isEnabled() != enabled) {
// change the enabled state
try {
// if the request has expired, do not disable.
// BreakpointRequests that have expired cannot be deleted.
if (!isExpired(request)) {
request.setEnabled(enabled);
}
} catch (VMDisconnectedException e) {
} catch (RuntimeException e) {
target.internalError(e);
}
}
}
/**
* Returns whether this breakpoint has expired.
*/
public boolean isExpired() throws CoreException {
return ensureMarker().getAttribute(EXPIRED, false);
}
/**
* Returns whether the given request is expired
*/
protected boolean isExpired(EventRequest request) {
Boolean requestExpired = (Boolean) request.getProperty(EXPIRED);
if (requestExpired == null) {
return false;
}
return requestExpired.booleanValue();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.core.IJavaBreakpoint#isInstalled()
*/
@Override
public boolean isInstalled() throws CoreException {
return ensureMarker().getAttribute(INSTALL_COUNT, 0) > 0;
}
/**
* Increments the install count of this breakpoint
*/
protected void incrementInstallCount() throws CoreException {
int count = getInstallCount();
setAttribute(INSTALL_COUNT, count + 1);
}
/**
* Returns the <code>INSTALL_COUNT</code> attribute of this breakpoint or 0
* if the attribute is not set.
*/
public int getInstallCount() throws CoreException {
return ensureMarker().getAttribute(INSTALL_COUNT, 0);
}
/**
* Returns whether this trigger breakpoint has expired.
*/
public boolean isTriggerPointExpired() throws CoreException {
return ensureMarker().getAttribute(EXPIRED_TRIGGER_POINT, false);
}
/**
* Decrements the install count of this breakpoint.
*/
protected void decrementInstallCount() throws CoreException {
int count = getInstallCount();
if (count > 0) {
setAttribute(INSTALL_COUNT, count - 1);
}
if (count == 1) {
if (isExpired()) {
// if breakpoint was auto-disabled, re-enable it
setAttributes(fgExpiredEnabledAttributes, new Object[] {
Boolean.FALSE, Boolean.TRUE });
}
}
}
/**
* Sets the type name in which to install this breakpoint.
*/
protected void setTypeName(String typeName) throws CoreException {
setAttribute(TYPE_NAME, typeName);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.core.IJavaBreakpoint#getTypeName()
*/
@Override
public String getTypeName() throws CoreException {
if (fInstalledTypeName == null) {
return ensureMarker().getAttribute(TYPE_NAME, null);
}
return fInstalledTypeName;
}
/**
* Resets the install count attribute on this breakpoint's marker to "0".
* Resets the expired attribute on all breakpoint markers to
* <code>false</code>. Resets the enabled attribute on the breakpoint marker
* to <code>true</code>. If a workbench crashes, the attributes could have
* been persisted in an incorrect state.
*/
private void configureAtStartup() throws CoreException {
List<String> attributes = null;
List<Object> values = new ArrayList<>(3);
if (isInstalled()) {
attributes = new ArrayList<>(3);
attributes.add(INSTALL_COUNT);
values.add(new Integer(0));
}
if (isExpired()) {
if (attributes == null) {
attributes = new ArrayList<>(3);
}
// if breakpoint was auto-disabled, re-enable it
attributes.add(EXPIRED);
values.add(Boolean.FALSE);
attributes.add(ENABLED);
values.add(Boolean.TRUE);
}
if (attributes != null) {
String[] strAttributes = new String[attributes.size()];
setAttributes(attributes.toArray(strAttributes), values.toArray());
}
String[] listeners = readBreakpointListeners();
if (listeners.length > 0) {
fBreakpointListenerIds = new ArrayList<>();
for (String listener : listeners) {
fBreakpointListenerIds.add(listener);
}
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.core.IJavaBreakpoint#getHitCount()
*/
@Override
public int getHitCount() throws CoreException {
return ensureMarker().getAttribute(HIT_COUNT, -1);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.core.IJavaBreakpoint#setHitCount(int)
*/
@Override
public void setHitCount(int count) throws CoreException {
if (getHitCount() != count) {
if (!isEnabled() && count > -1) {
setAttributes(new String[] { ENABLED, HIT_COUNT, EXPIRED },
new Object[] { Boolean.TRUE, new Integer(count),
Boolean.FALSE });
} else {
setAttributes(new String[] { HIT_COUNT, EXPIRED },
new Object[] { new Integer(count), Boolean.FALSE });
}
recreate();
}
}
protected String getMarkerMessage(int hitCount, int suspendPolicy) {
StringBuilder buff = new StringBuilder();
if (hitCount > 0) {
buff.append(MessageFormat
.format(JDIDebugBreakpointMessages.JavaBreakpoint___Hit_Count___0___1,
new Object[] { Integer.toString(hitCount) }));
buff.append(' ');
}
String suspendPolicyString;
if (suspendPolicy == IJavaBreakpoint.SUSPEND_THREAD) {
suspendPolicyString = JDIDebugBreakpointMessages.JavaBreakpoint__suspend_policy__thread__1;
} else {
suspendPolicyString = JDIDebugBreakpointMessages.JavaBreakpoint__suspend_policy__VM__2;
}
buff.append(suspendPolicyString);
return buff.toString();
}
/**
* Sets whether this breakpoint's hit count has expired.
*/
public void setExpired(boolean expired) throws CoreException {
setAttribute(EXPIRED, expired);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.core.IJavaBreakpoint#getSuspendPolicy()
*/
@Override
public int getSuspendPolicy() throws CoreException {
return ensureMarker().getAttribute(SUSPEND_POLICY,
IJavaBreakpoint.SUSPEND_THREAD);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.core.IJavaBreakpoint#setSuspendPolicy(int)
*/
@Override
public void setSuspendPolicy(int suspendPolicy) throws CoreException {
if (getSuspendPolicy() != suspendPolicy) {
setAttribute(SUSPEND_POLICY, suspendPolicy);
recreate();
}
}
/**
* Notifies listeners this breakpoint is to be added to the given target.
*
* @param target
* debug target
*/
protected void fireAdding(IJavaDebugTarget target) {
JDIDebugPlugin plugin = JDIDebugPlugin.getDefault();
if (plugin != null) {
plugin.fireBreakpointAdding(target, this);
}
}
/**
* Notifies listeners this breakpoint has been removed from the given
* target.
*
* @param target
* debug target
*/
protected void fireRemoved(IJavaDebugTarget target) {
JDIDebugPlugin plugin = JDIDebugPlugin.getDefault();
if (plugin != null) {
plugin.fireBreakpointRemoved(target, this);
setInstalledIn(target, false);
}
}
/**
* Notifies listeners this breakpoint has been installed in the given
* target.
*
* @param target
* debug target
*/
protected void fireInstalled(IJavaDebugTarget target) {
JDIDebugPlugin plugin = JDIDebugPlugin.getDefault();
if (plugin != null && !isInstalledIn(target)) {
plugin.fireBreakpointInstalled(target, this);
setInstalledIn(target, true);
}
}
/**
* Returns whether this breakpoint is installed in the given target.
*
* @param target
* @return whether this breakpoint is installed in the given target
*/
protected boolean isInstalledIn(IJavaDebugTarget target) {
return fInstalledTargets != null && fInstalledTargets.contains(target);
}
/**
* Sets this breakpoint as installed in the given target
*
* @param target
* @param installed
* whether installed
*/
protected void setInstalledIn(IJavaDebugTarget target, boolean installed) {
if (installed) {
if (fInstalledTargets == null) {
fInstalledTargets = new HashSet<>();
}
fInstalledTargets.add(target);
} else {
if (fInstalledTargets != null) {
fInstalledTargets.remove(target);
}
}
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jdt.debug.core.IJavaBreakpoint#setThreadFilter(org.eclipse
* .jdt.debug.core.IJavaThread)
*/
@Override
public void setThreadFilter(IJavaThread thread) throws CoreException {
if (!(thread.getDebugTarget() instanceof JDIDebugTarget)
|| !(thread instanceof JDIThread)) {
return;
}
JDIDebugTarget target = (JDIDebugTarget) thread.getDebugTarget();
if (thread != fFilteredThreadsByTarget.put(target, thread)) {
// recreate the breakpoint only if it is not the same thread
// Other breakpoints set attributes on the underlying
// marker and the marker changes are eventually
// propagated to the target. The target then asks the
// breakpoint to update its request. Since thread filters
// are transient properties, they are not set on
// the marker. Thus we must update the request
// here.
recreate(target);
fireChanged();
}
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.debug.core.IDebugEventSetListener#handleDebugEvents(org.eclipse
* .debug.core.DebugEvent[])
*/
@Override
public void handleDebugEvents(DebugEvent[] events) {
for (DebugEvent event : events) {
if (event.getKind() == DebugEvent.TERMINATE) {
Object source = event.getSource();
if (!(source instanceof JDIThread)) {
return;
}
try {
cleanupForThreadTermination((JDIThread) source);
} catch (VMDisconnectedException exception) {
// Thread death often occurs at shutdown.
// A VMDisconnectedException trying to
// update the breakpoint request is
// acceptable.
}
}
}
}
/**
* Removes cached information relevant to this thread which has terminated.
*
* Remove thread filters for terminated threads
*
* Subclasses may override but need to call super.
*/
protected void cleanupForThreadTermination(JDIThread thread) {
JDIDebugTarget target = (JDIDebugTarget) thread.getDebugTarget();
try {
if (thread == getThreadFilter(target)) {
removeThreadFilter(target);
}
} catch (CoreException exception) {
JDIDebugPlugin.log(exception);
}
}
/**
* EventRequest does not support thread filters, so they can't be set
* generically here. However, each of the breakpoint subclasses of
* EventRequest do support thread filters. So subclasses can set thread
* filters on their specific request type.
*/
protected abstract void setRequestThreadFilter(EventRequest request,
ThreadReference thread);
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jdt.debug.core.IJavaBreakpoint#getThreadFilter(org.eclipse
* .jdt.debug.core.IJavaDebugTarget)
*/
@Override
public IJavaThread getThreadFilter(IJavaDebugTarget target) {
return fFilteredThreadsByTarget.get(target);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.core.IJavaBreakpoint#getThreadFilters()
*/
@Override
public IJavaThread[] getThreadFilters() {
IJavaThread[] threads = null;
Collection<IJavaThread> values = fFilteredThreadsByTarget.values();
threads = new IJavaThread[values.size()];
values.toArray(threads);
return threads;
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jdt.debug.core.IJavaBreakpoint#removeThreadFilter(org.eclipse
* .jdt.debug.core.IJavaDebugTarget)
*/
@Override
public void removeThreadFilter(IJavaDebugTarget javaTarget)
throws CoreException {
if (!(javaTarget instanceof JDIDebugTarget)) {
return;
}
JDIDebugTarget target = (JDIDebugTarget) javaTarget;
if (fFilteredThreadsByTarget.remove(target) != null) {
recreate(target);
fireChanged();
}
}
/**
* Returns whether this breakpoint should be installed in the given
* reference type in the given target according to registered breakpoint
* listeners.
*
* @param target
* debug target
* @param type
* reference type or <code>null</code> if this breakpoint is not
* installed in a specific type
*/
protected boolean queryInstallListeners(JDIDebugTarget target,
ReferenceType type) {
JDIDebugPlugin plugin = JDIDebugPlugin.getDefault();
if (plugin != null) {
IJavaType jt = null;
if (type != null) {
jt = JDIType.createType(target, type);
}
return plugin.fireInstalling(target, this, jt);
}
return false;
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jdt.debug.core.IJavaBreakpoint#addInstanceFilter(org.eclipse
* .jdt.debug.core.IJavaObject)
*/
@Override
public void addInstanceFilter(IJavaObject object) throws CoreException {
if (fInstanceFilters == null) {
fInstanceFilters = new ArrayList<>();
}
if (!fInstanceFilters.contains(object)) {
fInstanceFilters.add(object);
recreate((JDIDebugTarget) object.getDebugTarget());
fireChanged();
}
}
/**
* Change notification when there are no marker changes. If the marker does
* not exist, do not fire a change notification (the marker may not exist if
* the associated project was closed).
*/
protected void fireChanged() {
DebugPlugin plugin = DebugPlugin.getDefault();
if (plugin != null && markerExists()) {
plugin.getBreakpointManager().fireBreakpointChanged(this);
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.core.IJavaBreakpoint#getInstanceFilters()
*/
@Override
public IJavaObject[] getInstanceFilters() {
if (fInstanceFilters == null || fInstanceFilters.isEmpty()) {
return fgEmptyInstanceFilters;
}
return fInstanceFilters
.toArray(new IJavaObject[fInstanceFilters.size()]);
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jdt.debug.core.IJavaBreakpoint#removeInstanceFilter(org.eclipse
* .jdt.debug.core.IJavaObject)
*/
@Override
public void removeInstanceFilter(IJavaObject object) throws CoreException {
if (fInstanceFilters == null) {
return;
}
if (fInstanceFilters.remove(object)) {
recreate((JDIDebugTarget) object.getDebugTarget());
fireChanged();
}
}
/**
* An attribute of this breakpoint has changed - recreate event requests in
* all targets.
*/
protected void recreate() throws CoreException {
DebugPlugin plugin = DebugPlugin.getDefault();
if (plugin != null) {
IDebugTarget[] targets = plugin.getLaunchManager()
.getDebugTargets();
for (IDebugTarget target : targets) {
MultiStatus multiStatus = new MultiStatus(
JDIDebugPlugin.getUniqueIdentifier(),
JDIDebugPlugin.ERROR,
JDIDebugBreakpointMessages.JavaBreakpoint_Exception,
null);
IJavaDebugTarget jdiTarget = target
.getAdapter(IJavaDebugTarget.class);
if (jdiTarget instanceof JDIDebugTarget) {
try {
recreate((JDIDebugTarget) jdiTarget);
} catch (CoreException e) {
multiStatus.add(e.getStatus());
}
}
if (!multiStatus.isOK()) {
throw new CoreException(multiStatus);
}
}
}
}
/**
* Recreate this breakpoint in the given target, as long as the target
* already contains this breakpoint.
*
* @param target
* the target in which to re-create the breakpoint
*/
protected void recreate(JDIDebugTarget target) throws CoreException {
if (target.isAvailable() && target.getBreakpoints().contains(this)) {
removeRequests(target);
createRequests(target);
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.Breakpoint#setEnabled(boolean)
*/
@Override
public void setEnabled(boolean enabled) throws CoreException {
super.setEnabled(enabled);
recreate();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.core.IJavaBreakpoint#supportsInstanceFilters()
*/
@Override
public boolean supportsInstanceFilters() {
return true;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.core.IJavaBreakpoint#supportsThreadFilters()
*/
@Override
public boolean supportsThreadFilters() {
return true;
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jdt.debug.core.IJavaBreakpoint#addBreakpointListener(java
* .lang.String)
*/
@Override
public synchronized void addBreakpointListener(String identifier)
throws CoreException {
if (fBreakpointListenerIds == null) {
fBreakpointListenerIds = new ArrayList<>();
}
if (!fBreakpointListenerIds.contains(identifier)) {
fBreakpointListenerIds.add(identifier);
writeBreakpointListeners();
}
}
/**
* Writes the current breakpoint listener collection to the underlying
* marker.
*
* @throws CoreException
*/
private void writeBreakpointListeners() throws CoreException {
StringBuilder buf = new StringBuilder();
Iterator<String> iterator = fBreakpointListenerIds.iterator();
while (iterator.hasNext()) {
buf.append(iterator.next());
if (iterator.hasNext()) {
buf.append(","); //$NON-NLS-1$
}
}
setAttribute(BREAKPOINT_LISTENERS, buf.toString());
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jdt.debug.core.IJavaBreakpoint#removeBreakpointListener(java
* .lang.String)
*/
@Override
public synchronized boolean removeBreakpointListener(String identifier)
throws CoreException {
if (fBreakpointListenerIds != null) {
if (fBreakpointListenerIds.remove(identifier)) {
writeBreakpointListeners();
return true;
}
}
return false;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.core.IJavaBreakpoint#getBreakpointListeners()
*/
@Override
public synchronized String[] getBreakpointListeners() throws CoreException {
// use the cache in case the underlying marker has been deleted
if (fBreakpointListenerIds == null) {
return new String[0];
}
return fBreakpointListenerIds
.toArray(new String[fBreakpointListenerIds.size()]);
}
/**
* Reads breakpoint listeners from the underlying marker.
*
* @return breakpoint listener identifiers stored in this breakpoint's
* marker
* @throws CoreException
* if no marker
*/
private String[] readBreakpointListeners() throws CoreException {
String value = ensureMarker().getAttribute(BREAKPOINT_LISTENERS,
(String) null);
if (value == null) {
return new String[0];
}
return value.split(","); //$NON-NLS-1$
}
}