/*
* Copyright (c) 2012, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.eclipse.org/legal/epl-v10.html
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.dart.tools.debug.core.dartium;
import com.google.dart.tools.debug.core.breakpoints.DartBreakpoint;
import com.google.dart.tools.debug.core.webkit.WebkitCallFrame;
import com.google.dart.tools.debug.core.webkit.WebkitDebugger.PausedReasonType;
import com.google.dart.tools.debug.core.webkit.WebkitRemoteObject;
import org.eclipse.debug.core.DebugEvent;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.model.IBreakpoint;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.debug.core.model.IStackFrame;
import org.eclipse.debug.core.model.IThread;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* The IThread implementation for the Dartium debug elements.
*/
public class DartiumDebugThread extends DartiumDebugElement implements IThread {
private static final IBreakpoint[] EMPTY_BREAKPOINTS = new IBreakpoint[0];
private static final IStackFrame[] EMPTY_FRAMES = new IStackFrame[0];
private int expectedSuspendReason = DebugEvent.UNSPECIFIED;
private int expectedResumeReason = DebugEvent.UNSPECIFIED;
private boolean suspended;
private IStackFrame[] suspendedFrames = EMPTY_FRAMES;
private IBreakpoint[] suspendedBreakpoints = EMPTY_BREAKPOINTS;
/**
* @param target
*/
public DartiumDebugThread(IDebugTarget target) {
super(target);
}
@Override
public boolean canResume() {
return !isDisconnected() && isSuspended();
}
@Override
public boolean canStepInto() {
return isSuspended();
}
@Override
public boolean canStepOver() {
return isSuspended();
}
@Override
public boolean canStepReturn() {
// aka stepOut
return isSuspended();
}
@Override
public boolean canSuspend() {
return !isTerminated() && !isSuspended();
}
@Override
public boolean canTerminate() {
return getDebugTarget().canTerminate();
}
@Override
public IBreakpoint[] getBreakpoints() {
return suspendedBreakpoints;
}
/**
* Return a pseudo stack frame for the current isolate. This frame will be able to enumerate all
* the libraries in the current isolate, and each library's top-level variables.
*
* @return a stack frame representing the libraries and top-level variables for the isolate
*/
public IStackFrame getIsolateVarsPseudoFrame() {
return new DartiumDebugIsolateFrame(this);
}
@Override
public String getName() throws DebugException {
// TODO(devoncarew): we need to be able to retrieve the list of isolates from the VM.
return "isolate-0" + (isSuspended() ? " [suspended]" : "");
}
@Override
public int getPriority() throws DebugException {
return 0;
}
@Override
public IStackFrame[] getStackFrames() throws DebugException {
return suspendedFrames;
}
@Override
public IStackFrame getTopStackFrame() throws DebugException {
IStackFrame[] frames = getStackFrames();
return frames.length > 0 ? frames[0] : null;
}
@Override
public boolean hasStackFrames() throws DebugException {
return isSuspended();
}
@Override
public boolean isStepping() {
return expectedResumeReason == DebugEvent.STEP_INTO
|| expectedResumeReason == DebugEvent.STEP_OVER
|| expectedResumeReason == DebugEvent.STEP_RETURN
|| expectedSuspendReason == DebugEvent.STEP_END;
}
@Override
public boolean isSuspended() {
return suspended;
}
@Override
public boolean isTerminated() {
return getDebugTarget().isTerminated();
}
@Override
public void resume() throws DebugException {
try {
expectedResumeReason = DebugEvent.UNSPECIFIED;
getConnection().getDebugger().resume();
} catch (IOException exception) {
throw createDebugException(exception);
}
}
@Override
public void stepInto() throws DebugException {
expectedResumeReason = DebugEvent.STEP_INTO;
expectedSuspendReason = DebugEvent.STEP_END;
try {
getConnection().getDebugger().stepInto();
} catch (IOException exception) {
expectedResumeReason = DebugEvent.UNSPECIFIED;
expectedSuspendReason = DebugEvent.UNSPECIFIED;
throw createDebugException(exception);
}
}
@Override
public void stepOver() throws DebugException {
expectedResumeReason = DebugEvent.STEP_OVER;
expectedSuspendReason = DebugEvent.STEP_END;
try {
getConnection().getDebugger().stepOver();
} catch (IOException exception) {
expectedResumeReason = DebugEvent.UNSPECIFIED;
expectedSuspendReason = DebugEvent.UNSPECIFIED;
throw createDebugException(exception);
}
}
@Override
public void stepReturn() throws DebugException {
expectedResumeReason = DebugEvent.STEP_RETURN;
expectedSuspendReason = DebugEvent.STEP_END;
try {
getConnection().getDebugger().stepOut();
} catch (IOException exception) {
expectedResumeReason = DebugEvent.UNSPECIFIED;
expectedSuspendReason = DebugEvent.UNSPECIFIED;
throw createDebugException(exception);
}
}
/**
* Causes this element to suspend its execution, generating a <code>SUSPEND</code> event. Has no
* effect on an already suspended element. Implementations may be blocking or non-blocking.
*
* @exception DebugException on failure. Reasons include: <br>
* TARGET_REQUEST_FAILED - The request failed in the target <br>
* NOT_SUPPORTED - The capability is not supported by the target
*/
@Override
public void suspend() throws DebugException {
expectedSuspendReason = DebugEvent.CLIENT_REQUEST;
try {
getConnection().getDebugger().pause();
} catch (IOException exception) {
expectedSuspendReason = DebugEvent.UNSPECIFIED;
throw createDebugException(exception);
}
}
@Override
public void terminate() throws DebugException {
getDebugTarget().terminate();
}
protected void handleDebuggerSuspended(PausedReasonType pausedReason,
List<WebkitCallFrame> webkitFrames, WebkitRemoteObject exception) {
int reason = DebugEvent.BREAKPOINT;
if (expectedSuspendReason != DebugEvent.UNSPECIFIED) {
reason = expectedSuspendReason;
expectedSuspendReason = DebugEvent.UNSPECIFIED;
} else {
DartBreakpoint breakpoint = getBreakpointFor(webkitFrames);
if (breakpoint != null) {
suspendedBreakpoints = new IBreakpoint[] {breakpoint};
reason = DebugEvent.BREAKPOINT;
}
}
suspended = true;
suspendedFrames = createFrames(webkitFrames, exception);
fireSuspendEvent(reason);
}
void handleDebuggerResumed() {
// clear data
suspended = false;
suspendedFrames = EMPTY_FRAMES;
suspendedBreakpoints = EMPTY_BREAKPOINTS;
// send event
int reason = expectedResumeReason;
expectedResumeReason = DebugEvent.UNSPECIFIED;
fireResumeEvent(reason);
}
private IStackFrame[] createFrames(List<WebkitCallFrame> webkitFrames,
WebkitRemoteObject exception) {
List<IStackFrame> frames = new ArrayList<IStackFrame>();
for (int i = 0; i < webkitFrames.size(); i++) {
WebkitCallFrame webkitFrame = webkitFrames.get(i);
DartiumDebugStackFrame frame;
if (i == 0 && exception != null) {
frame = new DartiumDebugStackFrame(getTarget(), this, webkitFrame, exception);
} else {
frame = new DartiumDebugStackFrame(getTarget(), this, webkitFrame);
}
frames.add(frame);
}
return frames.toArray(new IStackFrame[frames.size()]);
}
private DartBreakpoint getBreakpointFor(List<WebkitCallFrame> frames) {
if (frames.size() > 0) {
return getBreakpointFor(frames.get(0));
} else {
return null;
}
}
private DartBreakpoint getBreakpointFor(WebkitCallFrame frame) {
DartBreakpointManager breakpointManager = getTarget().getBreakpointManager();
if (breakpointManager != null) {
return breakpointManager.getBreakpointFor(frame.getLocation());
} else {
return null;
}
}
private boolean isDisconnected() {
return getDebugTarget().isDisconnected();
}
}