blob: ae077bbbf770e5186a6da8c03f3bdae342bb4e10 [file] [log] [blame]
package org.eclipse.jdt.internal.debug.core.breakpoints;
/*
* (c) Copyright IBM Corp. 2000, 2001.
* All Rights Reserved.
*/
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
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.DebugEvent;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.model.IBreakpoint;
import org.eclipse.debug.core.model.IValue;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.debug.core.IJavaDebugTarget;
import org.eclipse.jdt.debug.core.IJavaLineBreakpoint;
import org.eclipse.jdt.debug.core.IJavaPrimitiveValue;
import org.eclipse.jdt.debug.core.IJavaThread;
import org.eclipse.jdt.debug.eval.EvaluationManager;
import org.eclipse.jdt.debug.eval.IAstEvaluationEngine;
import org.eclipse.jdt.debug.eval.ICompiledExpression;
import org.eclipse.jdt.debug.eval.IEvaluationEngine;
import org.eclipse.jdt.debug.eval.IEvaluationListener;
import org.eclipse.jdt.debug.eval.IEvaluationResult;
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.JDIStackFrame;
import org.eclipse.jdt.internal.debug.core.model.JDIThread;
import com.sun.jdi.AbsentInformationException;
import com.sun.jdi.ClassNotPreparedException;
import com.sun.jdi.InvalidLineNumberException;
import com.sun.jdi.Location;
import com.sun.jdi.NativeMethodException;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.ThreadReference;
import com.sun.jdi.VMDisconnectedException;
import com.sun.jdi.event.Event;
import com.sun.jdi.event.LocatableEvent;
import com.sun.jdi.request.BreakpointRequest;
import com.sun.jdi.request.EventRequest;
public class JavaLineBreakpoint extends JavaBreakpoint implements IJavaLineBreakpoint {
/**
* Breakpoint attribute storing a breakpoint's conditional expression
* (value <code>"org.eclipse.jdt.debug.core.condition"</code>). This attribute is stored as a
* <code>String</code>.
*/
protected static final String CONDITION= "org.eclipse.jdt.debug.core.condition";
/**
* Breakpoint attribute storing a breakpoint's condition enablement
* (value <code>"org.eclipse.jdt.debug.core.conditionEnabled"</code>). This attribute is stored as an
* <code>boolean</code>.
*/
protected static final String CONDITION_ENABLED= "org.eclipse.jdt.debug.core.conditionEnabled";
/**
* Breakpoint attribute storing a breakpoint's source file name (debug attribute)
* (value <code>"org.eclipse.jdt.debug.core.sourceName"</code>). This attribute is stored as
* a <code>String</code>.
*/
protected static final String SOURCE_NAME= "org.eclipse.jdt.debug.core.sourceName";
private static final String JAVA_LINE_BREAKPOINT = "org.eclipse.jdt.debug.javaLineBreakpointMarker"; //$NON-NLS-1$
// Marker label String keys
protected static final String LINE= "line"; //$NON-NLS-1$
/**
* Maps suspended threads to the suspend event that suspended them
*/
private Map fSuspendEvents= new HashMap();
/**
* The cached compiled expression for this breakpoint. This value must be cleared
* everytime the breakpoint is added to a target.
*/
private ICompiledExpression fCompiledExpression;
public JavaLineBreakpoint() {
}
/**
* @see JDIDebugModel#createLineBreakpoint(IResource, String, int, int, int, int, boolean, Map)
*/
public JavaLineBreakpoint(IResource resource, String typeName, int lineNumber, int charStart, int charEnd, int hitCount, boolean add, Map attributes) throws DebugException {
this(resource, typeName,lineNumber, charStart, charEnd, hitCount, add, attributes, JAVA_LINE_BREAKPOINT);
}
protected JavaLineBreakpoint(final IResource resource, final String typeName, final int lineNumber, final int charStart, final int charEnd, final int hitCount, final boolean add, final Map attributes, final String markerType) throws DebugException {
IWorkspaceRunnable wr= new IWorkspaceRunnable() {
public void run(IProgressMonitor monitor) throws CoreException {
// create the marker
setMarker(resource.createMarker(markerType));
// add attributes
addLineBreakpointAttributes(attributes, getModelIdentifier(), true, lineNumber, charStart, charEnd);
addTypeNameAndHitCount(attributes, typeName, hitCount);
// set attributes
ensureMarker().setAttributes(attributes);
// add to breakpoint manager if requested
register(add);
}
};
run(wr);
}
/**
* @see JavaBreakpoint#addToTarget(JDIDebugTarget)
*/
public void addToTarget(JDIDebugTarget target) throws CoreException {
fCompiledExpression= null;
super.addToTarget(target);
}
public void removeFromTarget(JDIDebugTarget target) throws CoreException {
super.removeFromTarget(target);
}
/**
* @see ILineBreakpoint#getLineNumber()
*/
public int getLineNumber() throws CoreException {
return ensureMarker().getAttribute(IMarker.LINE_NUMBER, -1);
}
/**
* @see ILineBreakpoint#getCharStart()
*/
public int getCharStart() throws CoreException {
return ensureMarker().getAttribute(IMarker.CHAR_START, -1);
}
/**
* @see ILineBreakpoint#getCharEnd()
*/
public int getCharEnd() throws CoreException {
return ensureMarker().getAttribute(IMarker.CHAR_END, -1);
}
/**
* Returns the type of marker associated with Java line breakpoints
*/
public static String getMarkerType() {
return JAVA_LINE_BREAKPOINT;
}
/**
* @see JavaBreakpoint#newRequest(JDIDebugTarget, ReferenceType)
*/
protected EventRequest newRequest(JDIDebugTarget target, ReferenceType type) throws CoreException {
Location location= null;
int lineNumber= getLineNumber();
location= determineLocation(lineNumber, type);
if (location == null) {
// could be an inner type not yet loaded, or line information not available
return null;
}
EventRequest request = createLineBreakpointRequest(location, target);
return request;
}
/**
* Creates, installs, and returns a line breakpoint request at
* the given location for this breakpoint.
*/
protected BreakpointRequest createLineBreakpointRequest(Location location, JDIDebugTarget target) throws CoreException {
BreakpointRequest request = null;
try {
request= target.getEventRequestManager().createBreakpointRequest(location);
configureRequest(request, target);
} catch (VMDisconnectedException e) {
if (!target.isAvailable()) {
return null;
}
JDIDebugPlugin.log(e);
} catch (RuntimeException e) {
JDIDebugPlugin.log(e);
return null;
}
return request;
}
/**
* @see JavaBreakpoint#setRequestThreadFilter(EventRequest)
*/
protected void setRequestThreadFilter(EventRequest request, ThreadReference thread) {
((BreakpointRequest)request).addThreadFilter(thread);
}
/**
* Returns a location for the line number in the given type.
* Returns <code>null</code> if a location cannot be determined.
*/
protected Location determineLocation(int lineNumber, ReferenceType type) {
List locations= null;
try {
locations= type.locationsOfLine(lineNumber);
} catch (AbsentInformationException e) {
return null;
} catch (NativeMethodException e) {
return null;
} catch (InvalidLineNumberException e) {
//possibly in a nested type, will be handled when that class is loaded
return null;
} catch (VMDisconnectedException e) {
return null;
} catch (ClassNotPreparedException e) {
// could be a nested type that is not yet loaded
return null;
} catch (RuntimeException e) {
// not able to retrieve line info
JDIDebugPlugin.log(e);
return null;
}
if (locations != null && locations.size() > 0) {
return (Location) locations.get(0);
}
return null;
}
/**
* 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) && isEnabled())) {
request= recreateRequest(request, target);
}
return request;
}
/**
* @see JavaBreakpoint#recreateRequest(EventRequest, JDIDebugTarget)
*/
protected EventRequest recreateRequest(EventRequest request, JDIDebugTarget target) throws CoreException {
try {
Location location = ((BreakpointRequest) request).location();
request = createLineBreakpointRequest(location, target);
} catch (VMDisconnectedException e) {
if (!target.isAvailable()) {
return request;
}
JDIDebugPlugin.log(e);
} catch (RuntimeException e) {
JDIDebugPlugin.log(e);
}
return request;
}
/**
* Adds the standard attributes of a line breakpoint to
* the given attribute map.
* The standard attributes are:
* <ol>
* <li>IBreakpoint.ID</li>
* <li>IBreakpoint.ENABLED</li>
* <li>IMarker.LINE_NUMBER</li>
* <li>IMarker.CHAR_START</li>
* <li>IMarker.CHAR_END</li>
* </ol>
*
*/
public void addLineBreakpointAttributes(Map attributes, String modelIdentifier, boolean enabled, int lineNumber, int charStart, int charEnd) {
attributes.put(IBreakpoint.ID, modelIdentifier);
attributes.put(IBreakpoint.ENABLED, new Boolean(enabled));
attributes.put(IMarker.LINE_NUMBER, new Integer(lineNumber));
attributes.put(IMarker.CHAR_START, new Integer(charStart));
attributes.put(IMarker.CHAR_END, new Integer(charEnd));
}
/**
* Adds type name and hit count attributes to the given
* map.
*
* If <code>hitCount > 0</code>, adds the <code>HIT_COUNT</code> attribute
* to 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 addTypeNameAndHitCount(Map attributes, String typeName, int hitCount) {
attributes.put(TYPE_NAME, typeName);
if (hitCount > 0) {
attributes.put(HIT_COUNT, new Integer(hitCount));
attributes.put(EXPIRED, Boolean.FALSE);
}
}
/**
* @see JavaBreakpoint#handleBreakpointEvent(Event, JDIDebugTarget)
*
* (From referenced JavaDoc:
* Returns whethers the thread should be resumed
*/
public boolean handleBreakpointEvent(Event event, JDIDebugTarget target) {
ThreadReference threadRef= ((LocatableEvent)event).thread();
JDIThread thread= target.findThread(threadRef);
if (thread == null) {
return true;
} else {
if (hasCondition()) {
try {
return handleConditionalBreakpointEvent(event, thread, target);
} catch (CoreException exception) {
// log error
return !suspendForEvent(event, thread);
}
} else {
return !suspendForEvent(event, thread); // Resume if suspend fails
}
}
}
/**
* Returns whether this breakpoint has an enabled condition
*/
protected boolean hasCondition() {
try {
String condition = getCondition();
return isConditionEnabled() && condition != null && (condition.length() > 0);
} catch (CoreException exception) {
// log error
return false;
}
}
/**
* Suspends the given thread for the given breakpoint event. Returns
* whether the thread suspends.
*/
protected boolean suspendForEvent(Event event, JDIThread thread) {
expireHitCount(event);
return suspend(thread);
}
/**
* Suspends the given thread for the given breakpoint event after
* a conditional expression evaluation. This method tells the thread
* to fire a suspend event immediately instead of queueing the event.
* This is required because of the asynchronous nature of expression
* evaluation. The EventDispatcher has already fired queued events
* by the time the evaluation completes.
*/
protected boolean suspendForCondition(Event event, JDIThread thread) {
expireHitCount(event);
return thread.handleSuspendForBreakpoint(this, false);
}
/**
* Returns whether this breakpoint should resume based on the
* value of its condition.
*
* If there is not an enabled condition which evaluates to <code>true</code>,
* the thread should resume.
*/
protected boolean handleConditionalBreakpointEvent(Event event, JDIThread thread, JDIDebugTarget target) throws CoreException {
if (thread.isPerformingEvaluation()) {
// If an evaluation is already being computed for this thread,
// we can't perform another
return !suspendForEvent(event, thread);
}
final String condition= getCondition();
if (!hasCondition()) {
return !suspendForEvent(event, thread);
}
IMarker marker= getMarker();
if (marker == null) {
return true;
}
IJavaProject project= getJavaProject(marker.getResource().getProject());
IAstEvaluationEngine engine = getEvaluationEngine(target, project);
if (engine == null) {
// If no engine is available, suspend
return !suspendForEvent(event, thread);
}
thread.handleSuspendForBreakpointQuiet(this);
JDIStackFrame frame= (JDIStackFrame)thread.computeNewStackFrames().get(0);
EvaluationListener listener= new EvaluationListener();
if (fCompiledExpression == null) {
fCompiledExpression= engine.getCompiledExpression(condition, frame);
}
if (conditionHasErrors()) {
fireConditionHasErrors();
return !suspendForEvent(event, thread);
}
fSuspendEvents.put(thread, event);
engine.evaluateExpression(fCompiledExpression, frame, listener, DebugEvent.EVALUATION, false);
// Do not resume. When the evaluation returns, the evaluation listener
// will resume the thread if necessary or update for suspension.
return false;
}
/**
* Listens for evaluation completion for condition evaluation.
* If an evaluation evaluates true or has an error, this breakpoint
* will suspend the thread in which the breakpoint was hit.
* If the evaluation returns false, the thread is resumed.
*/
class EvaluationListener implements IEvaluationListener {
IEvaluationResult fResult;
public void evaluationComplete(IEvaluationResult result) {
fResult= result;
JDIThread thread= (JDIThread)result.getThread();
Event event= (Event)fSuspendEvents.get(thread);
if (result.hasErrors()) {
DebugException exception= result.getException();
Throwable wrappedException= exception.getStatus().getException();
if (wrappedException instanceof VMDisconnectedException) {
JDIDebugPlugin.log((VMDisconnectedException)wrappedException);
try {
thread.resumeQuiet();
} catch(DebugException e) {
JDIDebugPlugin.log(e);
}
} else {
fireConditionHasRuntimeErrors(exception);
suspendForCondition(event, thread);
return;
}
}
try {
IValue value= result.getValue();
if (value instanceof IJavaPrimitiveValue) {
// Suspend when the condition evaluates true
IJavaPrimitiveValue javaValue= (IJavaPrimitiveValue)value;
if (javaValue.getJavaType().getName().equals("boolean") && javaValue.getBooleanValue()) {
suspendForCondition(event, thread);
return;
}
}
thread.resumeQuiet();
return;
} catch (DebugException e) {
JDIDebugPlugin.log(e);
}
// Suspend when the an error occurs
suspendForEvent(event, thread);
}
public IEvaluationResult getResult() {
return fResult;
}
};
private void fireConditionHasRuntimeErrors(DebugException exception) {
JDIDebugPlugin.getDefault().fireBreakpointHasRuntimeException(this, exception);
}
/**
* Notifies listeners that a conditional breakpoint expression has been
* compiled that contains errors
*/
private void fireConditionHasErrors() {
JDIDebugPlugin.getDefault().fireBreakpointHasCompilationErrors(this, fCompiledExpression.getErrors());
}
/**
* Returns whether the cached conditional expression has errors or
* <code>false</code> if there is no cached expression
*/
public boolean conditionHasErrors() {
if (fCompiledExpression == null) {
return false;
}
return fCompiledExpression.hasErrors();
}
private IJavaProject getJavaProject(IProject project) {
try {
if (project.exists() && project.hasNature(JavaCore.NATURE_ID)) {
return JavaCore.create(project);
}
} catch (CoreException e) {
}
return null;
}
/**
* Returns an evaluation engine for evaluating this breakpoint's condition
* in the given target and project context.
*/
public IAstEvaluationEngine getEvaluationEngine(IJavaDebugTarget vm, IJavaProject project) {
return ((JDIDebugTarget)vm).getEvaluationEngine(project);
}
/**
* @see IJavaLineBreakpoint#supportsCondition
*/
public boolean supportsCondition() {
return true;
}
/**
* @see IJavaLineBreakpoint#getCondition()
*/
public String getCondition() throws CoreException {
return ensureMarker().getAttribute(CONDITION, null);
}
/**
* @see IJavaLineBreakpoint#setCondition(String)
*/
public void setCondition(String condition) throws CoreException {
// Clear the cached compiled expression
fCompiledExpression= null;
if (condition != null && condition.trim().length() == 0) {
condition = null;
}
ensureMarker().setAttributes(new String []{CONDITION},
new Object[]{condition});
}
/**
* @see IJavaLineBreakpoint#isConditionEnabled()
*/
public boolean isConditionEnabled() throws CoreException {
return ensureMarker().getAttribute(CONDITION_ENABLED, false);
}
/**
* @see IJavaLineBreakpoint#setConditionEnabled(boolean)
*/
public void setConditionEnabled(boolean conditionEnabled) throws CoreException {
ensureMarker().setAttributes(new String []{CONDITION_ENABLED},
new Object[]{new Boolean(conditionEnabled)});
}
}