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.";
+    }
+  }
+}