Add JSO inspection support.
IDEs can use this class to implement JSO inspection capabilities in their debug view.
Review at http://gwt-code-reviews.appspot.com/1454807
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10327 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/core/client/debug/JsoInspector.java b/user/src/com/google/gwt/core/client/debug/JsoInspector.java
new file mode 100644
index 0000000..bad03ee
--- /dev/null
+++ b/user/src/com/google/gwt/core/client/debug/JsoInspector.java
@@ -0,0 +1,193 @@
+/*
+ * 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.";
+ }
+ }
+}