package org.eclipse.jdt.internal.debug.core; | |
import java.util.HashMap; | |
import java.util.Map; | |
import org.eclipse.core.resources.IMarker; | |
import org.eclipse.core.resources.IResource; | |
import org.eclipse.core.resources.IWorkspaceRunnable; | |
import org.eclipse.core.runtime.CoreException; | |
import org.eclipse.core.runtime.IProgressMonitor; | |
import org.eclipse.debug.core.DebugException; | |
import org.eclipse.debug.core.model.IDebugTarget; | |
import org.eclipse.jdt.core.ICompilationUnit; | |
import org.eclipse.jdt.core.IField; | |
import org.eclipse.jdt.core.IJavaElement; | |
import org.eclipse.jdt.core.ISourceRange; | |
import org.eclipse.jdt.core.IWorkingCopy; | |
import org.eclipse.jdt.core.JavaCore; | |
import org.eclipse.jdt.debug.core.IJavaWatchpoint; | |
import com.sun.jdi.Field; | |
import com.sun.jdi.ReferenceType; | |
import com.sun.jdi.VMDisconnectedException; | |
import com.sun.jdi.VirtualMachine; | |
import com.sun.jdi.event.AccessWatchpointEvent; | |
import com.sun.jdi.event.Event; | |
import com.sun.jdi.event.ModificationWatchpointEvent; | |
import com.sun.jdi.request.AccessWatchpointRequest; | |
import com.sun.jdi.request.EventRequest; | |
import com.sun.jdi.request.ModificationWatchpointRequest; | |
import com.sun.jdi.request.WatchpointRequest; | |
public class JavaWatchpoint extends JavaLineBreakpoint implements IJavaWatchpoint { | |
public static final String JAVA_WATCHPOINT= "org.eclipse.jdt.debug.javaWatchpointMarker"; //$NON-NLS-1$ | |
/** | |
* Watchpoint attribute storing the access value (value <code>"access"</code>). | |
* This attribute is stored as a <code>boolean</code>, indicating whether a | |
* watchpoint is an access watchpoint. | |
*/ | |
private static final String ACCESS= "access"; //$NON-NLS-1$ | |
/** | |
* Watchpoint attribute storing the modification value (value <code>"modification"</code>). | |
* This attribute is stored as a <code>boolean</code>, indicating whether a | |
* watchpoint is a modification watchpoint. | |
*/ | |
private static final String MODIFICATION= "modification"; //$NON-NLS-1$ | |
/** | |
* Watchpoint attribute storing the auto_disabled value (value <code>"auto_disabled"</code>). | |
* This attribute is stored as a <code>boolean</code>, indicating whether a | |
* watchpoint has been auto-disabled (as opposed to being disabled explicitly by the user) | |
*/ | |
private static final String AUTO_DISABLED="auto_disabled"; //$NON-NLS-1$ | |
/** | |
* Breakpoint attribute storing the handle identifier of the Java element | |
* corresponding to the field on which a breakpoint is set | |
* (value <code>"fieldHandle"</code>). This attribute is a <code>String</code>. | |
*/ | |
private static final String FIELD_HANDLE= "fieldHandle"; //$NON-NLS-1$ | |
private final static Integer ACCESS_EVENT= new Integer(0); | |
private final static Integer MODIFICATION_EVENT= new Integer(1); | |
private HashMap fLastEventTypes= new HashMap(10); // maps targets to reason for suspension | |
public JavaWatchpoint() { | |
} | |
/** | |
* Creates and returns a watchpoint on the | |
* given field. | |
* If hitCount > 0, the breakpoint will suspend execution when it is | |
* "hit" the specified number of times. Note: the breakpoint is not | |
* added to the breakpoint manager - it is merely created. | |
* | |
* @param field the field on which to suspend (on access or modification) | |
* @param hitCount the number of times the breakpoint will be hit before | |
* suspending execution - 0 if it should always suspend | |
* @return a watchpoint | |
* @exception DebugException if unable to create the breakpoint marker due | |
* to a lower level exception | |
*/ | |
public JavaWatchpoint(final IField field, final int hitCount) throws DebugException { | |
IWorkspaceRunnable wr= new IWorkspaceRunnable() { | |
public void run(IProgressMonitor monitor) throws CoreException { | |
IResource resource = null; | |
ICompilationUnit compilationUnit = getCompilationUnit(field); | |
if (compilationUnit != null) { | |
resource = compilationUnit.getUnderlyingResource(); | |
} | |
if (resource == null) { | |
resource = field.getJavaProject().getProject(); | |
} | |
if (fMarker == null) { | |
// Only create a marker if one is not already assigned | |
fMarker= resource.createMarker(JAVA_WATCHPOINT); | |
} | |
// configure the standard attributes | |
setStandardAttributes(field); | |
// configure the type handle and hit count | |
setTypeAndHitCount(field.getDeclaringType(), hitCount); | |
// configure the field handle | |
setField(field); | |
// configure the access and modification flags to defaults | |
setDefaultAccessAndModification(); | |
setAutoDisabled(false); | |
// configure the marker as a Java marker | |
IMarker marker = ensureMarker(); | |
Map attributes= marker.getAttributes(); | |
JavaCore.addJavaElementMarkerAttributes(attributes, field); | |
marker.setAttributes(attributes); | |
// Lastly, add the breakpoint manager | |
addToBreakpointManager(); | |
} | |
}; | |
run(wr); | |
} | |
/** | |
* A single watchpoint can create multiple requests. This method provides control over this | |
* property for explicitly choosing which requests (access, modification, or both) to | |
* potentially add. | |
*/ | |
protected void createRequest(JDIDebugTarget target, ReferenceType type) throws CoreException { | |
IField javaField= getField(); | |
Field field= null; | |
field= type.fieldByName(javaField.getElementName()); | |
if (field == null) { | |
// error | |
return; | |
} | |
AccessWatchpointRequest accessRequest= null; | |
ModificationWatchpointRequest modificationRequest= null; | |
if (accessSupportedBy(target.getVM())) { | |
accessRequest= createAccessWatchpoint(target, field); | |
registerRequest(target, accessRequest); | |
} else { | |
notSupported(JDIDebugModelMessages.getString("JavaWatchpoint.no_access_watchpoints")); //$NON-NLS-1$ | |
} | |
if (modificationSupportedBy(target.getVM())) { | |
modificationRequest= createModificationWatchpoint(target, field); | |
registerRequest(target, modificationRequest); | |
} else { | |
notSupported(JDIDebugModelMessages.getString("JavaWatchpoint.no_modification_watchpoints")); //$NON-NLS-1$ | |
} | |
} | |
/** | |
* Returns whether this kind of breakpoint is supported by the given | |
* virtual machine. A watchpoint is supported if both access and | |
* modification watchpoints are supported. | |
*/ | |
public boolean isSupportedBy(VirtualMachine vm) { | |
return (modificationSupportedBy(vm) && accessSupportedBy(vm)); | |
} | |
/** | |
* Returns whether the given virtual machine supports modification watchpoints | |
*/ | |
public boolean modificationSupportedBy(VirtualMachine vm) { | |
return vm.canWatchFieldModification(); | |
} | |
/** | |
* Returns whether the given virtual machine supports access watchpoints | |
*/ | |
public boolean accessSupportedBy(VirtualMachine vm) { | |
return vm.canWatchFieldAccess(); | |
} | |
/** | |
* This watchpoint is not supported for some reason. Alert the user. | |
*/ | |
protected void notSupported(String message) { | |
} | |
/** | |
* Create an access watchpoint for the given breakpoint and associated field | |
*/ | |
protected AccessWatchpointRequest createAccessWatchpoint(JDIDebugTarget target, Field field) throws CoreException { | |
AccessWatchpointRequest request= null; | |
try { | |
request= target.getEventRequestManager().createAccessWatchpointRequest(field); | |
configureRequest(request); | |
} catch (VMDisconnectedException e) { | |
if (target.isTerminated() || target.isDisconnected()) { | |
return null; | |
} | |
target.internalError(e); | |
return null; | |
} catch (RuntimeException e) { | |
target.internalError(e); | |
return null; | |
} | |
return request; | |
} | |
/** | |
* Create a modification watchpoint for the given breakpoint and associated field | |
*/ | |
protected ModificationWatchpointRequest createModificationWatchpoint(JDIDebugTarget target, Field field) throws CoreException { | |
ModificationWatchpointRequest request= null; | |
try { | |
request= target.getEventRequestManager().createModificationWatchpointRequest(field); | |
configureRequest(request); | |
} catch (VMDisconnectedException e) { | |
if (target.isTerminated() || target.isDisconnected()) { | |
return null; | |
} | |
target.internalError(e); | |
return null; | |
} catch (RuntimeException e) { | |
target.internalError(e); | |
return null; | |
} | |
return request; | |
} | |
/** | |
* Update the hit count of an <code>EventRequest</code>. Return a new request with | |
* the appropriate settings. | |
*/ | |
protected EventRequest updateHitCount(EventRequest request, JDIDebugTarget target) throws CoreException { | |
// if the hit count has changed, or the request has expired and is being re-enabled, | |
// create a new request | |
if (hasHitCountChanged(request) || (isExpired(request) && this.isEnabled())) { | |
try { | |
// delete old request | |
//on JDK you cannot delete (disable) an event request that has hit its count filter | |
if (!isExpired(request)) { | |
target.getEventRequestManager().deleteEventRequest(request); // disable & remove | |
} | |
Field field= ((WatchpointRequest) request).field(); | |
if (request instanceof AccessWatchpointRequest) { | |
request= createAccessWatchpoint(target, field); | |
} else if (request instanceof ModificationWatchpointRequest) { | |
request= createModificationWatchpoint(target, field); | |
} | |
} catch (VMDisconnectedException e) { | |
if (target.isTerminated() || target.isDisconnected()) { | |
return request; | |
} | |
target.internalError(e); | |
return request; | |
} catch (RuntimeException e) { | |
JDIDebugPlugin.logError(e); | |
} | |
} | |
return request; | |
} | |
/** | |
* Enable this watchpoint. | |
* | |
* If the watchpoint is not watching access or modification, | |
* set the default values. If this isn't done, the resulting | |
* state (enabled with access and modification both disabled) | |
* is ambiguous. | |
*/ | |
public void setEnabled(boolean enabled) throws CoreException { | |
super.setEnabled(enabled); | |
if (isEnabled()) { | |
if (!(isAccess() || isModification())) { | |
setDefaultAccessAndModification(); | |
} | |
} | |
} | |
/** | |
* @see IJavaWatchpoint#isAccess | |
*/ | |
public boolean isAccess() throws CoreException { | |
return ensureMarker().getAttribute(ACCESS, false); | |
} | |
/** | |
* @see IJavaWatchpoint#setAccess | |
*/ | |
public void setAccess(boolean access) throws CoreException { | |
if (access == isAccess()) { | |
return; | |
} | |
ensureMarker().setAttribute(ACCESS, access); | |
if (access && !isEnabled()) { | |
setEnabled(true); | |
} else if (!(access || isModification())) { | |
setEnabled(false); | |
} | |
} | |
/** | |
* @see IJavaWatchpoint#isModification | |
*/ | |
public boolean isModification() throws CoreException { | |
return ensureMarker().getAttribute(MODIFICATION, false); | |
} | |
/** | |
* @see IJavaWatchpoint#setModification(boolean) | |
*/ | |
public void setModification(boolean modification) throws CoreException { | |
if (modification == isModification()) { | |
return; | |
} | |
ensureMarker().setAttribute(MODIFICATION, modification); | |
if (modification && !isEnabled()) { | |
setEnabled(true); | |
} else if (!(modification || isAccess())) { | |
setEnabled(false); | |
} | |
} | |
/** | |
* Sets the default access and modification attributes of the watchpoint. | |
* The default values are: | |
* <ul> | |
* <li>access = <code>false</code> | |
* <li>modification = <code>true</code> | |
* <ul> | |
*/ | |
private void setDefaultAccessAndModification() throws CoreException { | |
Object[] values= new Object[]{Boolean.FALSE, Boolean.TRUE}; | |
String[] attributes= new String[]{ACCESS, MODIFICATION}; | |
ensureMarker().setAttributes(attributes, values); | |
} | |
/** | |
* Sets the <code>FIELD_HANDLE</code> attribute of the given breakpoint, associated | |
* with the given IField. | |
*/ | |
public void setField(IField field) throws CoreException { | |
String handle = field.getHandleIdentifier(); | |
setFieldHandleIdentifier(handle); | |
} | |
/** | |
* Sets the <code>FIELD_HANDLE</code> attribute of the given breakpoint. | |
*/ | |
public void setFieldHandleIdentifier(String handle) throws CoreException { | |
ensureMarker().setAttribute(FIELD_HANDLE, handle); | |
} | |
/** | |
* Set standard attributes of a watchpoint | |
*/ | |
public void setStandardAttributes(IField field) throws CoreException { | |
// find the source range if available | |
int start = -1; | |
int stop = -1; | |
ISourceRange range = field.getSourceRange(); | |
if (range != null) { | |
start = range.getOffset(); | |
stop = start + range.getLength() - 1; | |
} | |
super.setLineBreakpointAttributes(getPluginIdentifier(), true, -1, start, stop); | |
} | |
/** | |
* Sets the <code>AUTO_DISABLED</code> attribute of this watchpoint. | |
*/ | |
private void setAutoDisabled(boolean autoDisabled) throws CoreException { | |
ensureMarker().setAttribute(AUTO_DISABLED, autoDisabled); | |
} | |
/** | |
* Returns the underlying compilation unit of an element. | |
*/ | |
public static ICompilationUnit getCompilationUnit(IJavaElement element) { | |
if (element instanceof IWorkingCopy) { | |
return (ICompilationUnit) ((IWorkingCopy) element).getOriginalElement(); | |
} | |
if (element instanceof ICompilationUnit) { | |
return (ICompilationUnit) element; | |
} | |
IJavaElement parent = element.getParent(); | |
if (parent != null) { | |
return getCompilationUnit(parent); | |
} | |
return null; | |
} | |
/** | |
* @see IJavaWatchpoint#getField() | |
*/ | |
public IField getField() throws CoreException { | |
String handle= getFieldHandleIdentifier(); | |
if (handle != null && handle != "") { //$NON-NLS-1$ | |
return (IField)JavaCore.create(handle); | |
} | |
return null; | |
} | |
/** | |
* Returns the <code>FIELD_HANDLE</code> attribute of this watchpoint. | |
*/ | |
public String getFieldHandleIdentifier() throws CoreException { | |
return (String) ensureMarker().getAttribute(FIELD_HANDLE); | |
} | |
/* | |
public String getFormattedThreadText(String threadName, String typeName, boolean systemThread) { | |
String fieldName= getField().getElementName(); | |
if (fLastEventType == ACCESS_EVENT) { | |
if (systemThread) { | |
return getFormattedString(ACCESS_SYS, new String[] {threadName, fieldName, typeName}); | |
} else { | |
return getFormattedString(ACCESS_USR, new String[] {threadName, fieldName, typeName}); | |
} | |
} else if (fLastEventType == MODIFICATION_EVENT) { | |
// modification | |
if (systemThread) { | |
return getFormattedString(MODIFICATION_SYS, new String[] {threadName, fieldName, typeName}); | |
} else { | |
return getFormattedString(MODIFICATION_USR, new String[] {threadName, fieldName, typeName}); | |
} | |
} | |
return ""; | |
} | |
public String getMarkerText(boolean qualified, String memberString) { | |
String lineInfo= super.getMarkerText(qualified, memberString); | |
String state= null; | |
boolean access= isAccess(); | |
boolean modification= isModification(); | |
if (access && modification) { | |
state= BOTH; | |
} else if (access) { | |
state= ACCESS; | |
} else if (modification) { | |
state= MODIFICATION; | |
} | |
String label= null; | |
if (state == null) { | |
label= lineInfo; | |
} else { | |
String format= DebugJavaUtils.getResourceString(FORMAT); | |
state= DebugJavaUtils.getResourceString(state); | |
label= MessageFormat.format(format, new Object[] {state, lineInfo}); | |
} | |
return label; | |
} | |
*/ | |
/** | |
* Store the type of the event, then handle it as specified in | |
* the superclass. This is useful for correctly generating the | |
* thread text when asked (assumes thread text is requested after | |
* the event is passed to this breakpoint. | |
* | |
* Also, @see JavaBreakpoint#handleEvent(Event) | |
*/ | |
public boolean handleEvent(Event event, JDIDebugTarget target) { | |
if (event instanceof AccessWatchpointEvent) { | |
fLastEventTypes.put(target, ACCESS_EVENT); | |
} else if (event instanceof ModificationWatchpointEvent) { | |
fLastEventTypes.put(target, MODIFICATION_EVENT); | |
} | |
return super.handleEvent(event, target); | |
} | |
/** | |
* Update the enabled state of the given request, 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) throws CoreException { | |
boolean enabled = isEnabled(); | |
if (request instanceof AccessWatchpointRequest) { | |
if (isAccess()) { | |
if (enabled != request.isEnabled()) { | |
internalUpdateEnabeldState(request, enabled); | |
} | |
} else { | |
if (request.isEnabled()) { | |
internalUpdateEnabeldState(request, false); | |
} | |
} | |
} | |
if (request instanceof ModificationWatchpointRequest) { | |
if (isModification()) { | |
if (enabled != request.isEnabled()) { | |
internalUpdateEnabeldState(request, enabled); | |
} | |
} else { | |
if (request.isEnabled()) { | |
internalUpdateEnabeldState(request, false); | |
} | |
} | |
} | |
} | |
protected void internalUpdateEnabeldState(EventRequest request, boolean enabled) { | |
// change the enabled state | |
try { | |
// if the request has expired, do not enable/disable. | |
// Requests that have expired cannot be deleted. | |
if (!isExpired(request)) { | |
request.setEnabled(enabled); | |
} | |
} catch (VMDisconnectedException e) { | |
} catch (RuntimeException e) { | |
JDIDebugPlugin.logError(e); | |
} | |
} | |
public boolean isAccessSuspend(IDebugTarget target) { | |
Integer lastEventType= (Integer) fLastEventTypes.get(target); | |
if (lastEventType == null) { | |
return false; | |
} | |
return lastEventType.equals(ACCESS_EVENT); | |
} | |
} | |