| /******************************************************************************* |
| * 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$ |
| } |
| } |