/******************************************************************************* * Copyright (c) 2000, 2016 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * 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.OperationCanceledException; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.jobs.Job; 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.event.ThreadStartEvent; 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) { // wait for any thread start event sets to complete processing // see bug 271700 try { Job.getJobManager().join(ThreadStartEvent.class, null); } catch (OperationCanceledException e) { } catch (InterruptedException e) { } 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()) { 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) { StringBuffer buff = new StringBuffer(); 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 { StringBuffer buf = new StringBuffer(); 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$ } }