blob: c9b3fb963be4fb7c2ed5a722d4ba2948d8e532c2 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2011 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
*******************************************************************************/
package org.eclipse.wst.jsdt.debug.internal.core.breakpoints;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.URIUtil;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.model.Breakpoint;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.wst.jsdt.debug.core.breakpoints.IJavaScriptBreakpoint;
import org.eclipse.wst.jsdt.debug.core.jsdi.ScriptReference;
import org.eclipse.wst.jsdt.debug.core.jsdi.event.BreakpointEvent;
import org.eclipse.wst.jsdt.debug.core.jsdi.event.Event;
import org.eclipse.wst.jsdt.debug.core.jsdi.event.EventSet;
import org.eclipse.wst.jsdt.debug.core.jsdi.event.ScriptLoadEvent;
import org.eclipse.wst.jsdt.debug.core.jsdi.request.BreakpointRequest;
import org.eclipse.wst.jsdt.debug.core.jsdi.request.EventRequest;
import org.eclipse.wst.jsdt.debug.core.jsdi.request.ScriptLoadRequest;
import org.eclipse.wst.jsdt.debug.core.model.JavaScriptDebugModel;
import org.eclipse.wst.jsdt.debug.internal.core.Constants;
import org.eclipse.wst.jsdt.debug.internal.core.JavaScriptDebugPlugin;
import org.eclipse.wst.jsdt.debug.internal.core.launching.SourceLookup;
import org.eclipse.wst.jsdt.debug.internal.core.model.IJavaScriptEventListener;
import org.eclipse.wst.jsdt.debug.internal.core.model.JavaScriptDebugTarget;
import org.eclipse.wst.jsdt.debug.internal.core.model.JavaScriptThread;
import org.eclipse.wst.jsdt.debug.internal.core.model.Script;
/**
* Abstract representation of a JSDI breakpoint
*
* @since 1.0
*/
public abstract class JavaScriptBreakpoint extends Breakpoint implements IJavaScriptBreakpoint, IJavaScriptEventListener {
/**
* The total count of all of the targets this breakpoint is installed in
*/
public static final String INSTALL_COUNT = JavaScriptDebugPlugin.PLUGIN_ID + ".install_count"; //$NON-NLS-1$
private HashSet targets = null;
private HashMap requestspertarget = new HashMap(4);
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.Breakpoint#setMarker(org.eclipse.core.resources.IMarker)
*/
public void setMarker(IMarker marker) throws CoreException {
super.setMarker(marker);
// reset the install count
setAttribute(INSTALL_COUNT, 0);
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.model.Breakpoint#setAttribute(java.lang.String, boolean)
*/
protected void setAttribute(final String attributeName, final boolean value) throws CoreException {
IWorkspace workspace = ResourcesPlugin.getWorkspace();
IWorkspaceRunnable runnable= new IWorkspaceRunnable() {
public void run(IProgressMonitor monitor) throws CoreException {
ensureMarker().setAttribute(attributeName, value);
}
};
workspace.run(runnable, getMarkerRule(), IWorkspace.AVOID_UPDATE, null);
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.model.Breakpoint#setAttribute(java.lang.String, java.lang.Object)
*/
protected void setAttribute(final String attributeName, final Object value) throws CoreException {
IWorkspace workspace = ResourcesPlugin.getWorkspace();
IWorkspaceRunnable runnable= new IWorkspaceRunnable() {
public void run(IProgressMonitor monitor) throws CoreException {
ensureMarker().setAttribute(attributeName, value);
}
};
workspace.run(runnable, getMarkerRule(), IWorkspace.AVOID_UPDATE, null);
}
/**
* Add this breakpoint to the breakpoint manager, or sets it as unregistered.
*/
protected void register(boolean register) throws CoreException {
DebugPlugin plugin = DebugPlugin.getDefault();
if (plugin != null && register) {
plugin.getBreakpointManager().addBreakpoint(this);
} else {
setRegistered(false);
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.IBreakpoint#getModelIdentifier()
*/
public String getModelIdentifier() {
return JavaScriptDebugModel.MODEL_ID;
}
/**
* Returns whether this breakpoint should be "skipped". Breakpoints are skipped if the breakpoint manager is disabled and the breakpoint is registered with the manager
*
* @return whether this breakpoint should be skipped
*/
public boolean shouldSkipBreakpoint() throws CoreException {
DebugPlugin plugin = DebugPlugin.getDefault();
return plugin != null && isRegistered() && !plugin.getBreakpointManager().isEnabled();
}
/**
* Adds the given debug target to the listing of targets that this breakpoint cares about
*
* @param target
* @throws CoreException
*/
public void addToTarget(JavaScriptDebugTarget target) throws CoreException {
if (target.isTerminated() || shouldSkipBreakpoint()) {
return;
}
// Add script load handler
ScriptLoadRequest request = target.getEventRequestManager().createScriptLoadRequest();
request.setEnabled(isEnabled());
registerRequest(target, request);
// Add to all loaded scripts
String scriptPath = getScriptPath();
if (scriptPath == null) {
return;
}
try {
if(JavaScriptDebugPlugin.isExternalSource(new Path(scriptPath))) {
scriptPath = getMarker().getResource().getPersistentProperty(SourceLookup.SCRIPT_URL);
}
if(scriptPath == null) {
return;
}
List/* ScriptReference */scripts = target.underlyingScripts(Script.resolveName(URIUtil.fromString(scriptPath)));
boolean success = true;
for (Iterator iter = scripts.iterator(); iter.hasNext();) {
ScriptReference script = (ScriptReference) iter.next();
if (JavaScriptDebugPlugin.getResolutionManager().matches(script, new Path(getScriptPath()))) {
success &= createRequest(target, script);
}
}
if (success) {
if (this.targets == null) {
this.targets = new HashSet();
}
this.targets.add(target);
}
}
catch(URISyntaxException urise) {
JavaScriptDebugPlugin.log(urise);
}
}
/**
* Creates a request for the given script in the given target
*
* @param target
* the target to register with
* @param script
* the script we want to set the breakpoint in
* @return true if a new request was created false otherwise
* @throws CoreException
*/
protected abstract boolean createRequest(JavaScriptDebugTarget target, ScriptReference script) throws CoreException;
/**
* Configures the request with attributes from the breakpoint
*
* @param request
* @throws CoreException
*/
protected void configureRequest(BreakpointRequest request) throws CoreException {
request.addHitCountFilter(getHitCount());
}
/**
* Removes the given debug target from the listing of targets this breakpoint cares about
*
* @param target
*/
public void removeFromTarget(JavaScriptDebugTarget target) {
List requests = getRequests(target);
if (requests != null) {
for (Iterator iter = requests.iterator(); iter.hasNext();) {
EventRequest request = null;
try {
request = (EventRequest) iter.next();
if (target.isAvailable()) {
target.getEventRequestManager().deleteEventRequest(request);
}
} finally {
deregisterRequest(target, request);
}
}
}
synchronized (this.requestspertarget) {
this.requestspertarget.remove(target);
}
if (this.targets == null) {
return;
}
this.targets.remove(target);
}
/**
* Recreate this breakpoint in all targets
*
* @throws CoreException
*/
protected void recreateBreakpoint() throws CoreException {
DebugPlugin plugin = DebugPlugin.getDefault();
if (plugin != null) {
IDebugTarget[] targets = plugin.getLaunchManager().getDebugTargets();
for (int i = 0; i < targets.length; i++) {
if (targets[i].getModelIdentifier().equals(JavaScriptDebugModel.MODEL_ID)) {
recreateBreakpointFor((JavaScriptDebugTarget) targets[i]);
}
}
}
}
/**
* Recreates this breakpoint in the given debug target
*
* @param target
* @throws CoreException
*/
void recreateBreakpointFor(JavaScriptDebugTarget target) throws CoreException {
if (target.isAvailable() && target.getBreakpoints().contains(this)) {
removeFromTarget(target);
addToTarget(target);
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.Breakpoint#setEnabled(boolean)
*/
public void setEnabled(boolean enabled) throws CoreException {
if(isEnabled() != enabled) {
setAttribute(ENABLED, enabled);
recreateBreakpoint();
}
}
/* (non-Javadoc)
* @see org.eclipse.wst.jsdt.debug.core.breakpoints.IJavaScriptBreakpoint#setSuspendPolicy(int)
*/
public void setSuspendPolicy(int policy) throws CoreException {
if(getSuspendPolicy() != policy) {
setAttribute(SUSPEND_POLICY, policy);
recreateBreakpoint();
}
}
/* (non-Javadoc)
* @see org.eclipse.wst.jsdt.debug.core.breakpoints.IJavaScriptBreakpoint#getSuspendPolicy()
*/
public int getSuspendPolicy() throws CoreException {
return ensureMarker().getAttribute(SUSPEND_POLICY, SUSPEND_THREAD);
}
/**
* Returns if this breakpoint is currently installed in any targets
*
* @return true if this breakpoint is installed in any targets, false otherwise
* @throws CoreException
*/
public boolean isInstalled() throws CoreException {
return ensureMarker().getAttribute(INSTALL_COUNT, 0) > 0;
}
/**
* Increments the install count of this breakpoint
*/
protected void incrementInstallCount() throws CoreException {
int count = getInstallCount();
setAttribute(INSTALL_COUNT, count + 1);
}
/**
* Returns the <code>INSTALL_COUNT</code> attribute of this breakpoint or 0 if the attribute is not set.
*/
public int getInstallCount() throws CoreException {
return ensureMarker().getAttribute(INSTALL_COUNT, 0);
}
/**
* Decrements the install count of this breakpoint.
*/
protected void decrementInstallCount() throws CoreException {
int count = getInstallCount();
if (count > 0) {
setAttribute(INSTALL_COUNT, count - 1);
}
}
/* (non-Javadoc)
* @see org.eclipse.wst.jsdt.debug.core.breakpoints.IJavaScriptBreakpoint#setHitCount(int)
*/
public void setHitCount(int count) throws CoreException, IllegalArgumentException {
if (count != getHitCount()) {
setAttribute(HIT_COUNT, count);
recreateBreakpoint();
}
}
/* (non-Javadoc)
* @see org.eclipse.wst.jsdt.debug.core.breakpoints.IJavaScriptBreakpoint#getHitCount()
*/
public int getHitCount() throws CoreException {
return ensureMarker().getAttribute(HIT_COUNT, -1);
}
/* (non-Javadoc)
* @see org.eclipse.wst.jsdt.debug.core.breakpoints.IJavaScriptBreakpoint#getScriptPath()
*/
public String getScriptPath() throws CoreException {
return ensureMarker().getAttribute(SCRIPT_PATH, Constants.EMPTY_STRING);
}
/* (non-Javadoc)
* @see org.eclipse.wst.jsdt.debug.core.breakpoints.IJavaScriptBreakpoint#getTypeName()
*/
public String getTypeName() throws CoreException {
return ensureMarker().getAttribute(TYPE_NAME, null);
}
/**
* Registers the given request for this breakpoint for the given target
*
* @param target
* @param request
*/
protected void registerRequest(JavaScriptDebugTarget target, EventRequest request) {
addRequestForTarget(target, request);
if (!(request instanceof ScriptLoadRequest)) {
try {
incrementInstallCount();
} catch (CoreException ce) {
JavaScriptDebugPlugin.log(ce);
}
}
}
/**
* Adds the given request to the list of requests for the given target
* @param target
* @param request
*/
protected synchronized void addRequestForTarget(JavaScriptDebugTarget target, EventRequest request) {
ArrayList requests = getRequests(target);
if (requests == null) {
synchronized (this.requestspertarget) {
requests = new ArrayList(2);
this.requestspertarget.put(target, requests);
}
}
requests.add(request);
target.addJSDIEventListener(this, request);
}
/**
* Returns any existing requests associated with the given target
*
* @param target
* @return list of requests for the given target
*/
protected ArrayList getRequests(JavaScriptDebugTarget target) {
ArrayList list = null;
synchronized (this.requestspertarget) {
list = (ArrayList) this.requestspertarget.get(target);
}
return list;
}
/**
* Remove this breakpoint as an event listener
*
* @param target
* @param request
*/
protected void deregisterRequest(JavaScriptDebugTarget target, EventRequest request) {
target.removeJSDIEventListener(this, request);
if (!(request instanceof ScriptLoadRequest)) {
try {
decrementInstallCount();
} catch (CoreException ce) {
JavaScriptDebugPlugin.log(ce);
}
}
}
/* (non-Javadoc)
* @see org.eclipse.wst.jsdt.debug.internal.core.model.IJavaScriptEventListener#handleEvent(org.eclipse.wst.jsdt.debug.core.jsdi.event.Event, org.eclipse.wst.jsdt.debug.internal.core.model.JavaScriptDebugTarget, boolean, org.eclipse.wst.jsdt.debug.core.jsdi.event.EventSet)
*/
public boolean handleEvent(Event event, JavaScriptDebugTarget target, boolean suspendVote, EventSet eventSet) {
// get the thread and suspend it
if (event instanceof BreakpointEvent) {
JavaScriptThread thread = target.findThread(((BreakpointEvent) event).thread());
if (thread != null) {
return !thread.suspendForBreakpoint(this, suspendVote);
}
}
if (event instanceof ScriptLoadEvent) {
ScriptLoadEvent sevent = (ScriptLoadEvent) event;
ScriptReference script = sevent.script();
try {
if (JavaScriptDebugPlugin.getResolutionManager().matches(script, new Path(getScriptPath()))) {
createRequest(target, script);
}
} catch (CoreException ce) {
JavaScriptDebugPlugin.log(ce);
}
}
return true;
}
/**
* Returns if the type names for the breakpoint are equal or not. Two <code>null</code> type names are considered to be equal.
*
* @param tname1
* @param tname2
* @return true if the type names are equal, false otherwise
*/
boolean typeNamesEqual(String tname1, String tname2) {
if (tname1 == null && tname2 == null) {
return true;
}
return tname1 != null && tname1.equals(tname2);
}
/**
* Custom comparison to avoid the leading separator issue from saying the paths are not equal
*
* @param p1
* @param p2
* @return
*/
boolean pathsEqual(IPath p1, IPath p2) {
if (p1.segmentCount() == p2.segmentCount()) {
String[] segments = p1.segments();
for (int i = 0; i < segments.length; i++) {
if (!segments[i].equals(p2.segment(i))) {
return false;
}
}
}
return true;
}
/* (non-Javadoc)
* @see org.eclipse.wst.jsdt.debug.internal.core.model.IJavaScriptEventListener#eventSetComplete(org.eclipse.wst.jsdt.debug.core.jsdi.event.Event, org.eclipse.wst.jsdt.debug.internal.core.model.JavaScriptDebugTarget, boolean, org.eclipse.wst.jsdt.debug.core.jsdi.event.EventSet)
*/
public void eventSetComplete(Event event, JavaScriptDebugTarget target, boolean suspend, EventSet eventSet) {
if (event instanceof BreakpointEvent) {
JavaScriptThread thread = target.findThread(((BreakpointEvent) event).thread());
if (thread != null) {
thread.suspendForBreakpointComplete(this, suspend, eventSet);
}
}
}
/* (non-Javadoc)
* @see org.eclipse.wst.jsdt.debug.core.breakpoints.IJavaScriptBreakpoint#setJavaScriptElementHandle(java.lang.String)
*/
public void setJavaScriptElementHandle(String handle) throws CoreException {
ensureMarker().setAttribute(ELEMENT_HANDLE, handle);
}
/* (non-Javadoc)
* @see org.eclipse.wst.jsdt.debug.core.breakpoints.IJavaScriptBreakpoint#getJavaScriptElementHandle()
*/
public String getJavaScriptElementHandle() throws CoreException {
return ensureMarker().getAttribute(ELEMENT_HANDLE, null);
}
}