| /******************************************************************************* |
| * 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; |
| } |
| } |