// Copyright 2012 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
//
// 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.collide.client.code.debugging;
import com.google.collide.client.code.debugging.DebuggerApiTypes.BreakpointInfo;
import com.google.collide.client.code.debugging.DebuggerApiTypes.CallFrame;
import com.google.collide.client.code.debugging.DebuggerApiTypes.ConsoleMessage;
import com.google.collide.client.code.debugging.DebuggerApiTypes.ConsoleMessageLevel;
import com.google.collide.client.code.debugging.DebuggerApiTypes.ConsoleMessageType;
import com.google.collide.client.code.debugging.DebuggerApiTypes.CssStyleSheetHeader;
import com.google.collide.client.code.debugging.DebuggerApiTypes.Location;
import com.google.collide.client.code.debugging.DebuggerApiTypes.OnAllCssStyleSheetsResponse;
import com.google.collide.client.code.debugging.DebuggerApiTypes.OnBreakpointResolvedResponse;
import com.google.collide.client.code.debugging.DebuggerApiTypes.OnEvaluateExpressionResponse;
import com.google.collide.client.code.debugging.DebuggerApiTypes.OnPausedResponse;
import com.google.collide.client.code.debugging.DebuggerApiTypes.OnRemoteObjectPropertiesResponse;
import com.google.collide.client.code.debugging.DebuggerApiTypes.OnRemoteObjectPropertyChanged;
import com.google.collide.client.code.debugging.DebuggerApiTypes.OnScriptParsedResponse;
import com.google.collide.client.code.debugging.DebuggerApiTypes.PropertyDescriptor;
import com.google.collide.client.code.debugging.DebuggerApiTypes.RemoteObject;
import com.google.collide.client.code.debugging.DebuggerApiTypes.RemoteObjectId;
import com.google.collide.client.code.debugging.DebuggerApiTypes.RemoteObjectSubType;
import com.google.collide.client.code.debugging.DebuggerApiTypes.RemoteObjectType;
import com.google.collide.client.code.debugging.DebuggerApiTypes.Scope;
import com.google.collide.client.code.debugging.DebuggerApiTypes.ScopeType;
import com.google.collide.client.code.debugging.DebuggerApiTypes.StackTraceItem;
import com.google.collide.json.client.Jso;
import com.google.collide.json.shared.JsonArray;
import com.google.collide.json.shared.JsonObject;
import com.google.collide.shared.util.JsonCollections;
import com.google.collide.shared.util.StringUtils;
/**
* Utility class to deal with the Chrome debugger responses.
*/
class DebuggerChromeApiUtils {
/**
* Parses the {@link OnPausedResponse} debugger response.
*
* @param result debugger result
* @return new instance, or {@code null} for invalid {@code result}
*/
public static OnPausedResponse parseOnPausedResponse(Jso result) {
if (result == null) {
return null;
}
final JsonArray<CallFrame> callFrames = parseCallFramesArray(result.getArrayField(
"callFrames"));
return new OnPausedResponse() {
@Override
public JsonArray<CallFrame> getCallFrames() {
return callFrames;
}
};
}
/**
* Parses the {@link OnBreakpointResolvedResponse} debugger response.
*
* @param request original request data that was sent to the debugger
* @param result debugger result
* @return new instance, or {@code null} for invalid {@code result}
*/
public static OnBreakpointResolvedResponse parseOnBreakpointResolvedResponse(Jso request,
Jso result) {
if (result == null) {
return null;
}
final JsonArray<Location> locations = parseBreakpointLocations(result);
final BreakpointInfo breakpointInfo = parseBreakpointInfo(request);
final String breakpointId = result.getStringField("breakpointId");
return new OnBreakpointResolvedResponse() {
@Override
public BreakpointInfo getBreakpointInfo() {
return breakpointInfo;
}
@Override
public String getBreakpointId() {
return breakpointId;
}
@Override
public JsonArray<Location> getLocations() {
return locations;
}
};
}
/**
* Parses the debugger response that is fired when a breakpoint got removed.
*
* @param request original request data that was sent to the debugger
* @return ID of the breakpoint that was removed
*/
public static String parseOnRemoveBreakpointResponse(Jso request) {
if (request == null) {
return null;
}
return request.getStringField("breakpointId");
}
/**
* Parses the {@link OnScriptParsedResponse} debugger response.
*
* @param result debugger result
* @return new instance, or {@code null} for invalid {@code result}
*/
public static OnScriptParsedResponse parseOnScriptParsedResponse(final Jso result) {
if (result == null) {
return null;
}
return new OnScriptParsedResponse() {
@Override
public int getStartLine() {
return result.getFieldCastedToInteger("startLine");
}
@Override
public int getStartColumn() {
return result.getFieldCastedToInteger("startColumn");
}
@Override
public int getEndLine() {
return result.getFieldCastedToInteger("endLine");
}
@Override
public int getEndColumn() {
return result.getFieldCastedToInteger("endColumn");
}
@Override
public String getUrl() {
return result.getStringField("url");
}
@Override
public String getScriptId() {
return result.getStringField("scriptId");
}
@Override
public boolean isContentScript() {
return result.getFieldCastedToBoolean("isContentScript");
}
};
}
/**
* Parses the {@link OnRemoteObjectPropertiesResponse} debugger response.
*
* @param request original request data that was sent to the debugger
* @param result debugger result
* @return new instance, or {@code null} for invalid {@code request} or
* {@code result}
*/
public static OnRemoteObjectPropertiesResponse parseOnRemoteObjectPropertiesResponse(Jso request,
Jso result) {
if (request == null || result == null) {
return null;
}
final RemoteObjectId objectId = parseRemoteObjectId(request.getStringField("objectId"));
final JsonArray<PropertyDescriptor> properties = parsePropertyDescriptorArray(
result.getArrayField("result"));
return new OnRemoteObjectPropertiesResponse() {
@Override
public RemoteObjectId getObjectId() {
return objectId;
}
@Override
public JsonArray<PropertyDescriptor> getProperties() {
return properties;
}
};
}
/**
* Creates a {@link OnRemoteObjectPropertyChanged} debugger response for a
* deleted remote object property event.
*
* @param remoteObjectId ID of the remote object the property was deleted from
* @param propertyName name of the deleted property
* @param wasThrown true if an exception was thrown by the debugger
* @return new instance
*/
public static OnRemoteObjectPropertyChanged createOnRemoveRemoteObjectPropertyResponse(
RemoteObjectId remoteObjectId, String propertyName, boolean wasThrown) {
return createOnRemoteObjectPropertyChangedResponse(
remoteObjectId, propertyName, null, null, false, wasThrown);
}
/**
* Creates a {@link OnRemoteObjectPropertyChanged} debugger response for a
* renamed remote object property event.
*
* @param remoteObjectId ID of the remote object the property was deleted from
* @param oldName old property name
* @param newName new property name
* @param wasThrown true if an exception was thrown by the debugger
* @return new instance
*/
public static OnRemoteObjectPropertyChanged createOnRenameRemoteObjectPropertyResponse(
RemoteObjectId remoteObjectId, String oldName, String newName, boolean wasThrown) {
return createOnRemoteObjectPropertyChangedResponse(
remoteObjectId, oldName, newName, null, false, wasThrown);
}
/**
* Creates a {@link OnRemoteObjectPropertyChanged} debugger response for a
* edited remote object property event.
*
* @param remoteObjectId ID of the remote object the property was deleted from
* @param propertyName name of the edited property
* @param propertyValue new property value
* @param wasThrown true if an exception was thrown by the debugger
* @return new instance
*/
public static OnRemoteObjectPropertyChanged createOnEditRemoteObjectPropertyResponse(
RemoteObjectId remoteObjectId, String propertyName, RemoteObject propertyValue,
boolean wasThrown) {
return createOnRemoteObjectPropertyChangedResponse(
remoteObjectId, propertyName, propertyName, propertyValue, true, wasThrown);
}
private static OnRemoteObjectPropertyChanged createOnRemoteObjectPropertyChangedResponse(
final RemoteObjectId remoteObjectId, final String oldName, final String newName,
final RemoteObject value, final boolean isValueChanged, final boolean wasThrown) {
return new OnRemoteObjectPropertyChanged() {
@Override
public RemoteObjectId getObjectId() {
return remoteObjectId;
}
@Override
public String getOldName() {
return oldName;
}
@Override
public String getNewName() {
return newName;
}
@Override
public boolean isValueChanged() {
return isValueChanged;
}
@Override
public RemoteObject getValue() {
return value;
}
@Override
public boolean wasThrown() {
return wasThrown;
}
};
}
/**
* Parses the {@link OnEvaluateExpressionResponse} debugger response.
*
* @param request original request data that was sent to the debugger
* @param result debugger result
* @return new instance, or {@code null} for invalid {@code request} or
* {@code result}
*/
public static OnEvaluateExpressionResponse parseOnEvaluateExpressionResponse(Jso request,
Jso result) {
if (request == null || result == null) {
return null;
}
final String expression = request.getStringField("expression");
final String callFrameId = request.getStringField("callFrameId");
final RemoteObject evaluationResult = parseRemoteObject((Jso) result.getObjectField("result"));
final boolean wasThrown = result.getFieldCastedToBoolean("wasThrown");
return new OnEvaluateExpressionResponse() {
@Override
public String getExpression() {
return expression;
}
@Override
public String getCallFrameId() {
return callFrameId;
}
@Override
public RemoteObject getResult() {
return evaluationResult;
}
@Override
public boolean wasThrown() {
return wasThrown;
}
};
}
/**
* Parses the {@link OnAllCssStyleSheetsResponse} debugger response.
*
* @param result debugger result
* @return new instance, or {@code null} for invalid {@code result}
*/
public static OnAllCssStyleSheetsResponse parseOnAllCssStyleSheetsResponse(Jso result) {
if (result == null) {
return null;
}
final JsonArray<CssStyleSheetHeader> headers = parseCssStyleSheetHeaders(
result.getArrayField("headers"));
return new OnAllCssStyleSheetsResponse() {
@Override
public JsonArray<CssStyleSheetHeader> getHeaders() {
return headers;
}
};
}
/**
* Parses the {@link DebuggerChromeApi#METHOD_RUNTIME_CALL_FUNCTION_ON}
* debugger response.
*
* @param result debugger result
* @return new instance, or {@code null} for invalid {@code result}
*/
public static RemoteObject parseCallFunctionOnResult(Jso result) {
if (result == null) {
return null;
}
return parseRemoteObject((Jso) result.getObjectField("result"));
}
/**
* Parses the {@link ConsoleMessage} debugger response.
*
* @param result debugger result
* @return new instance, or {@code null} for invalid {@code result}
*/
public static ConsoleMessage parseOnConsoleMessageReceived(Jso result) {
if (result == null) {
return null;
}
Jso json = (Jso) result.getObjectField("message");
final int lineNumber = json.hasOwnProperty("line") ? json.getFieldCastedToInteger("line") : -1;
final int repeatCount =
json.hasOwnProperty("repeatCount") ? json.getFieldCastedToInteger("repeatCount") : 1;
final ConsoleMessageLevel messageLevel = parseConsoleMessageLevel(json.getStringField("level"));
final ConsoleMessageType messageType = parseConsoleMessageType(json.getStringField("type"));
final JsonArray<RemoteObject> parameters = parseRemoteObjectArray(
json.getArrayField("parameters"));
final JsonArray<StackTraceItem> stackTrace = parseStackTraceItemArray(
json.getArrayField("stackTrace"));
final String text = json.getStringField("text");
final String url = json.getStringField("url");
return new ConsoleMessage() {
@Override
public ConsoleMessageLevel getLevel() {
return messageLevel;
}
@Override
public ConsoleMessageType getType() {
return messageType;
}
@Override
public int getLineNumber() {
return lineNumber;
}
@Override
public JsonArray<RemoteObject> getParameters() {
return parameters;
}
@Override
public int getRepeatCount() {
return repeatCount;
}
@Override
public JsonArray<StackTraceItem> getStackTrace() {
return stackTrace;
}
@Override
public String getText() {
return text;
}
@Override
public String getUrl() {
return url;
}
};
}
/**
* Parses the debugger response that is fired when subsequent console
* message(s) are equal to the previous one(s).
*
* @param result debugger result
* @return new repeat count value, or {@code -1} for invalid {@code result}
*/
public static int parseOnConsoleMessageRepeatCountUpdated(Jso result) {
if (result == null) {
return -1;
}
return result.getFieldCastedToInteger("count");
}
private static JsonArray<PropertyDescriptor> parsePropertyDescriptorArray(
JsonArray<JsonObject> jsonArray) {
JsonArray<PropertyDescriptor> result = JsonCollections.createArray();
if (jsonArray != null) {
for (int i = 0, n = jsonArray.size(); i < n; ++i) {
PropertyDescriptor propertyDescriptor = parsePropertyDescriptor((Jso) jsonArray.get(i));
if (propertyDescriptor != null) {
result.add(propertyDescriptor);
}
}
}
return result;
}
private static PropertyDescriptor parsePropertyDescriptor(final Jso json) {
if (json == null) {
return null;
}
final RemoteObject remoteObject = parseRemoteObject((Jso) json.getObjectField("value"));
final RemoteObject getter = parseRemoteObject((Jso) json.getObjectField("get"));
final RemoteObject setter = parseRemoteObject((Jso) json.getObjectField("set"));
return new PropertyDescriptor() {
@Override
public String getName() {
return json.getStringField("name");
}
@Override
public RemoteObject getValue() {
return remoteObject;
}
@Override
public boolean wasThrown() {
return json.getFieldCastedToBoolean("wasThrown");
}
@Override
public boolean isConfigurable() {
return json.getFieldCastedToBoolean("configurable");
}
@Override
public boolean isEnumerable() {
return json.getFieldCastedToBoolean("enumerable");
}
@Override
public boolean isWritable() {
return json.getFieldCastedToBoolean("writable");
}
@Override
public RemoteObject getGetterFunction() {
return getter;
}
@Override
public RemoteObject getSetterFunction() {
return setter;
}
};
}
private static JsonArray<CallFrame> parseCallFramesArray(JsonArray<JsonObject> jsonArray) {
JsonArray<CallFrame> result = JsonCollections.createArray();
if (jsonArray != null) {
for (int i = 0, n = jsonArray.size(); i < n; ++i) {
CallFrame callFrame = parseCallFrame((Jso) jsonArray.get(i));
if (callFrame != null) {
result.add(callFrame);
}
}
}
return result;
}
private static CallFrame parseCallFrame(Jso json) {
if (json == null) {
return null;
}
final String functionName = json.getStringField("functionName");
final String callFrameId = json.getStringField("callFrameId");
final Location location = parseLocation((Jso) json.getObjectField("location"));
final JsonArray<Scope> scopeChain = parseScopeChain(json.getArrayField("scopeChain"));
final RemoteObject thisObject = parseRemoteObject((Jso) json.getObjectField("this"));
return new CallFrame() {
@Override
public String getFunctionName() {
return functionName;
}
@Override
public String getId() {
return callFrameId;
}
@Override
public Location getLocation() {
return location;
}
@Override
public JsonArray<Scope> getScopeChain() {
return scopeChain;
}
@Override
public RemoteObject getThis() {
return thisObject;
}
};
}
private static Location parseLocation(final Jso json) {
if (json == null) {
return null;
}
return new Location() {
@Override
public int getColumnNumber() {
return json.getFieldCastedToInteger("columnNumber");
}
@Override
public int getLineNumber() {
return json.getFieldCastedToInteger("lineNumber");
}
@Override
public String getScriptId() {
return json.getStringField("scriptId");
}
};
}
private static JsonArray<Scope> parseScopeChain(JsonArray<JsonObject> jsonArray) {
JsonArray<Scope> result = JsonCollections.createArray();
if (jsonArray != null) {
for (int i = 0, n = jsonArray.size(); i < n; ++i) {
Scope scope = parseScope(jsonArray.get(i));
if (scope != null) {
result.add(scope);
}
}
}
return result;
}
private static Scope parseScope(JsonObject json) {
if (json == null) {
return null;
}
final RemoteObject object = parseRemoteObject((Jso) json.getObjectField("object"));
final ScopeType scopeType = parseScopeType(json.getStringField("type"));
return new Scope() {
@Override
public RemoteObject getObject() {
return object;
}
@Override
public boolean isTransient() {
// @see http://code.google.com/chrome/devtools/docs/protocol/tot/debugger.html#type-Scope
// For GLOBAL and WITH scopes it represents the actual object; for the rest of the scopes,
// it is artificial transient object enumerating scope variables as its properties.
return scopeType != ScopeType.GLOBAL && scopeType != ScopeType.WITH;
}
@Override
public ScopeType getType() {
return scopeType;
}
};
}
private static ScopeType parseScopeType(String type) {
return type == null ? null : ScopeType.valueOf(type.toUpperCase());
}
private static JsonArray<RemoteObject> parseRemoteObjectArray(JsonArray<JsonObject> jsonArray) {
JsonArray<RemoteObject> result = JsonCollections.createArray();
if (jsonArray != null) {
for (int i = 0, n = jsonArray.size(); i < n; ++i) {
RemoteObject remoteObject = parseRemoteObject((Jso) jsonArray.get(i));
if (remoteObject != null) {
result.add(remoteObject);
}
}
}
return result;
}
private static RemoteObject parseRemoteObject(Jso json) {
if (json == null) {
return null;
}
final RemoteObjectId objectId = parseRemoteObjectId(json.getStringField("objectId"));
final RemoteObjectType remoteObjectType = parseRemoteObjectType(json.getStringField("type"));
final RemoteObjectSubType remoteObjectSubType = parseRemoteObjectSubType(
json.getStringField("subtype"));
final String description = StringUtils.ensureNotEmpty(json.getStringField("description"),
json.getFieldCastedToString("value"));
return new RemoteObject() {
@Override
public String getDescription() {
return description;
}
@Override
public boolean hasChildren() {
return objectId != null;
}
@Override
public RemoteObjectId getObjectId() {
return objectId;
}
@Override
public RemoteObjectType getType() {
return remoteObjectType;
}
@Override
public RemoteObjectSubType getSubType() {
return remoteObjectSubType;
}
};
}
private static RemoteObjectType parseRemoteObjectType(String type) {
return type == null ? null : RemoteObjectType.valueOf(type.toUpperCase());
}
private static RemoteObjectSubType parseRemoteObjectSubType(String subtype) {
return subtype == null ? null : RemoteObjectSubType.valueOf(subtype.toUpperCase());
}
private static RemoteObjectId parseRemoteObjectId(final String objectId) {
if (StringUtils.isNullOrEmpty(objectId)) {
return null;
}
return new RemoteObjectId() {
@Override
public String toString() {
return objectId;
}
};
}
/**
* Handles both "Debugger.setBreakpointByUrl" and "Debugger.breakpointResolved" responses.
*
* Docs:
* http://code.google.com/chrome/devtools/docs/protocol/tot/debugger.html#command-setBreakpointByUrl
* http://code.google.com/chrome/devtools/docs/protocol/tot/debugger.html#event-breakpointResolved
*/
private static JsonArray<Location> parseBreakpointLocations(Jso json) {
JsonArray<Location> result = JsonCollections.createArray();
if (json != null) {
// Debugger.breakpointResolved response.
Location location = parseLocation((Jso) json.getObjectField("location"));
if (location != null) {
result.add(location);
}
// Debugger.setBreakpointByUrl response.
JsonArray<JsonObject> jsonArray = json.getArrayField("locations");
if (jsonArray != null) {
for (int i = 0, n = jsonArray.size(); i < n; ++i) {
location = parseLocation((Jso) jsonArray.get(i));
if (location != null) {
result.add(location);
}
}
}
}
return result;
}
private static BreakpointInfo parseBreakpointInfo(final Jso json) {
if (json == null) {
return null;
}
return new BreakpointInfo() {
@Override
public String getUrl() {
return json.getStringField("url");
}
@Override
public String getUrlRegex() {
return json.getStringField("urlRegex");
}
@Override
public int getLineNumber() {
return json.getFieldCastedToInteger("lineNumber");
}
@Override
public int getColumnNumber() {
return json.getFieldCastedToInteger("columnNumber");
}
@Override
public String getCondition() {
return json.getStringField("condition");
}
};
}
private static JsonArray<CssStyleSheetHeader> parseCssStyleSheetHeaders(
JsonArray<JsonObject> jsonArray) {
JsonArray<CssStyleSheetHeader> result = JsonCollections.createArray();
if (jsonArray != null) {
for (int i = 0, n = jsonArray.size(); i < n; ++i) {
CssStyleSheetHeader header = parseCssStyleSheetHeader((Jso) jsonArray.get(i));
if (header != null) {
result.add(header);
}
}
}
return result;
}
private static CssStyleSheetHeader parseCssStyleSheetHeader(final Jso json) {
if (json == null) {
return null;
}
return new CssStyleSheetHeader() {
@Override
public boolean isDisabled() {
return json.getFieldCastedToBoolean("disabled");
}
@Override
public String getId() {
return json.getStringField("styleSheetId");
}
@Override
public String getUrl() {
return json.getStringField("sourceURL");
}
@Override
public String getTitle() {
return json.getStringField("title");
}
};
}
private static ConsoleMessageLevel parseConsoleMessageLevel(String level) {
return level == null ? null : ConsoleMessageLevel.valueOf(level.toUpperCase());
}
private static ConsoleMessageType parseConsoleMessageType(String type) {
return type == null ? null : ConsoleMessageType.valueOf(type.toUpperCase());
}
private static JsonArray<StackTraceItem> parseStackTraceItemArray(
JsonArray<JsonObject> jsonArray) {
JsonArray<StackTraceItem> result = JsonCollections.createArray();
if (jsonArray != null) {
for (int i = 0, n = jsonArray.size(); i < n; ++i) {
StackTraceItem item = parseStackTraceItem((Jso) jsonArray.get(i));
if (item != null) {
result.add(item);
}
}
}
return result;
}
private static StackTraceItem parseStackTraceItem(final Jso json) {
if (json == null) {
return null;
}
return new StackTraceItem() {
@Override
public int getColumnNumber() {
return json.getFieldCastedToInteger("columnNumber");
}
@Override
public String getFunctionName() {
return json.getStringField("functionName");
}
@Override
public int getLineNumber() {
return json.getFieldCastedToInteger("lineNumber");
}
@Override
public String getUrl() {
return json.getStringField("url");
}
};
}
private DebuggerChromeApiUtils() {} // COV_NF_LINE
}