| /* |
| * Copyright 2010 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.web.bindery.autobean.gwt.client.impl; |
| |
| import com.google.gwt.core.client.GwtScriptOnly; |
| import com.google.gwt.core.client.JavaScriptObject; |
| import com.google.gwt.core.client.JsonUtils; |
| import com.google.web.bindery.autobean.shared.Splittable; |
| import com.google.web.bindery.autobean.shared.impl.HasSplittable; |
| import com.google.web.bindery.autobean.shared.impl.StringQuoter; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| |
| /** |
| * Implements the EntityCodex.Splittable interface using a raw JavaScriptObject. |
| * <p> |
| * A string value represented by a JsoSplittable can't use the string object |
| * directly, since {@code String.prototype} is overridden, so instead a |
| * temporary wrapper object is used to encapsulate the string data. |
| */ |
| @GwtScriptOnly |
| public final class JsoSplittable extends JavaScriptObject implements Splittable, HasSplittable { |
| private static boolean stringifyFastTested; |
| private static boolean stringifyFastResult; |
| |
| public static native JsoSplittable create() /*-{ |
| return {}; |
| }-*/; |
| |
| public static Splittable create(boolean value) { |
| return create0(value); |
| } |
| |
| public static Splittable create(double value) { |
| return create0(value); |
| } |
| |
| public static Splittable create(String value) { |
| return create0(value); |
| } |
| |
| public static native JsoSplittable createIndexed() /*-{ |
| return []; |
| }-*/; |
| |
| public static Splittable nullValue() { |
| return NullSplittable.INSTANCE; |
| } |
| |
| private static native Splittable create0(boolean object) /*-{ |
| return Object(object); |
| }-*/; |
| |
| private static native Splittable create0(double object) /*-{ |
| return Object(object); |
| }-*/; |
| |
| private static native Splittable create0(String object) /*-{ |
| return { |
| __s : object |
| }; |
| }-*/; |
| |
| private static native boolean isUnwrappedString(JavaScriptObject obj) /*-{ |
| return Object.prototype.toString.call(obj) == '[object String]'; |
| }-*/; |
| |
| private static boolean stringifyFastSupported() { |
| if (stringifyFastTested) { |
| return stringifyFastResult; |
| } |
| stringifyFastTested = true; |
| return stringifyFastResult = stringifyFastSupported0(); |
| } |
| |
| /** |
| * Test that the JSON api is available and that it does not add function |
| * objects to the output. The test for function objects is for old versions of |
| * Safari. |
| */ |
| private static native boolean stringifyFastSupported0() /*-{ |
| return $wnd.JSON && $wnd.JSON.stringify && $wnd.JSON.stringify({ |
| b : function() { |
| } |
| }) == '{}'; |
| }-*/; |
| |
| protected JsoSplittable() { |
| }; |
| |
| public native boolean asBoolean() /*-{ |
| return this && this.valueOf(); |
| }-*/; |
| |
| public native double asNumber() /*-{ |
| return this && this.valueOf(); |
| }-*/; |
| |
| public void assign(Splittable parent, int index) { |
| if (isString()) { |
| assign0(parent, index, asString()); |
| } else { |
| assign0(parent, index, this); |
| } |
| } |
| |
| public void assign(Splittable parent, String index) { |
| if (isString()) { |
| assign0(parent, index, asString()); |
| } else { |
| assign0(parent, index, this); |
| } |
| } |
| |
| public native String asString() /*-{ |
| return this.__s; |
| }-*/; |
| |
| public Splittable deepCopy() { |
| return StringQuoter.split(getPayload()); |
| } |
| |
| public JsoSplittable get(int index) { |
| return getRaw(index); |
| } |
| |
| public JsoSplittable get(String key) { |
| return getRaw(key); |
| } |
| |
| public String getPayload() { |
| if (isString()) { |
| return JsonUtils.escapeValue(asString()); |
| } |
| if (stringifyFastSupported()) { |
| return stringifyFast(); |
| } |
| return stringifySlow(); |
| } |
| |
| public List<String> getPropertyKeys() { |
| List<String> toReturn = new ArrayList<String>(); |
| getPropertyKeys0(toReturn); |
| return Collections.unmodifiableList(toReturn); |
| } |
| |
| public native Object getReified(String key) /*-{ |
| return this.__reified && this.__reified[':' + key]; |
| }-*/; |
| |
| public Splittable getSplittable() { |
| return this; |
| } |
| |
| public native boolean isBoolean() /*-{ |
| return this && typeof(this.valueOf()) === 'boolean'; |
| }-*/; |
| |
| public native boolean isFunction() /*-{ |
| return Object.prototype.toString.call(this) == '[object Function]'; |
| }-*/; |
| |
| public native boolean isIndexed() /*-{ |
| return Object.prototype.toString.call(this) == '[object Array]'; |
| }-*/; |
| |
| public boolean isKeyed() { |
| return this != NULL && !isString() && !isIndexed() && !isFunction(); |
| } |
| |
| public native boolean isNull(int index) /*-{ |
| return this[index] == null; |
| }-*/; |
| |
| public native boolean isNull(String key) /*-{ |
| return this[key] == null; |
| }-*/; |
| |
| public native boolean isNumber() /*-{ |
| return this && typeof(this.valueOf()) === 'number'; |
| }-*/; |
| |
| public native boolean isReified(String key) /*-{ |
| return !!(this.__reified && this.__reified.hasOwnProperty(':' + key)); |
| }-*/; |
| |
| /** |
| * Returns whether or not the current object is a string-carrier. |
| */ |
| public native boolean isString() /*-{ |
| return this && this.__s != null; |
| }-*/; |
| |
| public native boolean isUndefined(String key) /*-{ |
| return this[key] === undefined; |
| }-*/; |
| |
| @Override |
| public native void removeReified(String key) /*-{ |
| if (this.__reified) { |
| delete this.__reified[':' + key]; |
| } |
| }-*/; |
| |
| public native void setReified(String key, Object object) /*-{ |
| // Use a function object so native JSON.stringify will ignore |
| (this.__reified || (this.__reified = function() { |
| }))[':' + key] = object; |
| }-*/; |
| |
| public native void setSize(int size) /*-{ |
| this.length = size; |
| }-*/; |
| |
| public native int size() /*-{ |
| return this.length; |
| }-*/; |
| |
| private native void assign0(Splittable parent, int index, Splittable value) /*-{ |
| parent[index] = value; |
| }-*/; |
| |
| private native void assign0(Splittable parent, int index, String value) /*-{ |
| parent[index] = value; |
| }-*/; |
| |
| private native void assign0(Splittable parent, String index, Splittable value) /*-{ |
| parent[index] = value; |
| }-*/; |
| |
| private native void assign0(Splittable parent, String index, String value) /*-{ |
| parent[index] = value; |
| }-*/; |
| |
| private native void getPropertyKeys0(List<String> list) /*-{ |
| for (key in this) { |
| if (this.hasOwnProperty(key)) { |
| list.@java.util.List::add(Ljava/lang/Object;)(key); |
| } |
| } |
| }-*/; |
| |
| private native JsoSplittable getRaw(int index) /*-{ |
| _ = this[index]; |
| if (_ == null) { |
| return null; |
| } |
| if (@com.google.web.bindery.autobean.gwt.client.impl.JsoSplittable::isUnwrappedString(*)(_)) { |
| return @com.google.web.bindery.autobean.gwt.client.impl.JsoSplittable::create(Ljava/lang/String;)(_); |
| } |
| return Object(_); |
| }-*/; |
| |
| private native JsoSplittable getRaw(String index) /*-{ |
| _ = this[index]; |
| if (_ == null) { |
| return null; |
| } |
| if (@com.google.web.bindery.autobean.gwt.client.impl.JsoSplittable::isUnwrappedString(*)(_)) { |
| return @com.google.web.bindery.autobean.gwt.client.impl.JsoSplittable::create(Ljava/lang/String;)(_); |
| } |
| return Object(_); |
| }-*/; |
| |
| /** |
| * The test for {@code $H} removes the key in the emitted JSON, however making |
| * a similar test for {@code __reified} causes the key to be emitted with an |
| * explicit {@code null} value. |
| */ |
| private native String stringifyFast() /*-{ |
| return $wnd.JSON.stringify(this, function(key, value) { |
| if (key == "$H") { |
| return; |
| } |
| return value; |
| }); |
| }-*/; |
| |
| private String stringifySlow() { |
| StringBuilder sb = new StringBuilder(); |
| stringifySlow(sb); |
| return sb.toString(); |
| } |
| |
| private void stringifySlow(StringBuilder sb) { |
| if (this == NULL) { |
| sb.append("null"); |
| return; |
| } |
| if (isBoolean()) { |
| sb.append(asBoolean()); |
| return; |
| } |
| if (isNumber()) { |
| sb.append(asNumber()); |
| return; |
| } |
| if (isString()) { |
| sb.append(JsonUtils.escapeValue(asString())); |
| return; |
| } |
| if (isIndexed()) { |
| sb.append("["); |
| for (int i = 0, j = size(); i < j; i++) { |
| if (i > 0) { |
| sb.append(","); |
| } |
| get(i).stringifySlow(sb); |
| } |
| sb.append("]"); |
| return; |
| } |
| |
| sb.append("{"); |
| boolean needsComma = false; |
| for (String key : getPropertyKeys()) { |
| if (needsComma) { |
| sb.append(","); |
| } else { |
| needsComma = true; |
| } |
| JsoSplittable value = get(key); |
| if (!value.isFunction()) { |
| if ("$H".equals(key)) { |
| // Ignore hashcode |
| continue; |
| } |
| sb.append(JsonUtils.escapeValue(key)); |
| sb.append(":"); |
| value.stringifySlow(sb); |
| } |
| } |
| sb.append("}"); |
| } |
| } |