blob: f40eda828549c7e9fe470753951178756955209e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010 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.ui.eval;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.DebugEvent;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.IDebugEventFilter;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.osgi.util.NLS;
import org.eclipse.ui.PlatformUI;
import org.eclipse.wst.jsdt.core.IFunction;
import org.eclipse.wst.jsdt.core.JavaScriptModelException;
import org.eclipse.wst.jsdt.debug.core.model.IJavaScriptDebugTarget;
import org.eclipse.wst.jsdt.debug.core.model.IJavaScriptStackFrame;
import org.eclipse.wst.jsdt.debug.core.model.IJavaScriptThread;
import org.eclipse.wst.jsdt.debug.internal.ui.JavaScriptDebugUIPlugin;
/**
* Handles stepping into a selected function, for a specific thread.
*
* @since 1.0
*/
public class StepIntoSelectionHandler implements IDebugEventFilter {
/**
* The function to step into
*/
private IFunction function;
/**
* Resolved signature of the function to step into
*/
private String signature;
/**
* The thread in which to step
*/
private IJavaScriptThread thread;
/**
* The initial stack frame
*/
private String origname;
private int origdepth;
/**
* Whether this is the first step into.
*/
private boolean firststep = true;
/**
* Expected event kind
*/
private int eventkind = -1;
/**
* Expected event detail
*/
private int eventdetail = -1;
/**
* Constructs a step handler to step into the given function in the given thread
* starting from the given stack frame.
*
* @param thread
* @param frame
* @param func
*/
public StepIntoSelectionHandler(IJavaScriptThread thread, IJavaScriptStackFrame frame, IFunction func) {
function = func;
this.thread = thread;
try {
origname = frame.getName();
signature = func.getSignature();
} catch (CoreException e) {
JavaScriptDebugUIPlugin.log(e);
}
}
/**
* Returns the target thread for the step.
*
* @return the target thread for the step
*/
protected IJavaScriptThread getThread() {
return thread;
}
protected IJavaScriptDebugTarget getDebugTarget() {
return (IJavaScriptDebugTarget)getThread().getDebugTarget();
}
/**
* Returns the function to step into
*
* @return the function to step into
*/
protected IFunction getMethod() {
return function;
}
/**
* Returns the resolved signature of the function to step into
*
* @return the resolved signature of the function to step into
*/
protected String getSignature() {
return signature;
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.IDebugEventFilter#filterDebugEvents(org.eclipse.debug.core.DebugEvent[])
*/
public DebugEvent[] filterDebugEvents(DebugEvent[] events) {
// we only expect one event from our thread - find the event
DebugEvent event = null;
int index = -1;
int threadEvents = 0;
for (int i = 0; i < events.length; i++) {
DebugEvent e = events[i];
if (isExpectedEvent(e)) {
event = e;
index = i;
threadEvents++;
} else if (e.getSource() == getThread()) {
threadEvents++;
}
}
if (event == null) {
// nothing to process in this event set
return events;
}
// create filtered event set
DebugEvent[] filtered = new DebugEvent[events.length - 1];
if (filtered.length > 0) {
int j = 0;
for (int i = 0; i < events.length; i++) {
if (i != index) {
filtered[j] = events[i];
j++;
}
}
}
// if more than one event in our thread, abort (filtering our event)
if (threadEvents > 1) {
cleanup();
return filtered;
}
// we have the one expected event - process it
switch (event.getKind()) {
case DebugEvent.RESUME:
// next, we expect a step end
setExpectedEvent(DebugEvent.SUSPEND, DebugEvent.STEP_END);
if (firststep) {
firststep = false;
return events; // include the first resume event
}
// secondary step - filter the event
return filtered;
case DebugEvent.SUSPEND:
// compare location to desired location
try {
final IJavaScriptStackFrame frame = (IJavaScriptStackFrame)getThread().getTopStackFrame();
int stackDepth = frame.getThread().getStackFrames().length;
String name = frame.getName();
if (name.equals(getMethod().getElementName())) {
// hit
cleanup();
return events;
}
// step again
Runnable r = null;
if (stackDepth > origdepth) {
r = new Runnable() {
public void run() {
try {
setExpectedEvent(DebugEvent.RESUME, DebugEvent.STEP_RETURN);
frame.stepReturn();
} catch (DebugException e) {
JavaScriptDebugUIPlugin.log(e);
cleanup();
DebugPlugin.getDefault().fireDebugEventSet(new DebugEvent[]{new DebugEvent(getDebugTarget(), DebugEvent.CHANGE)});
}
}
};
} else if (stackDepth == origdepth){
// we should be back in the original stack frame - if not, abort
if (!frame.getName().equals(origname)) {
missed();
return events;
}
r = new Runnable() {
public void run() {
try {
setExpectedEvent(DebugEvent.RESUME, DebugEvent.STEP_INTO);
frame.stepInto();
} catch (DebugException e) {
JavaScriptDebugUIPlugin.log(e);
cleanup();
DebugPlugin.getDefault().fireDebugEventSet(new DebugEvent[]{new DebugEvent(getDebugTarget(), DebugEvent.CHANGE)});
}
}
};
} else {
// we returned from the original frame - never hit the desired method
missed();
return events;
}
DebugPlugin.getDefault().asyncExec(r);
// filter the events
return filtered;
} catch (CoreException e) {
// abort
JavaScriptDebugUIPlugin.log(e);
cleanup();
return events;
}
}
// execution should not reach here
return events;
}
/**
* Called when stepping returned from the original frame without entering the desired method.
*/
protected void missed() {
cleanup();
Runnable r = new Runnable() {
public void run() {
String methodName = null;
try {
methodName = org.eclipse.wst.jsdt.core.Signature.toString(getMethod().getSignature(), getMethod().getElementName(), getMethod().getParameterNames(), false, false);
} catch (JavaScriptModelException e) {
methodName = getMethod().getElementName();
}
new MessageDialog(
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
Messages.step_into_selection,
null,
NLS.bind(Messages.exe_did_not_enter__0__before_returning, new String[]{methodName}),
MessageDialog.INFORMATION, new String[] {IDialogConstants.OK_LABEL}, 0).open();
}
};
JavaScriptDebugUIPlugin.getStandardDisplay().asyncExec(r);
}
/**
* Performs the step.
*/
public void step() {
// add event filter and turn off step filters
DebugPlugin.getDefault().addDebugEventFilter(this);
try {
origdepth = getThread().getStackFrames().length;
setExpectedEvent(DebugEvent.RESUME, DebugEvent.STEP_INTO);
getThread().stepInto();
} catch (DebugException e) {
JavaScriptDebugUIPlugin.log(e);
cleanup();
DebugPlugin.getDefault().fireDebugEventSet(new DebugEvent[]{new DebugEvent(getDebugTarget(), DebugEvent.CHANGE)});
}
}
/**
* Cleans up when the step is complete/aborted.
*/
protected void cleanup() {
DebugPlugin.getDefault().removeDebugEventFilter(this);
}
/**
* Sets the expected debug event kind and detail we are waiting for next.
*
* @param kind event kind
* @param detail event detail
*/
private void setExpectedEvent(int kind, int detail) {
eventkind = kind;
eventdetail = detail;
}
/**
* Returns whether the given event is what we expected.
*
* @param event fire event
* @return whether the event is what we expected
*/
protected boolean isExpectedEvent(DebugEvent event) {
return event.getSource().equals(getThread()) &&
event.getKind() == eventkind &&
event.getDetail() == eventdetail;
}
}