// 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.protocol;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.List;
import org.chromium.sdk.JsValue;
import org.chromium.sdk.Script;
import org.chromium.sdk.Script.Type;
import org.chromium.sdk.Version;
import org.chromium.sdk.internal.JsonUtil;
import org.chromium.sdk.internal.protocolparser.JsonProtocolParseException;
import org.chromium.sdk.internal.v8native.V8ContextFilter;
import org.chromium.sdk.internal.v8native.protocol.input.SuccessCommandResponse;
import org.chromium.sdk.internal.v8native.protocol.input.VersionBody;
import org.chromium.sdk.internal.v8native.protocol.input.data.ContextHandle;
import org.chromium.sdk.internal.v8native.protocol.input.data.ObjectValueHandle;
import org.chromium.sdk.internal.v8native.protocol.input.data.PropertyObject;
import org.chromium.sdk.internal.v8native.protocol.input.data.PropertyWithRef;
import org.chromium.sdk.internal.v8native.protocol.input.data.PropertyWithValue;
import org.chromium.sdk.internal.v8native.protocol.input.data.ScriptHandle;
import org.chromium.sdk.internal.v8native.protocol.input.data.SomeHandle;
import org.chromium.sdk.internal.v8native.protocol.input.data.SomeRef;
import org.chromium.sdk.internal.v8native.protocol.output.ScriptsMessage;
import org.chromium.sdk.internal.v8native.value.DataWithRef;
import org.chromium.sdk.internal.v8native.value.PropertyReference;
import org.chromium.sdk.internal.v8native.value.PropertyType;
import org.json.simple.JSONObject;
/**
* A utility class to process V8 debugger messages.
*/
public class V8ProtocolUtil {
/**
* Computes a script type given a V8 Long type value
*
* @param typeNumber a type designator from a V8 JSON response
* @return a type corresponding to {@code typeNumber} or {@code null} if
* {@code typeNumber == null}
*/
public static Script.Type getScriptType(Long typeNumber) {
if (typeNumber == null) {
return null;
}
switch (typeNumber.intValue()) {
case ScriptsMessage.SCRIPTS_NORMAL:
return Type.NORMAL;
case ScriptsMessage.SCRIPTS_NATIVE:
return Type.NATIVE;
case ScriptsMessage.SCRIPTS_EXTENSION:
return Type.EXTENSION;
default:
throw new IllegalArgumentException("unknown script type: " + typeNumber);
}
}
/**
* Returns the value of "ref" field in object corresponding to the fieldName
* in parent.
*
* @param parent to get the object from
* @param fieldName of the object to get the "ref" from
* @return ref value or null if fieldName or "ref" not found
*/
public static Long getObjectRef(SomeRef child) {
if (child == null) {
return null;
}
return child.ref();
}
/**
* Constructs {@code PropertyReference}s from the specified object, be it in
* the "original" or "inlineRefs" format.
*
* @param handle to get property references from
* @return an array of PropertyReferences
*/
public static List<? extends PropertyReference> extractObjectProperties(
ObjectValueHandle handle) {
List<PropertyObject> props = handle.properties();
int propsLen = props.size();
List<PropertyReference> objProps = new ArrayList<PropertyReference>(propsLen);
for (int i = 0; i < propsLen; i++) {
PropertyObject prop = props.get(i);
putMirror(objProps, prop, PropertyNameGetter.SUBPROPERTY);
}
return objProps;
}
public static List<? extends PropertyReference> extractObjectInternalProperties(
ObjectValueHandle handle) {
List<PropertyReference> objProps = new ArrayList<PropertyReference>(3);
SomeRef protoObject = handle.protoObject();
if (protoObject != null) {
putMirror(objProps, protoObject, PropertyNameGetter.PROTO_OBJECT);
}
List<PropertyObject> props = handle.internalProperties();
if (props != null) {
for (int i = 0; i < props.size(); i++) {
PropertyObject prop = props.get(i);
putMirror(objProps, prop, PropertyNameGetter.PRIMITIVE_VALUE);
}
}
return objProps;
}
public static List<DataWithRef> extractAllPropertyRefs(ObjectValueHandle handle) {
final List<PropertyReference> properties = new ArrayList<PropertyReference>();
properties.addAll(extractObjectProperties(handle));
properties.addAll(extractObjectInternalProperties(handle));
return new AbstractList<DataWithRef>() {
@Override public int size() {
return properties.size();
}
@Override public DataWithRef get(int index) {
return properties.get(index).getValueObject();
}
};
}
public static <OBJ> void putMirror(List<PropertyReference> refs, OBJ propertyObject,
V8ProtocolUtil.PropertyNameGetter<OBJ> nameGetter) {
PropertyReference propertyRef = V8ProtocolUtil.extractProperty(propertyObject, nameGetter);
if (propertyRef != null) {
refs.add(propertyRef);
}
}
/**
* Constructs one {@code PropertyReference} from the specified object, be it in
* the "original" or "inlineRefs" format.
*
* @param prop json object
* @param valuePropertyName name of value property in this prop object, might be null
* @return PropertyReference or null if we ignore this property
*/
public static <OBJ> PropertyReference extractProperty(OBJ prop,
PropertyNameGetter<OBJ> nameGetter) {
Object name = nameGetter.getName(prop);
if (name == null) {
return null;
}
if (isInternalProperty(name)) {
return null;
}
DataWithRef propValue = nameGetter.getRef(prop);
Long propType = nameGetter.getPropertyType(prop);
// propType is NORMAL by default
int propTypeValue = propType != null
? propType.intValue()
: PropertyType.NORMAL.value;
if (propTypeValue == PropertyType.FIELD.value ||
propTypeValue == PropertyType.CONSTANT_FUNCTION.value ||
propTypeValue == PropertyType.CALLBACKS.value ||
propTypeValue == PropertyType.NORMAL.value) {
return new PropertyReference(name, propValue);
}
return null;
}
public static abstract class PropertyNameGetter<OBJ> {
static class SimpleNameGetter extends PropertyNameGetter<SomeRef> {
private final String name;
SimpleNameGetter(String name) {
this.name = name;
}
@Override
String getName(SomeRef ref) {
return name;
}
@Override
DataWithRef getRef(SomeRef someRef) {
return DataWithRef.fromSomeRef(someRef);
}
@Override
Long getPropertyType(SomeRef someRef) {
return null;
}
}
static final PropertyNameGetter<PropertyObject> LOCAL = new SubpropertyNameGetter() {
@Override
Object getName(PropertyObject ref) {
Object name = super.getName(ref);
if (V8ProtocolUtil.isInternalProperty(name)) {
return null;
}
return name;
}
};
/** The name of the "this" object to report as a variable name. */
public static final PropertyNameGetter<SomeRef> THIS = new SimpleNameGetter("this");
static final PropertyNameGetter<SomeRef> PROTO_OBJECT =
new SimpleNameGetter("__proto__");
static final PropertyNameGetter<PropertyObject> PRIMITIVE_VALUE = new SubpropertyNameGetter();
public static final PropertyNameGetter<PropertyObject> SUBPROPERTY =
new SubpropertyNameGetter();
static class SubpropertyNameGetter extends PropertyNameGetter<PropertyObject> {
@Override
Object getName(PropertyObject ref) {
return ref.name();
}
@Override
DataWithRef getRef(PropertyObject prop) {
PropertyWithValue asPropertyWithValue = prop.asPropertyWithValue();
if (asPropertyWithValue != null) {
return DataWithRef.fromSomeRef(asPropertyWithValue.value());
} else {
return DataWithRef.fromLong(prop.asPropertyWithRef().ref());
}
}
@Override
Long getPropertyType(PropertyObject prop) {
PropertyWithRef asPropertyWithRef = prop.asPropertyWithRef();
if (asPropertyWithRef == null) {
return null;
}
return asPropertyWithRef.propertyType();
}
}
abstract DataWithRef getRef(OBJ prop);
/**
* @return property name or null if we should skip this property
*/
abstract Object getName(OBJ ref);
abstract Long getPropertyType(OBJ prop);
}
/**
* @param propertyName the property name to check
* @return whether the given property name corresponds to an internal V8
* property
*/
public static boolean isInternalProperty(Object propertyName) {
if (propertyName instanceof String == false) {
return false;
}
String propertyNameStr = (String) propertyName;
// Chrome can return properties like ".arguments". They should be ignored.
return propertyNameStr.length() == 0 || propertyNameStr.startsWith(".");
}
/**
* Gets a function name from the given function handle.
*
* @param functionObject the function handle
* @return the actual of inferred function name. Will handle {@code null} or
* unnamed functions
*/
public static String getFunctionName(JSONObject functionObject) {
if (functionObject == null) {
return "<unknown>";
} else {
String name = getNameOrInferred(functionObject, V8Protocol.LOCAL_NAME);
if (isNullOrEmpty(name)) {
return "(anonymous function)";
} else {
return name;
}
}
}
/**
* Gets a script id from a script response.
*
* @param scriptObject to get the "id" value from
* @return the script id
*/
public static Long getScriptIdFromResponse(ScriptHandle scriptObject) {
return scriptObject.id();
}
/**
* Determines if a {@code script} is valid in the current debug context.
* Returns {@code null} if it is not, otherwise returns {@code script}.
*
* @param script to check and, possibly, modify
* @param refs from the corresponding V8 response
* @return script with a non-null name if the script is valid, {@code null}
* otherwise
*/
public static ScriptHandle validScript(ScriptHandle script, List<SomeHandle> refs,
V8ContextFilter contextFilter) {
Long contextRef = V8ProtocolUtil.getObjectRef(script.context());
for (int i = 0, size = refs.size(); i < size; i++) {
SomeHandle ref = refs.get(i);
if (ref.handle() != contextRef.longValue()) {
continue;
}
ContextHandle contextHandle;
try {
contextHandle = ref.asContextHandle();
} catch (JsonProtocolParseException e) {
throw new RuntimeException(e);
}
if (!contextFilter.isContextOurs(contextHandle)) {
return null;
}
return script;
}
return null; // good context not found
}
public static Version parseVersionResponse(SuccessCommandResponse versionResponse) {
VersionBody body;
try {
body = versionResponse.body().asVersionBody();
} catch (JsonProtocolParseException e) {
throw new RuntimeException(e);
}
String versionString = body.getV8Version();
if (versionString == null) {
return null;
}
return Version.parseString(versionString);
}
// This method extracts string message from exception value. The accurate way is to get
// message property, but since we currently don't have true exception values (we have only
// surrogate strings), the current approach is ok. Later this method may become
// asynchronous.
public static String getExceptionString(JsValue exception) {
return exception.getValueString();
}
private static String getNameOrInferred(JSONObject obj, V8Protocol nameProperty) {
String name = JsonUtil.getAsString(obj, nameProperty);
if (isNullOrEmpty(name)) {
name = JsonUtil.getAsString(obj, V8Protocol.INFERRED_NAME);
}
return name;
}
private static boolean isNullOrEmpty(String value) {
return value == null || value.length() == 0;
}
}