blob: b4bbe5ede829c745cdc0113fd477584a2f1e202e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2015 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
* Igor Fedorenko - Bug 368212 - JavaLineBreakpoint.computeJavaProject does not let ISourceLocator evaluate the stackFrame
* Jesper Møller - Bug 422016 - [1.8] Having reference expressions or lambdas in file triggers warning for missing line numbers
*******************************************************************************/
package org.eclipse.jdt.internal.debug.core.breakpoints;
import java.util.ArrayList;
import java.util.HashMap;
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.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.IStatusHandler;
import org.eclipse.debug.core.model.IBreakpoint;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.debug.core.model.IValue;
import org.eclipse.jdi.internal.AccessibleImpl;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.debug.core.IJavaLineBreakpoint;
import org.eclipse.jdt.debug.core.IJavaStackFrame;
import org.eclipse.jdt.debug.core.IJavaThread;
import org.eclipse.jdt.debug.core.JDIDebugModel;
import org.eclipse.jdt.debug.eval.ICompiledExpression;
import org.eclipse.jdt.internal.debug.core.JDIDebugPlugin;
import org.eclipse.jdt.internal.debug.core.JavaDebugUtils;
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.ibm.icu.text.MessageFormat;
import com.sun.jdi.AbsentInformationException;
import com.sun.jdi.ClassNotPreparedException;
import com.sun.jdi.InterfaceType;
import com.sun.jdi.Location;
import com.sun.jdi.NativeMethodException;
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.Event;
import com.sun.jdi.request.BreakpointRequest;
import com.sun.jdi.request.EventRequest;
import com.sun.jdi.request.EventRequestManager;
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"; //$NON-NLS-1$
/**
* Breakpoint attribute storing a breakpoint's condition enabled state
* (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"; //$NON-NLS-1$
/**
* Breakpoint attribute storing a breakpoint's condition suspend policy
* (value <code>" org.eclipse.jdt.debug.core.conditionSuspendOnTrue"
* </code>). This attribute is stored as an <code>boolean</code>.
*/
protected static final String CONDITION_SUSPEND_ON_TRUE = "org.eclipse.jdt.debug.core.conditionSuspendOnTrue"; //$NON-NLS-1$
/**
* 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"; //$NON-NLS-1$
public static final String JAVA_LINE_BREAKPOINT = "org.eclipse.jdt.debug.javaLineBreakpointMarker"; //$NON-NLS-1$
/**
* Maps suspended threads to the suspend event that suspended them
*/
private Map<IJavaThread, ICompiledExpression> fSuspendEvents = new HashMap<IJavaThread, ICompiledExpression>();
/**
* The map of cached compiled expressions (ICompiledExpression) for this
* breakpoint, keyed by thread. This value must be cleared every time the
* breakpoint is added to a target.
*/
private Map<IJavaThread, ICompiledExpression> fCompiledExpressions = new HashMap<IJavaThread, ICompiledExpression>();
/**
* Cache of projects for stack frames to avoid repetitive project resolution
* on conditional breakpoints.
*/
private Map<IJavaStackFrame, IJavaProject> fProjectsByFrame = new HashMap<IJavaStackFrame, IJavaProject>();
/**
* The map of the result value of the condition (IValue) for this
* breakpoint, keyed by debug target.
*/
private Map<IDebugTarget, IValue> fConditionValues = new HashMap<IDebugTarget, IValue>();
/**
* Status code indicating that a request to create a breakpoint in a type
* with no line number attributes has occurred.
*/
public static final int NO_LINE_NUMBERS = 162;
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<String, Object> 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<String, Object> attributes, final String markerType)
throws DebugException {
IWorkspaceRunnable wr = new IWorkspaceRunnable() {
@Override
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
attributes.put(SUSPEND_POLICY, new Integer(
getDefaultSuspendPolicy()));
ensureMarker().setAttributes(attributes);
// add to breakpoint manager if requested
register(add);
}
};
run(getMarkerRule(resource), wr);
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jdt.internal.debug.core.breakpoints.JavaBreakpoint#addToTarget
* (org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget)
*/
@Override
public void addToTarget(JDIDebugTarget target) throws CoreException {
clearCachedExpressionFor(target);
super.addToTarget(target);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.internal.debug.core.breakpoints.JavaBreakpoint#
* removeFromTarget
* (org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget)
*/
@Override
public void removeFromTarget(JDIDebugTarget target) throws CoreException {
clearCachedExpressionFor(target);
clearCachedSuspendEvents(target);
fConditionValues.remove(target);
super.removeFromTarget(target);
}
/**
* Removes all suspend events which are currently being cached for threads
* in the given target.
*/
protected void clearCachedSuspendEvents(JDIDebugTarget target) {
removeCachedThreads(fSuspendEvents, target);
}
private void removeCachedThreads(Map<IJavaThread, ICompiledExpression> map, JDIDebugTarget target) {
Set<IJavaThread> threads = map.keySet();
List<IJavaThread> threadsToRemove = new ArrayList<IJavaThread>();
Iterator<IJavaThread> iter = threads.iterator();
JDIThread thread;
while (iter.hasNext()) {
thread = (JDIThread) iter.next();
if (thread.getDebugTarget() == target) {
threadsToRemove.add(thread);
}
}
iter = threadsToRemove.iterator();
while (iter.hasNext()) {
map.remove(iter.next());
}
}
/**
* Removes all compiled expressions which are currently being cached for
* threads in the given target.
*/
protected void clearCachedExpressionFor(JDIDebugTarget target) {
removeCachedThreads(fCompiledExpressions, target);
// clean up cached projects for stack frames
synchronized (fProjectsByFrame) {
Set<IJavaStackFrame> frames = fProjectsByFrame.keySet();
List<IJavaStackFrame> framesToRemove = new ArrayList<IJavaStackFrame>();
Iterator<IJavaStackFrame> iter = frames.iterator();
JDIStackFrame frame;
while (iter.hasNext()) {
frame = (JDIStackFrame) iter.next();
if (frame.getDebugTarget() == target) {
framesToRemove.add(frame);
}
}
iter = framesToRemove.iterator();
while (iter.hasNext()) {
fProjectsByFrame.remove(iter.next());
}
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.ILineBreakpoint#getLineNumber()
*/
@Override
public int getLineNumber() throws CoreException {
return ensureMarker().getAttribute(IMarker.LINE_NUMBER, -1);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.ILineBreakpoint#getCharStart()
*/
@Override
public int getCharStart() throws CoreException {
return ensureMarker().getAttribute(IMarker.CHAR_START, -1);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.ILineBreakpoint#getCharEnd()
*/
@Override
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;
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jdt.internal.debug.core.breakpoints.JavaBreakpoint#newRequest
* (org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget,
* com.sun.jdi.ReferenceType)
*/
@Override
protected EventRequest[] newRequests(JDIDebugTarget target,
ReferenceType type) throws CoreException {
int lineNumber = getLineNumber();
List<Location> locations = determineLocations(lineNumber, type, target);
if (locations == null || locations.isEmpty()) {
// could be an inner type not yet loaded, or line information not
// available
return null;
}
EventRequest[] requests = new EventRequest[locations.size()];
int i = 0;
for(Location location : locations) {
requests[i] = createLineBreakpointRequest(location, target);
i++;
}
return requests;
}
/**
* 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;
EventRequestManager manager = target.getEventRequestManager();
if (manager != null) {
try {
request = manager.createBreakpointRequest(location);
configureRequest(request, target);
} catch (VMDisconnectedException e) {
if (!target.isAvailable()) {
return null;
}
JDIDebugPlugin.log(e);
} catch (RuntimeException e) {
target.internalError(e);
return null;
}
return request;
}
target.requestFailed(
JDIDebugBreakpointMessages.JavaLineBreakpoint_Unable_to_create_breakpoint_request___VM_disconnected__1,
null);
return null;
}
/**
* @see JavaBreakpoint#setRequestThreadFilter(EventRequest)
*/
@Override
protected void setRequestThreadFilter(EventRequest request,
ThreadReference thread) {
((BreakpointRequest) request).addThreadFilter(thread);
}
/**
* Returns a list of locations of the given line number in the given type.
* Returns <code>null</code> if locations cannot be determined.
*/
protected List<Location> determineLocations(int lineNumber, ReferenceType type,
JDIDebugTarget target) {
List<Location> locations = null;
try {
locations = type.locationsOfLine(JavaDebugUtils.JAVA_STRATUM, null, lineNumber);
} catch (AbsentInformationException aie) {
if (((type.modifiers() & (AccessibleImpl.MODIFIER_ACC_SYNTHETIC | AccessibleImpl.MODIFIER_SYNTHETIC)) != 0)||(type instanceof InterfaceType)) {
return null;
}
IStatus status = new Status(
IStatus.ERROR,
JDIDebugPlugin.getUniqueIdentifier(),
NO_LINE_NUMBERS,
JDIDebugBreakpointMessages.JavaLineBreakpoint_Absent_Line_Number_Information_1,
null);
IStatusHandler handler = DebugPlugin.getDefault().getStatusHandler(
status);
if (handler != null) {
try {
handler.handleStatus(status, type);
} catch (CoreException e) {
}
}
return null;
} catch (NativeMethodException e) {
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 information
target.internalError(e);
return null;
}
return locations;
}
/**
* 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<String, Object> attributes,
String modelIdentifier, boolean enabled, int lineNumber,
int charStart, int charEnd) {
attributes.put(IBreakpoint.ID, modelIdentifier);
attributes.put(IBreakpoint.ENABLED, Boolean.valueOf(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<String, Object> 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);
}
}
/**
* Returns whether this breakpoint has an enabled condition
*/
public boolean hasCondition() {
try {
String condition = getCondition();
return isConditionEnabled() && condition != null
&& (condition.length() > 0);
} catch (CoreException exception) {
JDIDebugPlugin.log(exception);
return false;
}
}
/**
* Suspends the given thread for the given breakpoint event. Returns whether
* the thread suspends.
*/
protected boolean suspendForEvent(Event event, JDIThread thread,
boolean suspendVote) {
expireHitCount(event);
disableTriggerPoint(event);
return suspend(thread, suspendVote);
}
protected IJavaProject getJavaProject(IJavaStackFrame stackFrame) {
synchronized (fProjectsByFrame) {
IJavaProject project = fProjectsByFrame.get(stackFrame);
if (project == null) {
project = JavaDebugUtils.resolveJavaProject(stackFrame);
if (project != null) {
fProjectsByFrame.put(stackFrame, project);
}
}
return project;
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.core.IJavaLineBreakpoint#supportsCondition()
*/
@Override
public boolean supportsCondition() {
return true;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.core.IJavaLineBreakpoint#getCondition()
*/
@Override
public String getCondition() throws CoreException {
return ensureMarker().getAttribute(CONDITION, null);
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jdt.debug.core.IJavaLineBreakpoint#setCondition(java.lang
* .String)
*/
@Override
public void setCondition(String condition) throws CoreException {
// Clear the cached compiled expressions
fCompiledExpressions.clear();
fConditionValues.clear();
fSuspendEvents.clear();
if (condition != null && condition.trim().length() == 0) {
condition = null;
}
setAttributes(new String[] { CONDITION }, new Object[] { condition });
recreate();
}
protected String getMarkerMessage(boolean conditionEnabled,
String condition, int hitCount, int suspendPolicy, int lineNumber) {
StringBuffer message = new StringBuffer(super.getMarkerMessage(
hitCount, suspendPolicy));
if (lineNumber != -1) {
message.append(MessageFormat
.format(JDIDebugBreakpointMessages.JavaLineBreakpoint___line___0___1,
new Object[] { Integer.toString(lineNumber) }));
}
if (conditionEnabled && condition != null) {
message.append(MessageFormat
.format(JDIDebugBreakpointMessages.JavaLineBreakpoint___Condition___0___2,
new Object[] { condition }));
}
return message.toString();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.debug.core.IJavaLineBreakpoint#isConditionEnabled()
*/
@Override
public boolean isConditionEnabled() throws CoreException {
return ensureMarker().getAttribute(CONDITION_ENABLED, false);
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jdt.debug.core.IJavaLineBreakpoint#setConditionEnabled(boolean
* )
*/
@Override
public void setConditionEnabled(boolean conditionEnabled)
throws CoreException {
setAttributes(new String[] { CONDITION_ENABLED },
new Object[] { Boolean.valueOf(conditionEnabled) });
recreate();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.internal.debug.core.breakpoints.JavaBreakpoint#
* cleanupForThreadTermination
* (org.eclipse.jdt.internal.debug.core.model.JDIThread)
*/
@Override
protected void cleanupForThreadTermination(JDIThread thread) {
fSuspendEvents.remove(thread);
fCompiledExpressions.remove(thread);
super.cleanupForThreadTermination(thread);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.internal.debug.core.breakpoints.JavaBreakpoint#
* addInstanceFilter(com.sun.jdi.request.EventRequest,
* com.sun.jdi.ObjectReference)
*/
@Override
protected void addInstanceFilter(EventRequest request,
ObjectReference object) {
if (request instanceof BreakpointRequest) {
((BreakpointRequest) request).addInstanceFilter(object);
}
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jdt.debug.core.IJavaLineBreakpoint#isConditionSuspendOnTrue()
*/
@Override
public boolean isConditionSuspendOnTrue() throws DebugException {
return ensureMarker().getAttribute(CONDITION_SUSPEND_ON_TRUE, true);
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jdt.debug.core.IJavaLineBreakpoint#setConditionSuspendOnTrue
* (boolean)
*/
@Override
public void setConditionSuspendOnTrue(boolean suspendOnTrue)
throws CoreException {
if (isConditionSuspendOnTrue() != suspendOnTrue) {
setAttributes(new String[] { CONDITION_SUSPEND_ON_TRUE },
new Object[] { Boolean.valueOf(suspendOnTrue) });
fConditionValues.clear();
recreate();
}
}
/**
* Returns existing compiled expression for the given thread or
* <code>null</code>.
*
* @param thread
* thread the breakpoint was hit in
* @return compiled expression or <code>null</code>
*/
protected ICompiledExpression getExpression(IJavaThread thread) {
return fCompiledExpressions.get(thread);
}
/**
* Sets the compiled expression for a thread.
*
* @param thread
* thread the breakpoint was hit in
* @param expression
* associated compiled expression
*/
protected void setExpression(IJavaThread thread,
ICompiledExpression expression) {
fCompiledExpressions.put(thread, expression);
}
/**
* Sets the current result value of the conditional expression evaluation
* for this breakpoint in the given target, and returns the previous value
* or <code>null</code> if none
*
* @param target
* debug target
* @param value
* current expression value
* @return previous value or <code>null</code>
*/
protected IValue setCurrentConditionValue(IDebugTarget target, IValue value) {
IValue prev = fConditionValues.get(target);
fConditionValues.put(target, value);
return prev;
}
}