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