package org.eclipse.jdt.internal.debug.core; | |
import java.text.MessageFormat; | |
import java.util.List; | |
import java.util.Map; | |
import org.eclipse.core.resources.*; | |
import org.eclipse.core.runtime.CoreException; | |
import org.eclipse.core.runtime.IProgressMonitor; | |
import org.eclipse.debug.core.DebugException; | |
import org.eclipse.jdt.core.*; | |
import org.eclipse.jdt.debug.core.IJavaDebugConstants; | |
import com.sun.jdi.*; | |
import com.sun.jdi.event.*; | |
import com.sun.jdi.request.*; | |
public class Watchpoint extends LineBreakpoint { | |
// Thread label String keys | |
private static final String ACCESS_SYS= THREAD_LABEL + "access_sys"; | |
private static final String ACCESS_USR= THREAD_LABEL + "access_usr"; | |
private static final String MODIFICATION_SYS= THREAD_LABEL + "modification_sys"; | |
private static final String MODIFICATION_USR= THREAD_LABEL + "modification_usr"; | |
// Error String keys | |
private final static String PREFIX= "jdi_breakpoint."; | |
private final static String ERROR = PREFIX + "error."; | |
private final static String ERROR_ACCESS_WATCHPOINT_NOT_SUPPORTED = ERROR + "access.not_supported"; | |
private final static String ERROR_MODIFICATION_WATCHPOINT_NOT_SUPPORTED = ERROR + "modification.net_supported"; | |
// Marker label String keys | |
protected final static String WATCHPOINT= MARKER_LABEL + "watchpoint."; | |
protected final static String FORMAT= WATCHPOINT + "format"; | |
protected final static String ACCESS= WATCHPOINT + "access"; | |
protected final static String MODIFICATION= WATCHPOINT + "modification"; | |
protected final static String BOTH= WATCHPOINT + "both"; | |
static String fMarkerType= IJavaDebugConstants.JAVA_WATCHPOINT; | |
private final static int ACCESS_EVENT= 0; | |
private final static int MODIFICATION_EVENT= 1; | |
private int fLastEventType= -1; | |
/** | |
* Create a watchpoint for the given marker | |
*/ | |
public Watchpoint(IMarker marker) { | |
super(marker); | |
} | |
/** | |
* 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 Watchpoint(final IField field) 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(fMarkerType); | |
} | |
// configure the standard attributes | |
setStandardAttributes(field); | |
// configure the type handle and hit count | |
setTypeAndHitCount(field.getDeclaringType(), 0); | |
// configure the field handle | |
setField(field); | |
// configure the access and modification flags to defaults | |
setDefaultAccessAndModification(); | |
setAutoDisabled(false); | |
// configure the marker as a Java marker | |
Map attributes= getAttributes(); | |
JavaCore.addJavaElementMarkerAttributes(attributes, field); | |
setAttributes(attributes); | |
} | |
}; | |
run(wr); | |
} | |
/** | |
* @see JavaBreakpoint#installIn(JDIDebugTarget) | |
*/ | |
public void addToTarget(JDIDebugTarget target) { | |
selectiveAdd(target, true, true); | |
} | |
/** | |
* 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 selectiveAdd(JDIDebugTarget target, boolean accessCheck, boolean modificationCheck) { | |
fTarget= target; | |
String topLevelName= getTopLevelTypeName(); | |
if (topLevelName == null) { | |
// internalError(ERROR_BREAKPOINT_NO_TYPE); | |
return; | |
} | |
List classes= target.jdiClassesByName(topLevelName); | |
if (classes == null || classes.isEmpty()) { | |
// defer | |
target.defer(this, topLevelName); | |
return; | |
} | |
IField javaField= getField(); | |
Field field= null; | |
ReferenceType reference= null; | |
for (int i=0; i<classes.size(); i++) { | |
reference= (ReferenceType) classes.get(i); | |
field= reference.fieldByName(javaField.getElementName()); | |
if (field == null) { | |
return; | |
} | |
AccessWatchpointRequest accessRequest= null; | |
ModificationWatchpointRequest modificationRequest= null; | |
// If we're not supposed to check access or modification, just retrieve the | |
// existing request | |
if (!accessCheck) { | |
accessRequest= target.getAccessWatchpointRequest(field); | |
} | |
if (!modificationCheck) { | |
modificationRequest= target.getModificationWatchpointRequest(field); | |
} | |
if (isAccess() && accessCheck) { | |
if (accessSupportedBy(target.getVM())) { | |
accessRequest= accessWatchpointAdded(target, field); | |
} else { | |
notSupported(ERROR_ACCESS_WATCHPOINT_NOT_SUPPORTED); | |
} | |
} | |
if (isModification() && modificationCheck) { | |
if (modificationSupportedBy(target.getVM())) { | |
modificationRequest= modificationWatchpointAdded(target, field); | |
} else { | |
notSupported(ERROR_MODIFICATION_WATCHPOINT_NOT_SUPPORTED); | |
} | |
} | |
if (!(accessRequest == null && modificationRequest == null)) { | |
Object[] requests= {accessRequest, modificationRequest}; | |
target.installBreakpoint(this, requests); | |
try { | |
incrementInstallCount(); | |
} catch (CoreException e) { | |
target.internalError(e); | |
} | |
} | |
} | |
} | |
/** | |
* 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 error_key) { | |
} | |
/** | |
* An access watchpoint has been added. | |
* Create or update the request. | |
*/ | |
protected AccessWatchpointRequest accessWatchpointAdded(JDIDebugTarget target, Field field) { | |
AccessWatchpointRequest request= target.getAccessWatchpointRequest(field); | |
if (request == null) { | |
request= createAccessWatchpoint(target, field); | |
} | |
// Important: Enable only after request has been configured | |
request.setEnabled(isEnabled()); | |
return request; | |
} | |
/** | |
* Create an access watchpoint for the given breakpoint and associated field | |
*/ | |
protected AccessWatchpointRequest createAccessWatchpoint(JDIDebugTarget target, Field field) { | |
AccessWatchpointRequest request= null; | |
try { | |
request= target.getEventRequestManager().createAccessWatchpointRequest(field); | |
configureRequest(request); | |
} catch (VMDisconnectedException e) { | |
return null; | |
} catch (RuntimeException e) { | |
target.internalError(e); | |
return null; | |
} | |
return request; | |
} | |
/** | |
* A modification watchpoint has been added. | |
* Create or update the request. | |
*/ | |
protected ModificationWatchpointRequest modificationWatchpointAdded(JDIDebugTarget target, Field field) { | |
ModificationWatchpointRequest request= target.getModificationWatchpointRequest(field); | |
if (request == null) { | |
request= createModificationWatchpoint(target, field); | |
} | |
// Important: only enable a request after it has been configured | |
request.setEnabled(isEnabled()); | |
return request; | |
} | |
/** | |
* Create a modification watchpoint for the given breakpoint and associated field | |
*/ | |
protected ModificationWatchpointRequest createModificationWatchpoint(JDIDebugTarget target, Field field) { | |
ModificationWatchpointRequest request= null; | |
try { | |
request= target.getEventRequestManager().createModificationWatchpointRequest(field); | |
configureRequest(request); | |
} catch (VMDisconnectedException e) { | |
return null; | |
} catch (RuntimeException e) { | |
target.internalError(e); | |
return null; | |
} | |
return request; | |
} | |
/** | |
* A watchpoint has been changed. | |
* Update the request. | |
*/ | |
public void changeForTarget(JDIDebugTarget target) { | |
Object[] requests= (Object[])target.getRequest(this); | |
for (int i=0; i < requests.length; i++) { | |
WatchpointRequest request= (WatchpointRequest)requests[i]; | |
if (request == null) { | |
if ((i == 0) && isAccess()) { | |
selectiveAdd(target, true, false); | |
} | |
if ((i == 1) && isModification()) { | |
selectiveAdd(target, false, true); | |
} | |
continue; | |
} | |
if ((!isAccess() && (request instanceof AccessWatchpointRequest)) || | |
(!isModification() && (request instanceof ModificationWatchpointRequest))) { | |
target.getEventRequestManager().deleteEventRequest(request); // disable & remove | |
continue; | |
} | |
request= (WatchpointRequest)updateHitCount(request, target); | |
if (request != null) { | |
updateEnabledState(request); | |
requests[i]= 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) { | |
// 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) { | |
} catch (RuntimeException e) { | |
logError(e); | |
} | |
} | |
return request; | |
} | |
/** | |
* @see JavaBreakpoint#removeFromTarget(JDIDebugTarget) | |
*/ | |
public void removeFromTarget(JDIDebugTarget target) { | |
Object[] requests= (Object[]) target.getRequest(this); | |
if (requests == null) { | |
//deferred breakpoint | |
if (!this.exists()) { | |
//resource no longer exists | |
return; | |
} | |
String name= getTopLevelTypeName(); | |
if (name == null) { | |
// internalError(ERROR_BREAKPOINT_NO_TYPE); | |
return; | |
} | |
List breakpoints= (List) target.getDeferredBreakpointsByClass(name); | |
if (breakpoints == null) { | |
return; | |
} | |
breakpoints.remove(this); | |
if (breakpoints.isEmpty()) { | |
target.removeDeferredBreakpointByClass(name); | |
} | |
} else { | |
//installed breakpoint | |
try { | |
for (int i=0; i<requests.length; i++) { | |
WatchpointRequest request= (WatchpointRequest)requests[i]; | |
if (request == null) { | |
continue; | |
} | |
target.getEventRequestManager().deleteEventRequest(request); // disable & remove | |
} | |
try { | |
decrementInstallCount(); | |
} catch (CoreException e) { | |
fTarget= null; | |
logError(e); | |
} | |
} catch (VMDisconnectedException e) { | |
fTarget= null; | |
return; | |
} catch (RuntimeException e) { | |
fTarget= null; | |
logError(e); | |
} | |
} | |
fTarget= null; | |
} | |
/** | |
* 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 enable() { | |
if (!(isAccess() || isModification())) { | |
setDefaultAccessAndModification(); | |
} | |
super.enable(); | |
} | |
/** | |
* Returns whether this watchpoint is an access watchpoint | |
*/ | |
public boolean isAccess() { | |
return getAttribute(IJavaDebugConstants.ACCESS, false); | |
} | |
/** | |
* Toggle the access attribute of this watchpoint | |
*/ | |
public void toggleAccess() { | |
setAccess(!isAccess()); | |
} | |
/** | |
* Sets the access attribute of this watchpoint. If access is set to true | |
* and the watchpoint is disabled, enable the watchpoint. If both access and | |
* modification are false, disable the watchpoint. | |
*/ | |
public void setAccess(boolean access) { | |
if (access == isAccess()) { | |
return; | |
} | |
try { | |
setAttribute(IJavaDebugConstants.ACCESS, access); | |
if (access && !isEnabled()) { | |
enable(); | |
} else if (!(access || isModification())) { | |
disable(); | |
} | |
} catch (CoreException ce) { | |
logError(ce); | |
} | |
if (fTarget != null) { | |
// Notify the target that this watchpoint has changed | |
changeForTarget(fTarget); | |
} | |
} | |
/** | |
* Returns whether this watchpoint is a modification watchpoint | |
*/ | |
public boolean isModification() { | |
return getAttribute(IJavaDebugConstants.MODIFICATION, false); | |
} | |
/** | |
* Toggle the modification attribute of this watchpoint | |
*/ | |
public void toggleModification() { | |
setModification(!isModification()); | |
} | |
/** | |
* Sets the modification attribute of this watchpoint. If modification is set to true | |
* and the watchpoint is disabled, enable the watchpoint. If both access and | |
* modification are false, disable the watchpoint. | |
*/ | |
public void setModification(boolean modification) { | |
if (modification == isModification()) { | |
return; | |
} | |
try { | |
setAttribute(IJavaDebugConstants.MODIFICATION, modification); | |
if (modification && !isEnabled()) { | |
enable(); | |
} else if (!(modification || isAccess())) { | |
disable(); | |
} | |
} catch (CoreException ce) { | |
logError(ce); | |
} | |
if (fTarget != null) { | |
// Notify the target that this watchpoint has changed | |
changeForTarget(fTarget); | |
} | |
} | |
/** | |
* 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> | |
*/ | |
public void setDefaultAccessAndModification() { | |
Object[] values= new Object[]{Boolean.FALSE, Boolean.TRUE}; | |
String[] attributes= new String[]{IJavaDebugConstants.ACCESS, IJavaDebugConstants.MODIFICATION}; | |
try { | |
setAttributes(attributes, values); | |
} catch (CoreException ce) { | |
logError(ce); | |
} | |
} | |
/** | |
* 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 { | |
setAttribute(IJavaDebugConstants.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 the given breakpoint. | |
*/ | |
public void setAutoDisabled(boolean autoDisabled) throws CoreException { | |
setAttribute(IJavaDebugConstants.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; | |
} | |
/** | |
* Generate the field associated with the given marker | |
*/ | |
public IField getField(IMarker marker) { | |
String handle= getFieldHandleIdentifier(marker); | |
if (handle != null && handle != "") { | |
return (IField)JavaCore.create(handle); | |
} | |
return null; | |
} | |
/** | |
* Generate the field associated with this watchpoint | |
*/ | |
public IField getField() { | |
String handle= getFieldHandleIdentifier(); | |
if (handle != null && handle != "") { | |
return (IField)JavaCore.create(handle); | |
} | |
return null; | |
} | |
/** | |
* Returns the <code>FIELD_HANDLE</code> attribute of the given marker. | |
*/ | |
public String getFieldHandleIdentifier(IMarker marker) { | |
String handle; | |
try { | |
handle= (String)marker.getAttribute(IJavaDebugConstants.FIELD_HANDLE); | |
} catch (CoreException ce) { | |
handle= ""; | |
logError(ce); | |
} | |
return handle; | |
} | |
/** | |
* Returns the <code>FIELD_HANDLE</code> attribute of this watchpoint. | |
*/ | |
public String getFieldHandleIdentifier() { | |
String handle; | |
try { | |
handle= (String)getAttribute(IJavaDebugConstants.FIELD_HANDLE); | |
} catch (CoreException ce) { | |
handle= ""; | |
logError(ce); | |
} | |
return handle; | |
} | |
/** | |
* @see JavaBreakpoint | |
*/ | |
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 void handleEvent(Event event, JDIDebugTarget target) { | |
if (event instanceof AccessWatchpointEvent) { | |
fLastEventType= ACCESS_EVENT; | |
} else if (event instanceof ModificationWatchpointEvent) { | |
fLastEventType= MODIFICATION_EVENT; | |
} | |
super.handleEvent(event, target); | |
} | |
/** | |
* Returns the <code>HIT_COUNT</code> attribute of the given breakpoint | |
* or -1 if the attribute is not set. | |
*/ | |
public int getHitCount() { | |
return getAttribute(IJavaDebugConstants.HIT_COUNT, -1); | |
} | |
/** | |
* Sets the <code>HIT_COUNT</code> attribute of the given breakpoint, | |
* and resets the <code>EXPIRED</code> attribute to false (since, if | |
* the hit count is changed, the breakpoint should no longer be expired). | |
*/ | |
public void setHitCount(int count) throws CoreException { | |
setAttributes(new String[]{IJavaDebugConstants.HIT_COUNT, IJavaDebugConstants.EXPIRED}, | |
new Object[]{new Integer(count), Boolean.FALSE}); | |
} | |
} | |