blob: bad03ee3824a68ebc70382b80066c9ffbd1a6a8a [file] [log] [blame]
/*
* Copyright 2011 Google Inc.
*
* 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.gwt.core.client.debug;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
/**
* This class provides an API for IDEs to inspect JavaScript objects and is not
* intended to be used in GWT applications. IDEs that allow custom value
* renderers for debugging can use it to box JavaScript objects into suitable
* Java types.
*
* TODO: provide a way to test whether a node has children (to be used as an
* optimization in IntelliJ).
*
* TODO: implement and return concrete JsoProperty subtypes (Integer, Float,
* etc.) to get more descriptive labels in IntelliJ.
*/
public class JsoInspector {
/**
* A simple Java object to hold a key and value pair.
*/
public static class JsoProperty implements Comparable<JsoProperty> {
public final String key;
public final Object value;
public final boolean isOwnProperty;
private JsoProperty(String key, Object value, boolean isOwnProperty) {
this.key = key;
this.value = value;
this.isOwnProperty = isOwnProperty;
}
@Override
public int compareTo(JsoProperty o) {
int keyComparison = key.compareTo(o.key);
/*
* The hash code comparison is so this class's natural ordering is
* consistent (see Comparable interface javadoc). Ideally, we would have
* done value.compareTo(o.value), but value may not implement Comparable.
*/
return keyComparison != 0 ? keyComparison : value.hashCode()
- o.value.hashCode();
}
@Override
public String toString() {
StringBuilder s = new StringBuilder();
if (isOwnProperty) {
s.append('*');
}
s.append(key).append(": ");
if (value instanceof String) {
s.append('"').append(value).append('"');
} else {
s.append(value);
}
return s.toString();
}
}
private static class JsoBoxer extends JavaScriptObject {
protected JsoBoxer() {
}
/**
* Returns a Java object that represents this JavaScriptObject instance. The
* returned Java object may still contain other JavaScriptObjects. Eclipse
* will attempt to lazily fetch the logical structure for those children
* JavaScriptObjects when the user clicks on one of them.
*/
public final native Object box() /*-{
// Returns a Java object for simpler JavaScript values
// (primitives, functions, null, undefined), or the given value
// if it is not a "simple" type
var asJavaObjectForSimpleValue = function(value) {
var valueType = typeof(value);
// Earlier, I had function types printing their function contents,
// but pure Java classes in GWT get converted to function prototypes
// in Javascript, so there were issues
if (valueType == "number") {
// Differentiate int from float
if (/[.]/.test(value + '')) {
return @java.lang.Float::new(F)(value);
} else {
return @java.lang.Integer::new(I)(value);
}
} else if (valueType == "boolean") {
return @java.lang.Boolean::new(Z)(value);
} else if (valueType == "string") {
return value;
} else if (valueType == "undefined" || valueType == "null") {
// Return the corresponding string
return valueType;
} else {
return value;
}
}
var asJavaObject = function(value) {
var valueType = typeof(value);
if (valueType == "object") {
if (value instanceof Array) {
var list = @java.util.ArrayList::new()();
for (i in value) {
list.@java.util.ArrayList::add(Ljava/lang/Object;)(asJavaObjectForSimpleValue(value[i]));
}
// If we return a List, Eclipse's Variables view will show one
// extra level unnecessarily. It does not do this for Java arrays.
return list.@java.util.ArrayList::toArray()();
} else {
var properties = @java.util.ArrayList::new()();
for (var name in value) {
var propertyValue;
try {
var origPropertyValue = value[name];
if (typeof(origPropertyValue) == "function"
&& !value.hasOwnProperty(name)) {
// Prevent every JavaScriptObject method from appearing
continue;
}
// Sometimes, we don't have permission to see the value
// and an exception is thrown
propertyValue = asJavaObjectForSimpleValue(origPropertyValue);
} catch (e) {
propertyValue = "ERROR: " + e.name + " - " + e.message;
}
// Wrap the (key, value) in our JsoProperty class
var jsoProperty = @com.google.gwt.core.client.debug.JsoInspector.JsoProperty::new(Ljava/lang/String;Ljava/lang/Object;Z)(name, propertyValue, value.hasOwnProperty(name));
properties.@java.util.ArrayList::add(Ljava/lang/Object;)(jsoProperty);
}
// Sort the properties
@java.util.Collections::sort(Ljava/util/List;)(properties);
// If we return a List, Eclipse's Variables view will show one
// extra level unnecessarily. It does not do this for Java arrays.
var propertiesAsArray = properties.@java.util.ArrayList::toArray()();
return propertiesAsArray;
}
} else {
return asJavaObjectForSimpleValue(value);
}
};
return asJavaObject(this);
}-*/;
}
/**
* Wraps a JavaScript object into a suitable inspectable type.
*/
public static Object convertToInspectableObject(Object jso) {
try {
JsoBoxer boxer = (JsoBoxer) jso;
return boxer.box();
} catch (Throwable t) {
GWT.log("GWT could not inspect the JavaScriptObject.", t);
return "ERROR: Could not inspect the JavaScriptObject, see GWT log for details.";
}
}
}