blob: 2e37c7f65dbc655d2e25aa504375726247317f6f [file] [log] [blame]
package org.eclipse.jdt.internal.debug.core.breakpoints;
/*
* (c) Copyright IBM Corp. 2000, 2001.
* All Rights Reserved.
*/
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.StringTokenizer;
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.DebugPlugin;
import org.eclipse.debug.core.model.IBreakpoint;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.jdt.debug.core.IJavaExceptionBreakpoint;
import org.eclipse.jdt.internal.debug.core.JDIDebugPlugin;
import org.eclipse.jdt.internal.debug.core.StringMatcher;
import org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget;
import com.sun.jdi.Location;
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.ExceptionEvent;
import com.sun.jdi.request.ClassPrepareRequest;
import com.sun.jdi.request.EventRequest;
import com.sun.jdi.request.ExceptionRequest;
public class JavaExceptionBreakpoint extends JavaBreakpoint implements IJavaExceptionBreakpoint {
private static final String JAVA_EXCEPTION_BREAKPOINT= "org.eclipse.jdt.debug.javaExceptionBreakpointMarker"; //$NON-NLS-1$
/**
* Exception breakpoint attribute storing the suspend on caught value
* (value <code>"org.eclipse.jdt.debug.core.caught"</code>). This attribute is stored as a <code>boolean</code>.
* When this attribute is <code>true</code>, a caught exception of the associated
* type will cause excecution to suspend .
*/
protected static final String CAUGHT = "org.eclipse.jdt.debug.core.caught"; //$NON-NLS-1$
/**
* Exception breakpoint attribute storing the suspend on uncaught value
* (value <code>"org.eclipse.jdt.debug.core.uncaught"</code>). This attribute is stored as a
* <code>boolean</code>. When this attribute is <code>true</code>, an uncaught
* exception of the associated type will cause excecution to suspend.
*/
protected static final String UNCAUGHT = "org.eclipse.jdt.debug.core.uncaught"; //$NON-NLS-1$
/**
* Exception breakpoint attribute storing the checked value (value <code>"org.eclipse.jdt.debug.core.checked"</code>).
* This attribute is stored as a <code>boolean</code>, indicating whether an
* exception is a checked exception.
*/
protected static final String CHECKED = "org.eclipse.jdt.debug.core.checked"; //$NON-NLS-1$
/**
* Exception breakpoint attribute storing the inclusive value (value <code>"org.eclipse.jdt.debug.core.inclusive_filters"</code>).
* This attribute is stored as a <code>boolean</code>, indicating whether class
* filters are inclusive or exclusive.
*/
protected static final String INCLUSIVE_FILTERS = "org.eclipse.jdt.debug.core.inclusive_filters"; //$NON-NLS-1$
/**
* Exception breakpoint attribute storing the String value (value <code>"org.eclipse.jdt.debug.core.filters"</code>).
* This attribute is stored as a <code>String</code>, a comma delimited list
* of class filters. The filters are applied as inclusion or exclusion depending on
* INCLUSIVE_FILTERS.
*/
protected static final String FILTERS = "org.eclipse.jdt.debug.core.filters"; //$NON-NLS-1$
/**
* Name of the exception that was actually hit (could be a
* subtype of the type that is being caught).
*/
protected String fExceptionName = null;
/**
* The current set of class filters.
*/
protected String[] fClassFilters= null;
/**
* The current common pattern generated from the set of filters
* May be null.
*/
protected String fCommonPattern;
public JavaExceptionBreakpoint() {
}
/**
* Creates and returns an exception breakpoint for the
* given (throwable) type. Caught and uncaught specify where the exception
* should cause thread suspensions - that is, in caught and/or uncaught locations.
* Checked indicates if the given exception is a checked exception.
* @param resource the resource on which to create the associated
* breakpoint marker
* @param exceptionName the fully qualified name of the exception for
* which to create the breakpoint
* @param caught whether to suspend in caught locations
* @param uncaught whether to suspend in uncaught locations
* @param checked whether the exception is a checked exception
* @param add whether to add this breakpoint to the breakpoint manager
* @return a Java exception breakpoint
* @exception DebugException if unable to create the associated marker due
* to a lower level exception.
*/
public JavaExceptionBreakpoint(final IResource resource, final String exceptionName, final boolean caught, final boolean uncaught, final boolean checked, final boolean add, final Map attributes) throws DebugException {
IWorkspaceRunnable wr= new IWorkspaceRunnable() {
public void run(IProgressMonitor monitor) throws CoreException {
// create the marker
setMarker(resource.createMarker(JAVA_EXCEPTION_BREAKPOINT));
// add attributes
attributes.put(IBreakpoint.ID, getModelIdentifier());
attributes.put(TYPE_NAME, exceptionName);
attributes.put(ENABLED, Boolean.TRUE);
attributes.put(CAUGHT, new Boolean(caught));
attributes.put(UNCAUGHT, new Boolean(uncaught));
attributes.put(CHECKED, new Boolean(checked));
ensureMarker().setAttributes(attributes);
register(add);
}
};
run(wr);
}
/**
* Creates a request in the given target to suspend when the given exception
* type is thrown. The request is returned installed, configured, and enabled
* as appropriate for this breakpoint.
*/
protected EventRequest newRequest(JDIDebugTarget target, ReferenceType type) throws CoreException {
if (!isCaught() && !isUncaught()) {
return null;
}
ExceptionRequest request= null;
try {
request= target.getEventRequestManager().createExceptionRequest(type, isCaught(), isUncaught());
configureRequest(request, target);
} catch (VMDisconnectedException e) {
if (target.isAvailable()) {
JDIDebugPlugin.log(e);
}
return null;
} catch (RuntimeException e) {
JDIDebugPlugin.log(e);
return null;
}
return request;
}
/**
* Enable this exception breakpoint.
*
* If the exception breakpoint is not catching caught or uncaught,
* turn both modes on. If this isn't done, the resulting
* state (enabled with caught and uncaught both disabled)
* is ambiguous.
*/
public void setEnabled(boolean enabled) throws CoreException {
super.setEnabled(enabled);
if (isEnabled()) {
if (!(isCaught() || isUncaught())) {
setCaughtAndUncaught(true, true);
}
}
}
/**
* Sets the values for whether this breakpoint will
* suspend execution when the associated exception is thrown
* and caught or not caught.
*/
protected void setCaughtAndUncaught(boolean caught, boolean uncaught) throws CoreException {
Object[] values= new Object[]{new Boolean(caught), new Boolean(uncaught)};
String[] attributes= new String[]{CAUGHT, UNCAUGHT};
setAttributes(attributes, values);
}
/**
* @see IJavaExceptionBreakpoint#isCaught()
*/
public boolean isCaught() throws CoreException {
return ensureMarker().getAttribute(CAUGHT, false);
}
/**
* @see IJavaExceptionBreakpoint#setCaught(boolean)
*/
public void setCaught(boolean caught) throws CoreException {
if (caught == isCaught()) {
return;
}
setAttribute(CAUGHT, caught);
if (caught && !isEnabled()) {
setEnabled(true);
} else if (!(caught || isUncaught())) {
setEnabled(false);
}
}
/**
* @see IJavaExceptionBreakpoint#isUncaught()
*/
public boolean isUncaught() throws CoreException {
return ensureMarker().getAttribute(UNCAUGHT, false);
}
/**
* @see IJavaExceptionBreakpoint#setUncaught(boolean)
*/
public void setUncaught(boolean uncaught) throws CoreException {
if (uncaught == isUncaught()) {
return;
}
setAttribute(UNCAUGHT, uncaught);
if (uncaught && !isEnabled()) {
setEnabled(true);
} else if (!(uncaught || isCaught())) {
setEnabled(false);
}
}
/**
* @see IJavaExceptionBreakpoint#isChecked()
*/
public boolean isChecked() throws CoreException {
return ensureMarker().getAttribute(CHECKED, false);
}
/**
* @see JavaBreakpoint#updateRequest(EventRequest, JDIDebugTarget)
*/
protected EventRequest updateRequest(EventRequest request, JDIDebugTarget target) throws CoreException {
updateEnabledState(request);
EventRequest newRequest = updateHitCount(request, target);
if (newRequest == request) {
newRequest= updateCaughtState(newRequest, target);
} else {
replaceRequest(target, request, newRequest);
return newRequest;
}
return request;
}
/**
* Return a request that will suspend execution when a caught and/or uncaught
* exception is thrown as is appropriate for the current state of this breakpoint.
*/
protected EventRequest updateCaughtState(EventRequest req, JDIDebugTarget target) throws CoreException {
if(!(req instanceof ExceptionRequest)) {
return req;
}
ExceptionRequest request= (ExceptionRequest) req;
if (request.notifyCaught() != isCaught() || request.notifyUncaught() != isUncaught()) {
request= (ExceptionRequest)recreateRequest(request, target);
}
return request;
}
/**
* @see JavaBreakpoint#recreateRequest(EventRequest, JDIDebugTarget)
*/
protected EventRequest recreateRequest(EventRequest request, JDIDebugTarget target) throws CoreException{
try {
ReferenceType exClass = ((ExceptionRequest)request).exception();
request = newRequest(target, exClass);
} catch (VMDisconnectedException e) {
if (!target.isAvailable()) {
return request;
}
JDIDebugPlugin.log(e);
} catch (RuntimeException e) {
JDIDebugPlugin.log(e);
}
return request;
}
/**
* @see JavaBreakpoint#setRequestThreadFilter(EventRequest)
*/
protected void setRequestThreadFilter(EventRequest request, ThreadReference thread) {
((ExceptionRequest)request).addThreadFilter(thread);
}
/**
* @see IJDIEventListener#handleEvent(Event, JDIDebugTarget)
*/
public boolean handleEvent(Event event, JDIDebugTarget target) {
if (event instanceof ExceptionEvent) {
setExceptionName(((ExceptionEvent)event).exception().type().name());
if (getClassFilters().length > 1 || classFiltersIncludeDefaultPackage()) {
if (isExceptionEventExcluded((ExceptionEvent)event)) {
return true;
}
}
}
return super.handleEvent(event, target);
}
protected boolean classFiltersIncludeDefaultPackage() {
for (int i = 0; i < fClassFilters.length; i++) {
if (fClassFilters[i].length() == 0) {
return true;
}
}
return false;
}
/**
* Returns whether this is an event that has
* has been set to be filtered (ie not hit).
*/
protected boolean isExceptionEventExcluded(ExceptionEvent event) {
Location location= event.location();
String fullyQualifiedName= location.declaringType().name();
boolean defaultPackage= fullyQualifiedName.indexOf('.') == -1;
String[] filters= getClassFilters();
try {
boolean inclusive= isInclusiveFiltered();
if (defaultPackage) {
for (int i= 0; i < filters.length; i++) {
if (filters[i].length() == 0){
return !inclusive;
}
}
} else {
for (int i= 0; i < filters.length; i++) {
if (filters[i].length() == 0) {
continue;
}
StringMatcher matcher= new StringMatcher(filters[i], false, false);
if (matcher.match(fullyQualifiedName)) {
return !inclusive;
}
}
}
} catch (CoreException ce) {
JDIDebugPlugin.log(ce);
}
return true;
}
/**
* Sets the name of the exception that was last hit
*
* @param name fully qualified exception name
*/
protected void setExceptionName(String name) {
fExceptionName = name;
}
/**
* @see IJavaExceptionBreakpoint#getExceptionTypeName()
*/
public String getExceptionTypeName() {
return fExceptionName;
}
/**
* @see IJavaExceptionBreakpoint#getFilters()
*/
public String[] getFilters() throws CoreException {
return getClassFilters();
}
/**
* @see IJavaExceptionBreakpoint#setFilters(String[], boolean)
*/
public void setFilters(String[] filters, boolean inclusive) throws CoreException {
String serializedFilters= serializeList(filters);
if (inclusive == ensureMarker().getAttribute(INCLUSIVE_FILTERS, false)) {
if (serializedFilters.equals(ensureMarker().getAttribute(FILTERS, ""))) { //$NON-NLS-1$
//no change
return;
}
}
setClassFilters(filters);
fCommonPattern= null;
if (inclusive) {
setAttribute(INCLUSIVE_FILTERS, true);
} else {
//exclusion
setAttribute(INCLUSIVE_FILTERS, false);
}
setAttribute(FILTERS, serializedFilters);
IDebugTarget[] targets= DebugPlugin.getDefault().getLaunchManager().getDebugTargets();
for (int i = 0; i < targets.length; i++) {
IDebugTarget t = targets[i];
if (!(t instanceof JDIDebugTarget)) {
continue;
}
JDIDebugTarget target= (JDIDebugTarget)t;
List requests= getRequests(target);
ListIterator iter= requests.listIterator();
EventRequest request= null;
while (iter.hasNext()) {
request= (EventRequest)iter.next();
if (request instanceof ClassPrepareRequest) {
continue;
}
EventRequest newRequest = recreateRequest(request, target);
if (newRequest != request) {
replaceRequest(target, request, newRequest);
iter.set(newRequest);
}
}
}
}
/**
* Adds the filtering to the exception request
*/
protected void configureRequest(EventRequest eRequest, JDIDebugTarget target) throws CoreException {
String[] filters= getClassFilters();
if (filters.length > 0 ) {
boolean inclusive= ensureMarker().getAttribute(INCLUSIVE_FILTERS, true);
String pattern= ""; //$NON-NLS-1$
//emulated for more than one class filter
//as class filters must all be satisfied for the event to occur
if (filters.length == 1) {
pattern= filters[0];
} else {
pattern= getGreatestCommonPatternFilter();
}
if (pattern.length() > 0) {
ExceptionRequest request= (ExceptionRequest)eRequest;
if (inclusive) {
request.addClassFilter(pattern);
} else {
request.addClassExclusionFilter(pattern);
}
}
}
super.configureRequest(eRequest, target);
}
/**
* Returns the longest common pattern from the class filters specified
* for this breakpoint.
*/
protected String getGreatestCommonPatternFilter() {
if (fCommonPattern != null) {
return fCommonPattern;
}
String[] filters= null;
StringBuffer buff= new StringBuffer();
try {
filters= parseList(ensureMarker().getAttribute(FILTERS, "")); //$NON-NLS-1$
} catch(DebugException e) {
return buff.toString();
}
int max=0;
int longest= 0;
for (int i= 0;i < filters.length; i++) {
String filter = filters[i];
int length= filter.length();
if (length > max) {
max= length;
longest= i;
}
}
int index= 0;
String longestFilter= filters[longest];
filters[longest]= null;
char current= longestFilter.charAt(index);
boolean common= true;
if (current != '*') {
while (common) {
for (int i = 0; i < filters.length; i++) {
String filter = filters[i];
if (filter == null) {
//filter ended with a '*'
continue;
}
if (filter.length() <= index) {
common= false;
break;
}
char other= filter.charAt(index);
if (other == '*') {
if (index == 0) {
common= false;
break;
} else if (index == filter.length() - 1) {
filters[i] = null;
}
} else if (other != current) {
common= false;
break;
}
}
if (common) {
buff.append(current);
index++;
if (index == max) {
break;
}
current= longestFilter.charAt(index);
}
}
}
if (buff.length() > 0) {
if (buff.charAt(buff.length() - 1) != '*') {
buff.append('*');
}
}
fCommonPattern= buff.toString();
return fCommonPattern;
}
/**
* Serializes the array of strings into one comma
* separated string.
*/
protected String serializeList(String[] list) {
if (list == null) {
return ""; //$NON-NLS-1$
}
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < list.length; i++) {
if (i > 0) {
buffer.append(',');
}
buffer.append(list[i]);
}
return buffer.toString();
}
/**
* Parses the comma separated String into an array of Strings
*/
protected String[] parseList(String listString) {
List list = new ArrayList(10);
StringTokenizer tokenizer = new StringTokenizer(listString, ","); //$NON-NLS-1$
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
list.add(token);
}
return (String[])list.toArray(new String[list.size()]);
}
/**
* @see IJavaExceptionBreakpoint#isInclusiveFiltered()
*/
public boolean isInclusiveFiltered() throws CoreException {
return ensureMarker().getAttribute(INCLUSIVE_FILTERS, true);
}
protected String[] getClassFilters() {
if (fClassFilters == null) {
try {
fClassFilters= parseList(ensureMarker().getAttribute(FILTERS, "")); //$NON-NLS-1$
} catch (CoreException ce) {
fClassFilters= new String[]{};
}
}
return fClassFilters;
}
protected void setClassFilters(String[] filters) {
fClassFilters = filters;
}
/**
* @see JavaBreakpoint#installableReferenceType(ReferenceType, JDIDebugTarget)
*/
protected boolean installableReferenceType(ReferenceType type, JDIDebugTarget target) throws CoreException {
String installableType= getTypeName();
String queriedType= type.name();
if (installableType == null || queriedType == null) {
return false;
}
if (installableType.equals(queriedType)) {
return queryInstallListeners(target, type);
}
return false;
}
}