// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.sdk.internal.v8native;
import org.chromium.sdk.Breakpoint;
import org.chromium.sdk.BreakpointTypeExtension;
import org.chromium.sdk.IgnoreCountBreakpointExtension;
import org.chromium.sdk.JavascriptVm;
import org.chromium.sdk.RelayOk;
import org.chromium.sdk.SyncCallback;
import org.chromium.sdk.internal.ScriptRegExpBreakpointTarget;
import org.chromium.sdk.internal.v8native.protocol.input.SuccessCommandResponse;
import org.chromium.sdk.internal.v8native.protocol.input.data.BreakpointInfo;
import org.chromium.sdk.internal.v8native.protocol.output.ChangeBreakpointMessage;
import org.chromium.sdk.util.GenericCallback;
import org.chromium.sdk.util.RelaySyncCallback;
/**
* A generic implementation of the Breakpoint interface.
*/
public class BreakpointImpl implements Breakpoint {
/**
* The breakpoint target.
*/
private Target target;
/**
* The breakpoint id as reported by the JavaScript VM.
*/
private long id;
/**
* Breakpoint line number. May become invalidated by LiveEdit actions.
*/
private long lineNumber;
/**
* Whether the breakpoint is enabled.
*/
private boolean isEnabled;
/**
* The breakpoint condition (plain JavaScript) that should be {@code true}
* for the breakpoint to fire.
*/
private String condition;
/**
* The breakpoint manager that manages this breakpoint.
*/
private final BreakpointManager breakpointManager;
/**
* Whether the breakpoint data have changed with respect
* to the JavaScript VM data.
*/
private volatile boolean isDirty = false;
public BreakpointImpl(long id, Target target, long lineNumber,
boolean enabled, String condition, BreakpointManager breakpointManager) {
this.target = target;
this.id = id;
this.isEnabled = enabled;
this.condition = condition;
this.lineNumber = lineNumber;
this.breakpointManager = breakpointManager;
}
public BreakpointImpl(BreakpointInfo info, BreakpointManager breakpointManager) {
this.target = getType(info);
this.id = info.number();
this.breakpointManager = breakpointManager;
updateFromRemote(info);
}
public void updateFromRemote(BreakpointInfo info) {
if (this.id != info.number()) {
throw new IllegalArgumentException();
}
this.lineNumber = info.line();
this.isEnabled = info.active();
this.condition = info.condition();
}
@Override
public boolean isEnabled() {
return isEnabled;
}
@Override
public Target getTarget() {
return target;
}
@Override
public long getId() {
return id;
}
@Override
public String getCondition() {
return condition;
}
@Override
public long getLineNumber() {
return lineNumber;
}
@Override
public void setEnabled(boolean enabled) {
if (this.isEnabled != enabled) {
setDirty(true);
}
this.isEnabled = enabled;
}
private RelayOk setIgnoreCount(int ignoreCount,
final GenericCallback<Void> callback, SyncCallback syncCallback) {
ChangeBreakpointMessage message = new ChangeBreakpointMessage(id, ignoreCount);
V8CommandCallbackBase wrappedCallback;
if (callback == null) {
wrappedCallback = null;
} else {
wrappedCallback = new V8CommandCallbackBase() {
@Override
public void success(SuccessCommandResponse successResponse) {
callback.success(null);
}
@Override
public void failure(String message) {
callback.failure(new Exception(message));
}
};
}
DebugSession debugSession = breakpointManager.getDebugSession();
return debugSession.sendMessageAsync(message, true, wrappedCallback, syncCallback);
}
@Override
public void setCondition(String condition) {
if (!eq(this.condition, condition)) {
setDirty(true);
}
this.condition = condition;
}
private static <T> boolean eq(T left, T right) {
return left == right || (left != null && left.equals(right));
}
@Override
public RelayOk clear(JavascriptVm.BreakpointCallback callback, SyncCallback syncCallback) {
// TODO: make this code thread-safe.
long originalId = this.id;
this.id = INVALID_ID;
return breakpointManager.clearBreakpoint(this, callback, syncCallback, originalId);
}
@Override
public RelayOk flush(final JavascriptVm.BreakpointCallback callback, SyncCallback syncCallback) {
if (!isDirty()) {
if (callback != null) {
callback.success(this);
}
return RelaySyncCallback.finish(syncCallback);
}
setDirty(false);
return breakpointManager.changeBreakpoint(this, callback, syncCallback);
}
@Override
public IgnoreCountBreakpointExtension getIgnoreCountBreakpointExtension() {
JavascriptVm javascriptVm = breakpointManager.getDebugSession().getJavascriptVm();
return javascriptVm.getIgnoreCountBreakpointExtension();
}
private void setDirty(boolean isDirty) {
this.isDirty = isDirty;
}
private boolean isDirty() {
return isDirty;
}
private static Target getType(BreakpointInfo info) {
BreakpointInfo.Type infoType = info.type();
switch (infoType) {
case SCRIPTID: return new Target.ScriptId(info.script_id());
case SCRIPTNAME: return new Target.ScriptName(info.script_name());
case SCRIPTREGEXP: return new ScriptRegExpBreakpointTarget(info.script_regexp());
case FUNCTION: return new FunctionTarget(null);
}
throw new RuntimeException("Unknown type: " + infoType);
}
/**
* Visitor interface that includes all extensions.
*/
public interface TargetExtendedVisitor<R> extends
BreakpointTypeExtension.FunctionSupport.Visitor<R>,
BreakpointTypeExtension.ScriptRegExpSupport.Visitor<R> {
}
static class FunctionTarget extends Target {
private final String expression;
FunctionTarget(String expression) {
this.expression = expression;
}
@Override
public <R> R accept(Visitor<R> visitor) {
if (visitor instanceof BreakpointTypeExtension.FunctionSupport.Visitor) {
BreakpointTypeExtension.FunctionSupport.Visitor<R> functionVisitor =
(BreakpointTypeExtension.FunctionSupport.Visitor<R>) visitor;
return functionVisitor.visitFunction(expression);
} else {
return visitor.visitUnknown(this);
}
}
}
static final IgnoreCountBreakpointExtension IGNORE_COUNT_EXTENSION =
new IgnoreCountBreakpointExtension() {
@Override
public RelayOk setBreakpoint(JavascriptVm javascriptVm, Breakpoint.Target target,
int line, int column,
boolean enabled, String condition, int ignoreCount,
JavascriptVm.BreakpointCallback callback, SyncCallback syncCallback) {
JavascriptVmImpl javascriptVmImpl = (JavascriptVmImpl) javascriptVm;
BreakpointManager breakpointManager =
javascriptVmImpl.getDebugSession().getBreakpointManager();
return breakpointManager.setBreakpoint(target, line, column, enabled, condition, ignoreCount,
callback, syncCallback);
}
@Override
public RelayOk setIgnoreCount(Breakpoint breakpoint, int ignoreCount,
GenericCallback<Void> callback, SyncCallback syncCallback) {
BreakpointImpl breakpointImpl = (BreakpointImpl) breakpoint;
return breakpointImpl.setIgnoreCount(ignoreCount, callback, syncCallback);
}
};
}