Re-apply the AutoBean JSO lazy reification patch.
Change type-detection and fast serialization path in JsoSplittable to work
around browser variations.
http://gwt-code-reviews.appspot.com/1407802/
Patch by: bobv
Review by: rice, cromwellian
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9964 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java b/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
index 11f6998..5eb9763 100644
--- a/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
+++ b/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
@@ -535,7 +535,7 @@
*/
JClassType implementingType = typeOracle.getSingleJsoImpl(intfMethod.getEnclosingType());
- if (implementingType == null) {
+ if (implementingType == null || implementingType.isAnnotationPresent(GwtScriptOnly.class)) {
/*
* This means that there is no concrete implementation of the
* interface by a JSO. Any implementation that might be created by a
diff --git a/tools/api-checker/config/gwt22_23userApi.conf b/tools/api-checker/config/gwt22_23userApi.conf
index 88cb8f5..7041d0b 100644
--- a/tools/api-checker/config/gwt22_23userApi.conf
+++ b/tools/api-checker/config/gwt22_23userApi.conf
@@ -16,7 +16,9 @@
:**/tools/**\
:com/google/gwt/regexp/shared/**\
:com/google/gwt/autobean/**/impl/**\
+:com/google/gwt/autobean/shared/ValueCodex.java\
:com/google/gwt/autobean/shared/ValueCodexHelper.java\
+:com/google/gwt/autobean/shared/AutoBeanCodex.java\
:com/google/gwt/core/client/impl/WeakMapping.java\
:com/google/gwt/core/ext/**\
:com/google/gwt/dev/*.java\
diff --git a/user/src/com/google/gwt/autobean/client/impl/AbstractAutoBeanFactory.java b/user/src/com/google/gwt/autobean/client/impl/AbstractAutoBeanFactory.java
index 0310304..f8f9e3e 100644
--- a/user/src/com/google/gwt/autobean/client/impl/AbstractAutoBeanFactory.java
+++ b/user/src/com/google/gwt/autobean/client/impl/AbstractAutoBeanFactory.java
@@ -46,7 +46,7 @@
/**
* EnumMap support.
*/
- public <E extends Enum<E>> E getEnum(Class<E> clazz, String token) {
+ public <E extends Enum<?>> E getEnum(Class<E> clazz, String token) {
maybeInitializeEnumMap();
List<Enum<?>> list = stringsToEnumsMap.get(token);
if (list == null) {
diff --git a/user/src/com/google/gwt/autobean/client/impl/ClientPropertyContext.java b/user/src/com/google/gwt/autobean/client/impl/ClientPropertyContext.java
index 73a077a..620c9ec 100644
--- a/user/src/com/google/gwt/autobean/client/impl/ClientPropertyContext.java
+++ b/user/src/com/google/gwt/autobean/client/impl/ClientPropertyContext.java
@@ -18,6 +18,8 @@
import com.google.gwt.autobean.shared.AutoBeanVisitor.CollectionPropertyContext;
import com.google.gwt.autobean.shared.AutoBeanVisitor.MapPropertyContext;
import com.google.gwt.autobean.shared.AutoBeanVisitor.ParameterizationVisitor;
+import com.google.gwt.autobean.shared.AutoBeanVisitor.PropertyContext;
+import com.google.gwt.autobean.shared.impl.AbstractAutoBean;
import com.google.gwt.core.client.JavaScriptObject;
import java.util.List;
@@ -27,18 +29,20 @@
/**
* Provides base methods for generated implementations of PropertyContext.
*/
-public final class ClientPropertyContext implements CollectionPropertyContext, MapPropertyContext {
+public final class ClientPropertyContext implements PropertyContext, CollectionPropertyContext,
+ MapPropertyContext {
/**
* A reference to an instance setter method.
*/
public static final class Setter extends JavaScriptObject {
/**
- * Create a trivial Setter that calls {@link Map#put(Object, Object)}.
+ * Create a trivial Setter that calls {@link AbstractAutoBean#setProperty()}
+ * .
*/
- public static native Setter mapSetter(Map<String, Object> values, String key) /*-{
+ public static native Setter beanSetter(AbstractAutoBean<?> bean, String key) /*-{
return function(value) {
- values.@java.util.Map::put(*)(key, value);
+ bean.@com.google.gwt.autobean.shared.impl.AbstractAutoBean::setProperty(*)(key, value);
};
}-*/;
diff --git a/user/src/com/google/gwt/autobean/client/impl/JsoSplittable.java b/user/src/com/google/gwt/autobean/client/impl/JsoSplittable.java
index 6a86993..008f3f9 100644
--- a/user/src/com/google/gwt/autobean/client/impl/JsoSplittable.java
+++ b/user/src/com/google/gwt/autobean/client/impl/JsoSplittable.java
@@ -16,8 +16,11 @@
package com.google.gwt.autobean.client.impl;
import com.google.gwt.autobean.shared.Splittable;
+import com.google.gwt.autobean.shared.impl.HasSplittable;
import com.google.gwt.autobean.shared.impl.StringQuoter;
+import com.google.gwt.core.client.GwtScriptOnly;
import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsonUtils;
import java.util.ArrayList;
import java.util.Collections;
@@ -25,91 +28,129 @@
/**
* 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.
*/
-public final class JsoSplittable extends JavaScriptObject implements Splittable {
+@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 native JsoSplittable nullValue() /*-{
+ return null;
+ }-*/;
+
+ private static native Splittable create0(boolean object) /*-{
+ return Boolean(object);
+ }-*/;
+
+ private static native Splittable create0(double object) /*-{
+ return Number(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();
+ }
+
/**
- * This type is used because we can't treat Strings as JSOs.
+ * 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.
*/
- public static class StringSplittable implements Splittable {
- private final String value;
-
- public StringSplittable(String value) {
- this.value = value;
- }
-
- public String asString() {
- return value;
- }
-
- public Splittable get(int index) {
- throw new UnsupportedOperationException();
- }
-
- public Splittable get(String key) {
- throw new UnsupportedOperationException();
- }
-
- public String getPayload() {
- return StringQuoter.quote(value);
- }
-
- public List<String> getPropertyKeys() {
- return Collections.emptyList();
- }
-
- public boolean isIndexed() {
- return false;
- }
-
- public boolean isKeyed() {
- return false;
- }
-
- public boolean isNull(int index) {
- throw new UnsupportedOperationException();
- }
-
- public boolean isNull(String key) {
- throw new UnsupportedOperationException();
- }
-
- public boolean isString() {
- return true;
- }
-
- public int size() {
- return 0;
- }
- }
-
- public static Splittable create(Object object) {
- if (object instanceof String) {
- return new StringSplittable((String) object);
- }
- return create0(object);
- }
-
- private static native Splittable create0(Object object) /*-{
- return object;
+ private static native boolean stringifyFastSupported0() /*-{
+ return $wnd.JSON && $wnd.JSON.stringify && $wnd.JSON.stringify({
+ b : function() {
+ }
+ }) == '{}';
}-*/;
protected JsoSplittable() {
+ };
+
+ public native boolean asBoolean() /*-{
+ return this == true;
+ }-*/;
+
+ public native double asNumber() /*-{
+ return Number(this);
+ }-*/;
+
+ 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 String(this);
+ return this.__s;
}-*/;
- public Splittable get(int index) {
- return create(get0(index));
+ public Splittable deepCopy() {
+ return StringQuoter.split(getPayload());
}
- public Splittable get(String key) {
- return create(get0(key));
+ public JsoSplittable get(int index) {
+ return getRaw(index);
+ }
+
+ public JsoSplittable get(String key) {
+ return getRaw(key);
}
public String getPayload() {
- throw new UnsupportedOperationException("Cannot convert JsoSplittable to payload");
+ if (isString()) {
+ return JsonUtils.escapeValue(asString());
+ }
+ if (stringifyFastSupported()) {
+ return stringifyFast();
+ }
+ return stringifySlow();
}
public List<String> getPropertyKeys() {
@@ -118,12 +159,28 @@
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 Object.prototype.toString.call(this) == '[object Boolean]';
+ }-*/;
+
+ public native boolean isFunction() /*-{
+ return Object.prototype.toString.call(this) == '[object Function]';
+ }-*/;
+
public native boolean isIndexed() /*-{
- return this instanceof Array;
+ return Object.prototype.toString.call(this) == '[object Array]';
}-*/;
public boolean isKeyed() {
- return !isString() && !isIndexed();
+ return this != NULL && !isString() && !isIndexed() && !isFunction();
}
public native boolean isNull(int index) /*-{
@@ -134,20 +191,53 @@
return this[key] == null;
}-*/;
+ public native boolean isNumber() /*-{
+ return Object.prototype.toString.call(this) == '[object 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 typeof (this) == 'string' || this instanceof String;
+ return this && this.__s != null;
+ }-*/;
+
+ public native boolean isUndefined(String key) /*-{
+ return this[key] === undefined;
+ }-*/;
+
+ 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 Object get0(int index) /*-{
- return Object(this[index]);
+ private native void assign0(Splittable parent, int index, Splittable value) /*-{
+ parent[index] = value;
}-*/;
- private native Object get0(String key) /*-{
- return Object(this[key]);
+ 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) /*-{
@@ -157,4 +247,97 @@
}
}
}-*/;
+
+ private native JsoSplittable getRaw(int index) /*-{
+ _ = this[index];
+ if (_ == null) {
+ return null;
+ }
+ if (@com.google.gwt.autobean.client.impl.JsoSplittable::isUnwrappedString(*)(_)) {
+ return @com.google.gwt.autobean.client.impl.JsoSplittable::create(Ljava/lang/String;)(_);
+ }
+ return Object(_);
+ }-*/;
+
+ private native JsoSplittable getRaw(String index) /*-{
+ _ = this[index];
+ if (_ == null) {
+ return null;
+ }
+ if (@com.google.gwt.autobean.client.impl.JsoSplittable::isUnwrappedString(*)(_)) {
+ return @com.google.gwt.autobean.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("}");
+ }
}
diff --git a/user/src/com/google/gwt/autobean/rebind/AutoBeanFactoryGenerator.java b/user/src/com/google/gwt/autobean/rebind/AutoBeanFactoryGenerator.java
index a07758d..1ec4f78 100644
--- a/user/src/com/google/gwt/autobean/rebind/AutoBeanFactoryGenerator.java
+++ b/user/src/com/google/gwt/autobean/rebind/AutoBeanFactoryGenerator.java
@@ -27,6 +27,7 @@
import com.google.gwt.autobean.shared.AutoBeanFactory;
import com.google.gwt.autobean.shared.AutoBeanUtils;
import com.google.gwt.autobean.shared.AutoBeanVisitor;
+import com.google.gwt.autobean.shared.Splittable;
import com.google.gwt.autobean.shared.impl.AbstractAutoBean;
import com.google.gwt.autobean.shared.impl.AbstractAutoBean.OneShotContext;
import com.google.gwt.core.client.JavaScriptObject;
@@ -185,28 +186,16 @@
AutoBeanFactory.class.getCanonicalName());
}
- // Clone constructor
- // public FooIntfAutoBean(FooIntfoAutoBean toClone, boolean deepClone) {
- sw.println("public %1$s(%1$s toClone, boolean deep) {", type.getSimpleSourceName());
- sw.indentln("super(toClone, deep);");
- sw.println("}");
-
// Wrapping constructor
// public FooIntfAutoBean(AutoBeanFactory factory, FooIntfo wrapped) {
sw.println("public %s(%s factory, %s wrapped) {", type.getSimpleSourceName(),
AutoBeanFactory.class.getCanonicalName(), type.getPeerType().getQualifiedSourceName());
- sw.indentln("super(factory, wrapped);");
+ sw.indentln("super(wrapped, factory);");
sw.println("}");
// public FooIntf as() {return shim;}
sw.println("public %s as() {return shim;}", type.getPeerType().getQualifiedSourceName());
- // public FooIntfAutoBean clone(boolean deep) {
- sw.println("public %s clone(boolean deep) {", type.getQualifiedSourceName());
- // return new FooIntfAutoBean(this, deep);
- sw.indentln("return new %s(this, deep);", type.getSimpleSourceName());
- sw.println("}");
-
// public Class<Intf> getType() {return Intf.class;}
sw.println("public Class<%1$s> getType() {return %1$s.class;}", ModelUtils.ensureBaseType(
type.getPeerType()).getQualifiedSourceName());
@@ -230,45 +219,49 @@
// return new FooIntf() {
sw.println("return new %s() {", type.getPeerType().getQualifiedSourceName());
sw.indent();
+ sw.println("private final %s data = %s.this.data;", Splittable.class.getCanonicalName(), type
+ .getQualifiedSourceName());
for (AutoBeanMethod method : type.getMethods()) {
JMethod jmethod = method.getMethod();
+ JType returnType = jmethod.getReturnType();
sw.println("public %s {", getBaseMethodDeclaration(jmethod));
sw.indent();
switch (method.getAction()) {
case GET: {
- // Must handle de-boxing primitive types
- JPrimitiveType primitive = jmethod.getReturnType().isPrimitive();
- if (primitive != null) {
- // Object toReturn = values.get("foo");
- sw.println("Object toReturn = values.get(\"%s\");", method.getPropertyName());
- sw.println("if (toReturn == null) {");
- // return 0;
- sw.indentln("return %s;", primitive.getUninitializedFieldExpression());
- sw.println("} else {");
- // return (BoxedType) toReturn;
- sw.indentln("return (%s) toReturn;", primitive.getQualifiedBoxedSourceName());
- sw.println("}");
+ String castType;
+ if (returnType.isPrimitive() != null) {
+ castType = returnType.isPrimitive().getQualifiedBoxedSourceName();
+ // Boolean toReturn = getOrReify("foo");
+ sw.println("%s toReturn = getOrReify(\"%s\");", castType, method.getPropertyName());
+ // return toReturn == null ? false : toReturn;
+ sw.println("return toReturn == null ? %s : toReturn;", returnType.isPrimitive()
+ .getUninitializedFieldExpression());
+ } else if (returnType.equals(context.getTypeOracle().findType(
+ Splittable.class.getCanonicalName()))) {
+ sw.println("return data.isNull(\"%1$s\") ? null : data.get(\"%1$s\");", method
+ .getPropertyName());
} else {
- // return (ReturnType) values.get(\"foo\");
- sw.println("return (%s) values.get(\"%s\");", ModelUtils
- .getQualifiedBaseSourceName(jmethod.getReturnType()), method.getPropertyName());
+ // return (ReturnType) values.getOrReify(\"foo\");
+ castType = ModelUtils.getQualifiedBaseSourceName(returnType);
+ sw.println("return (%s) getOrReify(\"%s\");", castType, method.getPropertyName());
}
}
break;
case SET:
- case SET_BUILDER:
- // values.put("foo", parameter);
- sw.println("values.put(\"%s\", %s);", method.getPropertyName(),
- jmethod.getParameters()[0].getName());
+ case SET_BUILDER: {
+ JParameter param = jmethod.getParameters()[0];
+ // setProperty("foo", parameter);
+ sw.println("setProperty(\"%s\", %s);", method.getPropertyName(), param.getName());
if (JBeanMethod.SET_BUILDER.equals(method.getAction())) {
sw.println("return this;");
}
break;
+ }
case CALL:
// return com.example.Owner.staticMethod(Outer.this, param,
// param);
JMethod staticImpl = method.getStaticImpl();
- if (!jmethod.getReturnType().equals(JPrimitiveType.VOID)) {
+ if (!returnType.equals(JPrimitiveType.VOID)) {
sw.print("return ");
}
sw.print("%s.%s(%s.this", staticImpl.getEnclosingType().getQualifiedSourceName(),
@@ -506,19 +499,15 @@
sw.println("public %s {", getBaseMethodDeclaration(jmethod));
sw.indent();
- // Use explicit enclosing this reference to avoid method conflicts
- sw.println("%s.this.checkWrapped();", type.getSimpleSourceName());
-
switch (method.getAction()) {
case GET:
/*
* The getter call will ensure that any non-value return type is
* definitely wrapped by an AutoBean instance.
*/
- // Foo toReturn=FooAutoBean.this.get("getFoo", getWrapped().getFoo());
- sw.println("%s toReturn = %3$s.this.get(\"%2$s\", getWrapped().%2$s());", ModelUtils
- .getQualifiedBaseSourceName(jmethod.getReturnType()), methodName, type
- .getSimpleSourceName());
+ sw.println("%s toReturn = %s.this.getWrapped().%s();", ModelUtils
+ .getQualifiedBaseSourceName(jmethod.getReturnType()), type.getSimpleSourceName(),
+ methodName);
// Non-value types might need to be wrapped
writeReturnWrapper(sw, type, method);
@@ -526,7 +515,6 @@
break;
case SET:
case SET_BUILDER:
- sw.println("%s.this.checkFrozen();", type.getSimpleSourceName());
// getWrapped().setFoo(foo);
sw.println("%s.this.getWrapped().%s(%s);", type.getSimpleSourceName(), methodName,
parameters[0].getName());
@@ -596,7 +584,6 @@
sw.println("%s propertyContext;", ClientPropertyContext.class.getCanonicalName());
// Local variable ref cleans up emitted js
sw.println("%1$s as = as();", type.getPeerType().getQualifiedSourceName());
- sw.println("%s<String, Object> values = this.values;", Map.class.getCanonicalName());
for (AutoBeanMethod method : type.getMethods()) {
if (!method.getAction().equals(JBeanMethod.GET)) {
@@ -662,9 +649,9 @@
referencedSetters.add(setter);
} else {
// Create a function that will update the values map
- // CPContext.mapSetter(values, "foo");
- sw.println("%s.mapSetter(values, \"%s\"),", ClientPropertyContext.Setter.class
- .getCanonicalName(), method.getPropertyName());
+ // CPContext.beanSetter(FooBeanImpl.this, "foo");
+ sw.println("%s.beanSetter(%s.this, \"%s\"),", ClientPropertyContext.Setter.class
+ .getCanonicalName(), type.getSimpleSourceName(), method.getPropertyName());
}
}
if (typeList.size() == 1) {
diff --git a/user/src/com/google/gwt/autobean/server/impl/BeanMethod.java b/user/src/com/google/gwt/autobean/server/impl/BeanMethod.java
index 132393f..32b4a1b 100644
--- a/user/src/com/google/gwt/autobean/server/impl/BeanMethod.java
+++ b/user/src/com/google/gwt/autobean/server/impl/BeanMethod.java
@@ -16,6 +16,7 @@
package com.google.gwt.autobean.server.impl;
import com.google.gwt.autobean.shared.AutoBean;
+import com.google.gwt.autobean.shared.AutoBean.PropertyName;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
@@ -38,8 +39,7 @@
}
@Override
- Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args)
- throws Throwable {
+ Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args) throws Throwable {
if (CALL.matches(handler, method)) {
return CALL.invoke(handler, method, args);
}
@@ -58,7 +58,7 @@
@Override
public String inferName(Method method) {
String name = method.getName();
- if (name.startsWith(IS_PREFIX)) {
+ if (name.startsWith(IS_PREFIX) && !method.isAnnotationPresent(PropertyName.class)) {
Class<?> returnType = method.getReturnType();
if (Boolean.TYPE.equals(returnType) || Boolean.class.equals(returnType)) {
return decapitalize(name.substring(2));
@@ -70,7 +70,7 @@
@Override
Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args) {
String propertyName = inferName(method);
- Object toReturn = handler.getBean().getValues().get(propertyName);
+ Object toReturn = handler.getBean().getOrReify(propertyName);
if (toReturn == null && method.getReturnType().isPrimitive()) {
toReturn = TypeUtils.getDefaultPrimitiveValue(method.getReturnType());
}
@@ -80,15 +80,14 @@
@Override
boolean matches(SimpleBeanHandler<?> handler, Method method) {
Class<?> returnType = method.getReturnType();
- if (method.getParameterTypes().length != 0
- || Void.TYPE.equals(returnType)) {
+ if (method.getParameterTypes().length != 0 || Void.TYPE.equals(returnType)) {
return false;
}
String name = method.getName();
if (Boolean.TYPE.equals(returnType) || Boolean.class.equals(returnType)) {
- if (name.startsWith(IS_PREFIX) && name.length() > 2
- || name.startsWith(HAS_PREFIX) && name.length() > 3) {
+ if (name.startsWith(IS_PREFIX) && name.length() > 2 || name.startsWith(HAS_PREFIX)
+ && name.length() > 3) {
return true;
}
}
@@ -101,7 +100,7 @@
SET {
@Override
Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args) {
- handler.getBean().getValues().put(inferName(method), args[0]);
+ handler.getBean().setProperty(inferName(method), args[0]);
return null;
}
@@ -109,8 +108,7 @@
boolean matches(SimpleBeanHandler<?> handler, Method method) {
String name = method.getName();
return name.startsWith(SET_PREFIX) && name.length() > 3
- && method.getParameterTypes().length == 1
- && method.getReturnType().equals(Void.TYPE);
+ && method.getParameterTypes().length == 1 && method.getReturnType().equals(Void.TYPE);
}
},
/**
@@ -121,15 +119,15 @@
SET_BUILDER {
@Override
Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args) {
- handler.getBean().getValues().put(inferName(method), args[0]);
- return handler.getBean().as();
+ ProxyAutoBean<?> bean = handler.getBean();
+ bean.setProperty(inferName(method), args[0]);
+ return bean.as();
}
@Override
boolean matches(SimpleBeanHandler<?> handler, Method method) {
String name = method.getName();
- return name.startsWith(SET_PREFIX)
- && name.length() > 3
+ return name.startsWith(SET_PREFIX) && name.length() > 3
&& method.getParameterTypes().length == 1
&& method.getReturnType().isAssignableFrom(method.getDeclaringClass());
}
@@ -144,8 +142,7 @@
}
@Override
- Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args)
- throws Throwable {
+ Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args) throws Throwable {
if (args == null) {
args = EMPTY_OBJECT;
}
@@ -190,8 +187,9 @@
continue;
}
// Check the AutoBean parameterization of the 0th argument
- Class<?> foundAutoBean = TypeUtils.ensureBaseType(TypeUtils.getSingleParameterization(
- AutoBean.class, found.getGenericParameterTypes()[0]));
+ Class<?> foundAutoBean =
+ TypeUtils.ensureBaseType(TypeUtils.getSingleParameterization(AutoBean.class, found
+ .getGenericParameterTypes()[0]));
if (!foundAutoBean.isAssignableFrom(autoBeanType)) {
continue;
}
@@ -205,8 +203,8 @@
}
/**
- * Private equivalent of Introspector.decapitalize(String)
- * since java.beans.Introspector is not available in Android 2.2.
+ * Private equivalent of Introspector.decapitalize(String) since
+ * java.beans.Introspector is not available in Android 2.2.
*/
private static String decapitalize(String name) {
if (name == null) {
@@ -223,6 +221,10 @@
}
public String inferName(Method method) {
+ PropertyName prop = method.getAnnotation(PropertyName.class);
+ if (prop != null) {
+ return prop.value();
+ }
return decapitalize(method.getName().substring(3));
}
@@ -236,11 +238,11 @@
/**
* Invoke the method.
*/
- abstract Object invoke(SimpleBeanHandler<?> handler, Method method,
- Object[] args) throws Throwable;
+ abstract Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args)
+ throws Throwable;
/**
* Determine if the method maches the given type.
*/
abstract boolean matches(SimpleBeanHandler<?> handler, Method method);
-}
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/autobean/server/impl/BeanPropertyContext.java b/user/src/com/google/gwt/autobean/server/impl/BeanPropertyContext.java
index f17e879..c1f4be9 100644
--- a/user/src/com/google/gwt/autobean/server/impl/BeanPropertyContext.java
+++ b/user/src/com/google/gwt/autobean/server/impl/BeanPropertyContext.java
@@ -16,20 +16,19 @@
package com.google.gwt.autobean.server.impl;
import java.lang.reflect.Method;
-import java.util.Map;
/**
* A property context that allows setters to be called on a simple peer,
* regardless of whether or not the interface actually has a setter.
*/
class BeanPropertyContext extends MethodPropertyContext {
+ private final ProxyAutoBean<?> bean;
private final String propertyName;
- private final Map<String, Object> map;
public BeanPropertyContext(ProxyAutoBean<?> bean, Method getter) {
super(getter);
+ this.bean = bean;
propertyName = BeanMethod.GET.inferName(getter);
- map = bean.getPropertyMap();
}
@Override
@@ -42,6 +41,6 @@
Class<?> maybeAutobox = TypeUtils.maybeAutobox(getType());
assert value == null || maybeAutobox.isInstance(value) : value.getClass().getCanonicalName()
+ " is not assignable to " + maybeAutobox.getCanonicalName();
- map.put(propertyName, maybeAutobox.cast(value));
+ bean.setProperty(propertyName, maybeAutobox.cast(value));
}
}
diff --git a/user/src/com/google/gwt/autobean/server/impl/JsonSplittable.java b/user/src/com/google/gwt/autobean/server/impl/JsonSplittable.java
index 51e7902..04b77a6 100644
--- a/user/src/com/google/gwt/autobean/server/impl/JsonSplittable.java
+++ b/user/src/com/google/gwt/autobean/server/impl/JsonSplittable.java
@@ -16,21 +16,39 @@
package com.google.gwt.autobean.server.impl;
import com.google.gwt.autobean.shared.Splittable;
+import com.google.gwt.autobean.shared.impl.HasSplittable;
import com.google.gwt.autobean.shared.impl.StringQuoter;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.Collections;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
+import java.util.WeakHashMap;
/**
* Uses the org.json packages to slice and dice request payloads.
*/
-public class JsonSplittable implements Splittable {
+public class JsonSplittable implements Splittable, HasSplittable {
+
+ /**
+ * Ensures that the same JsonSplittable will be returned for a given backing
+ * JSONObject.
+ */
+ private static final Map<Object, Reference<JsonSplittable>> canonical =
+ new WeakHashMap<Object, Reference<JsonSplittable>>();
+
+ public static JsonSplittable create() {
+ return new JsonSplittable(new JSONObject());
+ }
+
public static Splittable create(String payload) {
try {
switch (payload.charAt(0)) {
@@ -39,8 +57,8 @@
case '[':
return new JsonSplittable(new JSONArray(payload));
case '"':
- return new JsonSplittable(
- new JSONArray("[" + payload + "]").getString(0));
+ return new JsonSplittable(new JSONArray("[" + payload + "]").getString(0));
+ case '-':
case '0':
case '1':
case '2':
@@ -51,19 +69,31 @@
case '7':
case '8':
case '9':
- return new JsonSplittable(payload);
+ return new JsonSplittable(Double.parseDouble(payload));
+ case 't':
+ case 'f':
+ return new JsonSplittable(Boolean.parseBoolean(payload));
+ case 'n':
+ return null;
default:
- throw new RuntimeException("Could not parse payload: payload[0] = "
- + payload.charAt(0));
+ throw new RuntimeException("Could not parse payload: payload[0] = " + payload.charAt(0));
}
} catch (JSONException e) {
throw new RuntimeException("Could not parse payload", e);
}
}
+ public static Splittable createIndexed() {
+ return new JsonSplittable(new JSONArray());
+ }
+
+ public static Splittable createNull() {
+ return new JsonSplittable();
+ }
+
/**
- * Private equivalent of org.json.JSONObject.getNames(JSONObject)
- * since that method is not available in Android 2.2.
+ * Private equivalent of org.json.JSONObject.getNames(JSONObject) since that
+ * method is not available in Android 2.2. Used to represent a null value.
*/
private static String[] getNames(JSONObject json) {
int length = json.length();
@@ -79,20 +109,38 @@
return names;
}
- private final JSONArray array;
- private final JSONObject obj;
- private final String string;
+ private JSONArray array;
+ private Boolean bool;
+ /**
+ * Used to represent a null value.
+ */
+ private boolean isNull;
+ private Double number;
+ private JSONObject obj;
+ private String string;
+ private final Map<String, Object> reified = new HashMap<String, Object>();
+
+ /**
+ * Constructor for a null value.
+ */
+ private JsonSplittable() {
+ isNull = true;
+ }
+
+ private JsonSplittable(boolean value) {
+ this.bool = value;
+ }
+
+ private JsonSplittable(double value) {
+ this.number = value;
+ }
private JsonSplittable(JSONArray array) {
this.array = array;
- this.obj = null;
- this.string = null;
}
private JsonSplittable(JSONObject obj) {
- this.array = null;
this.obj = obj;
- this.string = null;
}
private JsonSplittable(String string) {
@@ -101,10 +149,38 @@
this.string = string;
}
+ public boolean asBoolean() {
+ return bool;
+ }
+
+ public double asNumber() {
+ return number;
+ }
+
+ public void assign(Splittable parent, int index) {
+ try {
+ ((JsonSplittable) parent).array.put(index, value());
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void assign(Splittable parent, String propertyName) {
+ try {
+ ((JsonSplittable) parent).obj.put(propertyName, value());
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
public String asString() {
return string;
}
+ public Splittable deepCopy() {
+ return create(getPayload());
+ }
+
public Splittable get(int index) {
try {
return makeSplittable(array.get(index));
@@ -122,6 +198,9 @@
}
public String getPayload() {
+ if (isNull) {
+ return "null";
+ }
if (obj != null) {
return obj.toString();
}
@@ -131,6 +210,12 @@
if (string != null) {
return StringQuoter.quote(string);
}
+ if (number != null) {
+ return String.valueOf(number);
+ }
+ if (bool != null) {
+ return String.valueOf(bool);
+ }
throw new RuntimeException("No data in this JsonSplittable");
}
@@ -143,6 +228,18 @@
}
}
+ public Object getReified(String key) {
+ return reified.get(key);
+ }
+
+ public Splittable getSplittable() {
+ return this;
+ }
+
+ public boolean isBoolean() {
+ return bool != null;
+ }
+
public boolean isIndexed() {
return array != null;
}
@@ -160,10 +257,39 @@
return !obj.has(key) || obj.isNull(key);
}
+ public boolean isNumber() {
+ return number != null;
+ }
+
+ public boolean isReified(String key) {
+ return reified.containsKey(key);
+ }
+
public boolean isString() {
return string != null;
}
+ public boolean isUndefined(String key) {
+ return !obj.has(key);
+ }
+
+ public void setReified(String key, Object object) {
+ reified.put(key, object);
+ }
+
+ public void setSize(int size) {
+ // This is terrible, but there's no API support for resizing or splicing
+ JSONArray newArray = new JSONArray();
+ for (int i = 0; i < size; i++) {
+ try {
+ newArray.put(i, array.get(i));
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ array = newArray;
+ }
+
public int size() {
return array.length();
}
@@ -173,23 +299,53 @@
*/
@Override
public String toString() {
- if (obj != null) {
- return obj.toString();
- } else if (array != null) {
- return array.toString();
- } else if (string != null) {
- return string;
- }
- return "<Uninitialized>";
+ return getPayload();
}
- private JsonSplittable makeSplittable(Object object) {
- if (object instanceof JSONObject) {
- return new JsonSplittable((JSONObject) object);
+ private synchronized JsonSplittable makeSplittable(Object object) {
+ if (JSONObject.NULL.equals(object)) {
+ return null;
}
- if (object instanceof JSONArray) {
- return new JsonSplittable((JSONArray) object);
+ Reference<JsonSplittable> ref = canonical.get(object);
+ JsonSplittable seen = ref == null ? null : ref.get();
+ if (seen == null) {
+ if (object instanceof JSONObject) {
+ seen = new JsonSplittable((JSONObject) object);
+ } else if (object instanceof JSONArray) {
+ seen = new JsonSplittable((JSONArray) object);
+ } else if (object instanceof String) {
+ seen = new JsonSplittable(object.toString());
+ } else if (object instanceof Number) {
+ seen = new JsonSplittable(((Number) object).doubleValue());
+ } else if (object instanceof Boolean) {
+ seen = new JsonSplittable((Boolean) object);
+ } else {
+ throw new RuntimeException("Unhandled type " + object.getClass());
+ }
+ canonical.put(object, new WeakReference<JsonSplittable>(seen));
}
- return new JsonSplittable(object.toString());
+ return seen;
}
-}
+
+ private Object value() {
+ if (isNull) {
+ return null;
+ }
+ if (obj != null) {
+ return obj;
+ }
+ if (array != null) {
+ return array;
+ }
+ if (string != null) {
+ return string;
+ }
+ if (number != null) {
+ return number;
+ }
+ if (bool != null) {
+ return bool;
+ }
+ throw new RuntimeException("No data");
+ }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/autobean/server/impl/ProxyAutoBean.java b/user/src/com/google/gwt/autobean/server/impl/ProxyAutoBean.java
index 4f8e3be..de65b5e 100644
--- a/user/src/com/google/gwt/autobean/server/impl/ProxyAutoBean.java
+++ b/user/src/com/google/gwt/autobean/server/impl/ProxyAutoBean.java
@@ -66,15 +66,14 @@
Class<?>... extraInterfaces) {
Class<?>[] intfs;
if (extraInterfaces == null) {
- intfs = new Class<?>[] {intf};
+ intfs = new Class<?>[]{intf};
} else {
intfs = new Class<?>[extraInterfaces.length + 1];
intfs[0] = intf;
System.arraycopy(extraInterfaces, 0, intfs, 1, extraInterfaces.length);
}
- return intf.cast(Proxy.newProxyInstance(intf.getClassLoader(), intfs,
- handler));
+ return intf.cast(Proxy.newProxyInstance(intf.getClassLoader(), intfs, handler));
}
private static Data calculateData(Class<?> beanType) {
@@ -117,13 +116,11 @@
private final Class<T> beanType;
private final Configuration configuration;
private final Data data;
-
private final T shim;
// These constructors mirror the generated constructors.
@SuppressWarnings("unchecked")
- public ProxyAutoBean(AutoBeanFactory factory, Class<?> beanType,
- Configuration configuration) {
+ public ProxyAutoBean(AutoBeanFactory factory, Class<?> beanType, Configuration configuration) {
super(factory);
this.beanType = (Class<T>) beanType;
this.configuration = configuration;
@@ -132,36 +129,20 @@
}
@SuppressWarnings("unchecked")
- public ProxyAutoBean(AutoBeanFactory factory, Class<?> beanType,
- Configuration configuration, T toWrap) {
- super(factory, toWrap);
- if (Proxy.isProxyClass(toWrap.getClass())) {
- System.out.println("blah");
- }
+ public ProxyAutoBean(AutoBeanFactory factory, Class<?> beanType, Configuration configuration,
+ T toWrap) {
+ super(toWrap, factory);
this.beanType = (Class<T>) beanType;
this.configuration = configuration;
this.data = calculateData(beanType);
this.shim = createShim();
}
- private ProxyAutoBean(ProxyAutoBean<T> toClone, boolean deep) {
- super(toClone, deep);
- this.beanType = toClone.beanType;
- this.configuration = toClone.configuration;
- this.data = toClone.data;
- this.shim = createShim();
- }
-
@Override
public T as() {
return shim;
}
- @Override
- public AutoBean<T> clone(boolean deep) {
- return new ProxyAutoBean<T>(this, deep);
- }
-
public Configuration getConfiguration() {
return configuration;
}
@@ -194,9 +175,13 @@
super.checkWrapped();
}
+ /**
+ * Not used in this implementation. Instead, the simple implementation is
+ * created lazily in {@link #getWrapped()}.
+ */
@Override
protected T createSimplePeer() {
- return ProxyAutoBean.makeProxy(beanType, new SimpleBeanHandler<T>(this));
+ return null;
}
/**
@@ -208,10 +193,11 @@
}
/**
- * Allow access by {@link BeanMethod}.
+ * Allow access by BeanMethod.
*/
- protected Map<String, Object> getValues() {
- return values;
+ @Override
+ protected <V> V getOrReify(String propertyName) {
+ return super.<V> getOrReify(propertyName);
}
/**
@@ -219,6 +205,9 @@
*/
@Override
protected T getWrapped() {
+ if (wrapped == null && isUsingSimplePeer()) {
+ wrapped = (T) ProxyAutoBean.makeProxy(beanType, new SimpleBeanHandler<T>(this));
+ }
return super.getWrapped();
}
@@ -230,6 +219,11 @@
super.set(method, value);
}
+ @Override
+ protected void setProperty(String propertyName, Object value) {
+ super.setProperty(propertyName, value);
+ }
+
// TODO: Port to model-based when class-based TypeOracle is available.
@Override
protected void traverseProperties(AutoBeanVisitor visitor, OneShotContext ctx) {
@@ -257,8 +251,9 @@
}
// Create the context used for the property visitation
- MethodPropertyContext x = isUsingSimplePeer() ? new BeanPropertyContext(
- this, getter) : new GetterPropertyContext(this, getter);
+ MethodPropertyContext x =
+ isUsingSimplePeer() ? new BeanPropertyContext(this, getter) : new GetterPropertyContext(
+ this, getter);
switch (propertyType) {
case VALUE: {
@@ -313,13 +308,8 @@
return beanType;
}
- Map<String, Object> getPropertyMap() {
- return values;
- }
-
private T createShim() {
- T toReturn = ProxyAutoBean.makeProxy(beanType, new ShimHandler<T>(this,
- getWrapped()));
+ T toReturn = ProxyAutoBean.makeProxy(beanType, new ShimHandler<T>(this, getWrapped()));
WeakMapping.set(toReturn, AutoBean.class.getName(), this);
return toReturn;
}
diff --git a/user/src/com/google/gwt/autobean/server/impl/ShimHandler.java b/user/src/com/google/gwt/autobean/server/impl/ShimHandler.java
index 23c190d..dc920a7 100644
--- a/user/src/com/google/gwt/autobean/server/impl/ShimHandler.java
+++ b/user/src/com/google/gwt/autobean/server/impl/ShimHandler.java
@@ -19,6 +19,7 @@
import com.google.gwt.autobean.shared.AutoBeanUtils;
import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
@@ -31,11 +32,9 @@
class ShimHandler<T> implements InvocationHandler {
private final ProxyAutoBean<T> bean;
private final Method interceptor;
- private final T toWrap;
public ShimHandler(ProxyAutoBean<T> bean, T toWrap) {
this.bean = bean;
- this.toWrap = toWrap;
Method maybe = null;
for (Class<?> clazz : bean.getConfiguration().getCategories()) {
@@ -67,36 +66,35 @@
return bean.getWrapped().hashCode();
}
- public Object invoke(Object proxy, Method method, Object[] args)
- throws Throwable {
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
method.setAccessible(true);
Object toReturn;
String name = method.getName();
- bean.checkWrapped();
method.setAccessible(true);
- if (BeanMethod.OBJECT.matches(method)) {
- return method.invoke(this, args);
- } else if (BeanMethod.GET.matches(method)) {
- toReturn = method.invoke(toWrap, args);
- toReturn = bean.get(name, toReturn);
- } else if (BeanMethod.SET.matches(method)
- || BeanMethod.SET_BUILDER.matches(method)) {
- bean.checkFrozen();
- toReturn = method.invoke(toWrap, args);
- bean.set(name, args[0]);
- } else {
- // XXX How should freezing and calls work together?
- // bean.checkFrozen();
- toReturn = method.invoke(toWrap, args);
- bean.call(name, toReturn, args);
- }
- Class<?> intf = method.getReturnType();
- if (!Object.class.equals(intf)) {
- // XXX Need to deal with resolving generic T return types
- toReturn = maybeWrap(intf, toReturn);
- }
- if (interceptor != null) {
- toReturn = interceptor.invoke(null, bean, toReturn);
+ try {
+ if (BeanMethod.OBJECT.matches(method)) {
+ return method.invoke(this, args);
+ } else if (BeanMethod.GET.matches(method)) {
+ toReturn = method.invoke(bean.getWrapped(), args);
+ toReturn = bean.get(name, toReturn);
+ } else if (BeanMethod.SET.matches(method) || BeanMethod.SET_BUILDER.matches(method)) {
+ toReturn = method.invoke(bean.getWrapped(), args);
+ bean.set(name, args[0]);
+ } else {
+ // XXX How should freezing and calls work together?
+ toReturn = method.invoke(bean.getWrapped(), args);
+ bean.call(name, toReturn, args);
+ }
+ Class<?> intf = method.getReturnType();
+ if (!Object.class.equals(intf)) {
+ // XXX Need to deal with resolving generic T return types
+ toReturn = maybeWrap(intf, toReturn);
+ }
+ if (interceptor != null) {
+ toReturn = interceptor.invoke(null, bean, toReturn);
+ }
+ } catch (InvocationTargetException e) {
+ throw e.getCause();
}
return toReturn;
}
@@ -110,9 +108,11 @@
if (toReturn == null) {
return null;
}
- if (TypeUtils.isValueType(intf)
- || TypeUtils.isValueType(toReturn.getClass())
- || AutoBeanUtils.getAutoBean(toReturn) != null
+ AutoBean<?> returnBean = AutoBeanUtils.getAutoBean(toReturn);
+ if (returnBean != null) {
+ return returnBean.as();
+ }
+ if (TypeUtils.isValueType(intf) || TypeUtils.isValueType(toReturn.getClass())
|| bean.getConfiguration().getNoWrap().contains(intf)) {
return toReturn;
}
@@ -124,8 +124,8 @@
*/
return toReturn;
}
- ProxyAutoBean<Object> newBean = new ProxyAutoBean<Object>(
- bean.getFactory(), intf, bean.getConfiguration(), toReturn);
+ ProxyAutoBean<Object> newBean =
+ new ProxyAutoBean<Object>(bean.getFactory(), intf, bean.getConfiguration(), toReturn);
return newBean.as();
}
}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/autobean/server/impl/SimpleBeanHandler.java b/user/src/com/google/gwt/autobean/server/impl/SimpleBeanHandler.java
index d25468d..766d26b 100644
--- a/user/src/com/google/gwt/autobean/server/impl/SimpleBeanHandler.java
+++ b/user/src/com/google/gwt/autobean/server/impl/SimpleBeanHandler.java
@@ -37,8 +37,7 @@
/**
* Delegates most work to {@link BeanMethod}.
*/
- public Object invoke(Object proxy, Method method, Object[] args)
- throws Throwable {
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
for (BeanMethod type : BeanMethod.values()) {
if (type.matches(this, method)) {
Object toReturn = type.invoke(this, method, args);
@@ -47,12 +46,12 @@
}
throw new RuntimeException("Unhandled invocation " + method.getName());
}
-
+
/**
* For debugging use only.
*/
@Override
public String toString() {
- return bean.getPropertyMap().toString();
+ return bean.getSplittable().getPayload();
}
}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/autobean/server/impl/TypeUtils.java b/user/src/com/google/gwt/autobean/server/impl/TypeUtils.java
index cf769c1..2503696 100644
--- a/user/src/com/google/gwt/autobean/server/impl/TypeUtils.java
+++ b/user/src/com/google/gwt/autobean/server/impl/TypeUtils.java
@@ -164,8 +164,9 @@
return false;
}
- public static Class<?> maybeAutobox(Class<?> domainType) {
- Class<?> autoBoxType = AUTOBOX_MAP.get(domainType);
+ public static <V> Class<V> maybeAutobox(Class<V> domainType) {
+ @SuppressWarnings("unchecked")
+ Class<V> autoBoxType = (Class<V>) AUTOBOX_MAP.get(domainType);
return autoBoxType == null ? domainType : autoBoxType;
}
diff --git a/user/src/com/google/gwt/autobean/shared/AutoBean.java b/user/src/com/google/gwt/autobean/shared/AutoBean.java
index 8b8613f..3af5128 100644
--- a/user/src/com/google/gwt/autobean/shared/AutoBean.java
+++ b/user/src/com/google/gwt/autobean/shared/AutoBean.java
@@ -58,16 +58,21 @@
T as();
/**
- * Creates a copy of the AutoBean.
+ * This method always throws an {@link UnsupportedOperationException}. The
+ * implementation of this method in previous releases was not sufficiently
+ * robust and there are no further uses of this method within the GWT code
+ * base. Furthermore, there are many different semantics that can be applied
+ * to a cloning process that cannot be adequately addressed with a single
+ * implementation.
* <p>
- * If the AutoBean has tags, the tags will be copied into the cloned AutoBean.
- * If any of the tag values are AutoBeans, they will not be cloned, regardless
- * of the value of <code>deep</code>.
+ * A simple clone of an acyclic datastructure can be created by using
+ * {@link AutoBeanCodex} to encode and decode the root object. Other cloning
+ * algorithms are best implemented by using an {@link AutoBeanVisitor}.
*
- * @param deep indicates if all referenced AutoBeans should be cloned
- * @return a copy of this {@link AutoBean}
- * @throws IllegalStateException if the AutoBean is a wrapper type
+ * @throws UnsupportedOperationException
+ * @deprecated with no replacement
*/
+ @Deprecated
AutoBean<T> clone(boolean deep);
/**
diff --git a/user/src/com/google/gwt/autobean/shared/AutoBeanCodex.java b/user/src/com/google/gwt/autobean/shared/AutoBeanCodex.java
index b7e4d52..9797984 100644
--- a/user/src/com/google/gwt/autobean/shared/AutoBeanCodex.java
+++ b/user/src/com/google/gwt/autobean/shared/AutoBeanCodex.java
@@ -15,21 +15,10 @@
*/
package com.google.gwt.autobean.shared;
-import com.google.gwt.autobean.shared.AutoBeanVisitor.ParameterizationVisitor;
-import com.google.gwt.autobean.shared.impl.EnumMap;
-import com.google.gwt.autobean.shared.impl.LazySplittable;
+import com.google.gwt.autobean.shared.impl.AutoBeanCodexImpl;
+import com.google.gwt.autobean.shared.impl.AutoBeanCodexImpl.EncodeState;
import com.google.gwt.autobean.shared.impl.StringQuoter;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.Stack;
-
/**
* Utility methods for encoding an AutoBean graph into a JSON-compatible string.
* This codex intentionally does not preserve object identity, nor does it
@@ -38,363 +27,16 @@
public class AutoBeanCodex {
/**
- * Describes a means of encoding or decoding a particular type of data to or
- * from a wire format representation.
+ * Decode an AutoBeanCodex payload.
+ *
+ * @param <T> the expected return type
+ * @param factory an AutoBeanFactory capable of producing {@code AutoBean<T>}
+ * @param clazz the expected return type
+ * @param data a payload previously generated by {@link #encode(AutoBean)}
+ * @return an AutoBean containing the payload contents
*/
- interface Coder {
- Object decode(Splittable data);
-
- void encode(StringBuilder sb, Object value);
- }
-
- /**
- * Creates a Coder that is capable of operating on a particular
- * parameterization of a datastructure (e.g. {@code Map<String, List<String>>}
- * ).
- */
- class CoderCreator extends ParameterizationVisitor {
- private Stack<Coder> stack = new Stack<Coder>();
-
- @Override
- public void endVisitType(Class<?> type) {
- if (List.class.equals(type) || Set.class.equals(type)) {
- stack.push(new CollectionCoder(type, stack.pop()));
- } else if (Map.class.equals(type)) {
- // Note that the parameters are passed in reverse order
- stack.push(new MapCoder(stack.pop(), stack.pop()));
- } else if (Splittable.class.equals(type)) {
- stack.push(new SplittableDecoder());
- } else if (type.getEnumConstants() != null) {
- @SuppressWarnings(value = {"rawtypes", "unchecked"})
- EnumCoder decoder = new EnumCoder(type);
- stack.push(decoder);
- } else if (ValueCodex.canDecode(type)) {
- stack.push(new ValueCoder(type));
- } else {
- stack.push(new ObjectCoder(type));
- }
- }
-
- public Coder getCoder() {
- assert stack.size() == 1 : "Incorrect size: " + stack.size();
- return stack.pop();
- }
- }
-
- class CollectionCoder implements Coder {
- private final Coder elementDecoder;
- private final Class<?> type;
-
- public CollectionCoder(Class<?> type, Coder elementDecoder) {
- this.elementDecoder = elementDecoder;
- this.type = type;
- }
-
- public Object decode(Splittable data) {
- Collection<Object> collection;
- if (List.class.equals(type)) {
- collection = new ArrayList<Object>();
- } else if (Set.class.equals(type)) {
- collection = new HashSet<Object>();
- } else {
- // Should not reach here
- throw new RuntimeException(type.getName());
- }
- for (int i = 0, j = data.size(); i < j; i++) {
- Object element = data.isNull(i) ? null : elementDecoder.decode(data.get(i));
- collection.add(element);
- }
- return collection;
- }
-
- public void encode(StringBuilder sb, Object value) {
- if (value == null) {
- sb.append("null");
- return;
- }
-
- Iterator<?> it = ((Collection<?>) value).iterator();
- sb.append("[");
- if (it.hasNext()) {
- elementDecoder.encode(sb, it.next());
- while (it.hasNext()) {
- sb.append(",");
- elementDecoder.encode(sb, it.next());
- }
- }
- sb.append("]");
- }
- }
-
- class EnumCoder<E extends Enum<E>> implements Coder {
- private final Class<E> type;
-
- public EnumCoder(Class<E> type) {
- this.type = type;
- }
-
- public Object decode(Splittable data) {
- return enumMap.getEnum(type, data.asString());
- }
-
- public void encode(StringBuilder sb, Object value) {
- if (value == null) {
- sb.append("null");
- }
- sb.append(StringQuoter.quote(enumMap.getToken((Enum<?>) value)));
- }
- }
-
- /**
- * Used to stop processing.
- */
- static class HaltException extends RuntimeException {
- public HaltException(RuntimeException cause) {
- super(cause);
- }
-
- @Override
- public RuntimeException getCause() {
- return (RuntimeException) super.getCause();
- }
- }
-
- class MapCoder implements Coder {
- private final Coder keyDecoder;
- private final Coder valueDecoder;
-
- /**
- * Parameters in reversed order to accommodate stack-based setup.
- */
- public MapCoder(Coder valueDecoder, Coder keyDecoder) {
- this.keyDecoder = keyDecoder;
- this.valueDecoder = valueDecoder;
- }
-
- public Object decode(Splittable data) {
- Map<Object, Object> toReturn = new HashMap<Object, Object>();
- if (data.isIndexed()) {
- assert data.size() == 2 : "Wrong data size: " + data.size();
- Splittable keys = data.get(0);
- Splittable values = data.get(1);
- for (int i = 0, j = keys.size(); i < j; i++) {
- Object key = keys.isNull(i) ? null : keyDecoder.decode(keys.get(i));
- Object value = values.isNull(i) ? null : valueDecoder.decode(values.get(i));
- toReturn.put(key, value);
- }
- } else {
- ValueCoder keyValueDecoder = (ValueCoder) keyDecoder;
- for (String rawKey : data.getPropertyKeys()) {
- Object key = keyValueDecoder.decode(rawKey);
- Object value = data.isNull(rawKey) ? null : valueDecoder.decode(data.get(rawKey));
- toReturn.put(key, value);
- }
- }
- return toReturn;
- }
-
- public void encode(StringBuilder sb, Object value) {
- if (value == null) {
- sb.append("null");
- return;
- }
-
- Map<?, ?> map = (Map<?, ?>) value;
- boolean isSimpleMap = keyDecoder instanceof ValueCoder;
- if (isSimpleMap) {
- boolean first = true;
- sb.append("{");
- for (Map.Entry<?, ?> entry : map.entrySet()) {
- Object mapKey = entry.getKey();
- if (mapKey == null) {
- // A null key in a simple map is meaningless
- continue;
- }
- Object mapValue = entry.getValue();
- if (mapValue == null) {
- // A null value can be ignored
- continue;
- }
-
- if (first) {
- first = false;
- } else {
- sb.append(",");
- }
-
- keyDecoder.encode(sb, mapKey);
- sb.append(":");
- valueDecoder.encode(sb, mapValue);
- }
- sb.append("}");
- } else {
- List<Object> keys = new ArrayList<Object>(map.size());
- List<Object> values = new ArrayList<Object>(map.size());
- for (Map.Entry<?, ?> entry : map.entrySet()) {
- keys.add(entry.getKey());
- values.add(entry.getValue());
- }
- sb.append("[");
- new CollectionCoder(List.class, keyDecoder).encode(sb, keys);
- sb.append(",");
- new CollectionCoder(List.class, valueDecoder).encode(sb, values);
- sb.append("]");
- }
- }
- }
-
- class ObjectCoder implements Coder {
- private final Class<?> type;
-
- public ObjectCoder(Class<?> type) {
- this.type = type;
- }
-
- public Object decode(Splittable data) {
- AutoBean<?> bean = doDecode(type, data);
- return bean == null ? null : bean.as();
- }
-
- public void encode(StringBuilder sb, Object value) {
- if (value == null) {
- sb.append("null");
- return;
- }
- doEncode(sb, AutoBeanUtils.getAutoBean(value));
- }
- }
-
- /**
- * Extracts properties from a bean and turns them into JSON text.
- */
- class PropertyGetter extends AutoBeanVisitor {
- private boolean first = true;
- private final StringBuilder sb;
-
- public PropertyGetter(StringBuilder sb) {
- this.sb = sb;
- }
-
- @Override
- public void endVisit(AutoBean<?> bean, Context ctx) {
- sb.append("}");
- seen.pop();
- }
-
- @Override
- public boolean visit(AutoBean<?> bean, Context ctx) {
- if (seen.contains(bean)) {
- throw new HaltException(new UnsupportedOperationException("Cycles not supported"));
- }
- seen.push(bean);
- sb.append("{");
- return true;
- }
-
- @Override
- public boolean visitReferenceProperty(String propertyName, AutoBean<?> value,
- PropertyContext ctx) {
- if (value != null) {
- encodeProperty(propertyName, value.as(), ctx);
- }
- return false;
- }
-
- @Override
- public boolean visitValueProperty(String propertyName, Object value, PropertyContext ctx) {
- if (value != null && !value.equals(ValueCodex.getUninitializedFieldValue(ctx.getType()))) {
- encodeProperty(propertyName, value, ctx);
- }
- return false;
- }
-
- private void encodeProperty(String propertyName, Object value, PropertyContext ctx) {
- CoderCreator pd = new CoderCreator();
- ctx.accept(pd);
- Coder decoder = pd.getCoder();
- if (first) {
- first = false;
- } else {
- sb.append(",");
- }
- sb.append(StringQuoter.quote(propertyName));
- sb.append(":");
- decoder.encode(sb, value);
- }
- }
-
- /**
- * Populates beans with data extracted from an evaluated JSON payload.
- */
- class PropertySetter extends AutoBeanVisitor {
- private Splittable data;
-
- public void decodeInto(Splittable data, AutoBean<?> bean) {
- this.data = data;
- bean.accept(this);
- }
-
- @Override
- public boolean visitReferenceProperty(String propertyName, AutoBean<?> value,
- PropertyContext ctx) {
- decodeProperty(propertyName, ctx);
- return false;
- }
-
- @Override
- public boolean visitValueProperty(String propertyName, Object value, PropertyContext ctx) {
- decodeProperty(propertyName, ctx);
- return false;
- }
-
- protected void decodeProperty(String propertyName, PropertyContext ctx) {
- if (!data.isNull(propertyName)) {
- CoderCreator pd = new CoderCreator();
- ctx.accept(pd);
- Coder decoder = pd.getCoder();
- Object propertyValue = decoder.decode(data.get(propertyName));
- ctx.set(propertyValue);
- }
- }
- }
-
- class SplittableDecoder implements Coder {
- public Object decode(Splittable data) {
- return data;
- }
-
- public void encode(StringBuilder sb, Object value) {
- if (value == null) {
- sb.append("null");
- return;
- }
- sb.append(((Splittable) value).getPayload());
- }
- }
-
- class ValueCoder implements Coder {
- private final Class<?> type;
-
- public ValueCoder(Class<?> type) {
- assert type.getEnumConstants() == null : "Should use EnumTypeCodex";
- this.type = type;
- }
-
- public Object decode(Splittable propertyValue) {
- return decode(propertyValue.asString());
- }
-
- public Object decode(String propertyValue) {
- return ValueCodex.decode(type, propertyValue);
- }
-
- public void encode(StringBuilder sb, Object value) {
- sb.append(ValueCodex.encode(value).getPayload());
- }
- }
-
public static <T> AutoBean<T> decode(AutoBeanFactory factory, Class<T> clazz, Splittable data) {
- return new AutoBeanCodex(factory).doDecode(clazz, data);
+ return AutoBeanCodexImpl.doDecode(EncodeState.forDecode(factory), clazz, data);
}
/**
@@ -404,7 +46,8 @@
* @param factory an AutoBeanFactory capable of producing {@code AutoBean<T>}
* @param clazz the expected return type
* @param payload a payload string previously generated by
- * {@link #encode(AutoBean)}
+ * {@link #encode(AutoBean)}{@link Splittable#getPayload()
+ * .getPayload()}.
* @return an AutoBean containing the payload contents
*/
public static <T> AutoBean<T> decode(AutoBeanFactory factory, Class<T> clazz, String payload) {
@@ -420,7 +63,7 @@
* @param bean the target AutoBean
*/
public static void decodeInto(Splittable data, AutoBean<?> bean) {
- new AutoBeanCodex(bean.getFactory()).doDecodeInto(data, bean);
+ AutoBeanCodexImpl.doDecodeInto(EncodeState.forDecode(bean.getFactory()), data, bean);
}
/**
@@ -432,42 +75,12 @@
*/
public static Splittable encode(AutoBean<?> bean) {
if (bean == null) {
- return LazySplittable.NULL;
+ return Splittable.NULL;
}
StringBuilder sb = new StringBuilder();
- new AutoBeanCodex(bean.getFactory()).doEncode(sb, bean);
- return new LazySplittable(sb.toString());
- }
-
- private final EnumMap enumMap;
- private final AutoBeanFactory factory;
- private final Stack<AutoBean<?>> seen = new Stack<AutoBean<?>>();
-
- private AutoBeanCodex(AutoBeanFactory factory) {
- this.factory = factory;
- this.enumMap = factory instanceof EnumMap ? (EnumMap) factory : null;
- }
-
- <T> AutoBean<T> doDecode(Class<T> clazz, Splittable data) {
- AutoBean<T> toReturn = factory.create(clazz);
- if (toReturn == null) {
- throw new IllegalArgumentException(clazz.getName());
- }
- doDecodeInto(data, toReturn);
- return toReturn;
- }
-
- void doDecodeInto(Splittable data, AutoBean<?> bean) {
- new PropertySetter().decodeInto(data, bean);
- }
-
- void doEncode(StringBuilder sb, AutoBean<?> bean) {
- PropertyGetter e = new PropertyGetter(sb);
- try {
- bean.accept(e);
- } catch (HaltException ex) {
- throw ex.getCause();
- }
+ EncodeState state = EncodeState.forEncode(bean.getFactory(), sb);
+ AutoBeanCodexImpl.doEncode(state, bean);
+ return StringQuoter.split(sb.toString());
}
}
diff --git a/user/src/com/google/gwt/autobean/shared/AutoBeanUtils.java b/user/src/com/google/gwt/autobean/shared/AutoBeanUtils.java
index e617ddf..ca8de60 100644
--- a/user/src/com/google/gwt/autobean/shared/AutoBeanUtils.java
+++ b/user/src/com/google/gwt/autobean/shared/AutoBeanUtils.java
@@ -314,8 +314,7 @@
*/
private static boolean sameOrEquals(Collection<?> collection, Collection<?> otherCollection,
Map<PendingComparison, Comparison> pending, Map<Object, Object> pairs) {
- if (collection.size() != otherCollection.size()
- || !collection.getClass().equals(otherCollection.getClass())) {
+ if (collection.size() != otherCollection.size()) {
return false;
}
diff --git a/user/src/com/google/gwt/autobean/shared/Splittable.java b/user/src/com/google/gwt/autobean/shared/Splittable.java
index 65a2286..9011fc7 100644
--- a/user/src/com/google/gwt/autobean/shared/Splittable.java
+++ b/user/src/com/google/gwt/autobean/shared/Splittable.java
@@ -15,6 +15,8 @@
*/
package com.google.gwt.autobean.shared;
+import com.google.gwt.autobean.shared.impl.StringQuoter;
+
import java.util.List;
/**
@@ -24,11 +26,41 @@
*/
public interface Splittable {
/**
+ * A value that represents {@code null}.
+ */
+ Splittable NULL = StringQuoter.nullValue();
+
+ /**
+ * Returns a boolean representation of the data;
+ */
+ boolean asBoolean();
+
+ /**
+ * Returns a numeric representation of the data.
+ */
+ double asNumber();
+
+ /**
+ * Assign the splittable to the specified index of the {@code parent} object.
+ */
+ void assign(Splittable parent, int index);
+
+ /**
+ * Assign the splittable to the named property of the {@code parent} object.
+ */
+ void assign(Splittable parent, String propertyName);
+
+ /**
* Returns a string representation of the data.
*/
String asString();
/**
+ * Clones the Splittable, ignoring cycles and tags.
+ */
+ Splittable deepCopy();
+
+ /**
* Returns the nth element of a list.
*/
Splittable get(int index);
@@ -50,6 +82,16 @@
List<String> getPropertyKeys();
/**
+ * Returns a value previously set with {@link #setReified(String, Object)}.
+ */
+ Object getReified(String key);
+
+ /**
+ * Returns {@code true} if the value of the Splittable is a boolean.
+ */
+ boolean isBoolean();
+
+ /**
* Returns {@code} true if {@link #size()} and {@link #get(int)} can be
* expected to return meaningful values.
*/
@@ -62,23 +104,49 @@
boolean isKeyed();
/**
- * Indicates if the nth element of a list is null.
+ * Indicates if the nth element of a list is null or undefined.
*/
boolean isNull(int index);
/**
- * Indicates if the named property is null.
+ * Indicates if the named property is null or undefined.
*/
boolean isNull(String key);
/**
+ * Returns {@code true} if the value of the Splittable is numeric.
+ */
+ boolean isNumber();
+
+ /**
+ * Returns {@code true} if {@link #setReified(String, Object)} has been called
+ * with the given key.
+ */
+ boolean isReified(String key);
+
+ /**
* Returns {@code} true if {@link #asString()} can be expected to return a
* meaningful value.
*/
boolean isString();
/**
- * Returns the size of the list.
+ * Returns {@code true} if the value of the key is undefined.
+ */
+ boolean isUndefined(String key);
+
+ /**
+ * Associates a tag value with the Splittable.
+ */
+ void setReified(String key, Object object);
+
+ /**
+ * Resets the length of an indexed Splittable.
+ */
+ void setSize(int i);
+
+ /**
+ * Returns the size of an indexed Splittable.
*/
int size();
}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/autobean/shared/ValueCodex.java b/user/src/com/google/gwt/autobean/shared/ValueCodex.java
index b5e103d..8234ee6 100644
--- a/user/src/com/google/gwt/autobean/shared/ValueCodex.java
+++ b/user/src/com/google/gwt/autobean/shared/ValueCodex.java
@@ -15,7 +15,6 @@
*/
package com.google.gwt.autobean.shared;
-import com.google.gwt.autobean.shared.impl.LazySplittable;
import com.google.gwt.autobean.shared.impl.StringQuoter;
import java.math.BigDecimal;
@@ -38,13 +37,13 @@
}
@Override
- public BigDecimal decode(Class<?> clazz, String value) {
- return new BigDecimal(value);
+ public BigDecimal decode(Class<?> clazz, Splittable value) {
+ return new BigDecimal(value.asString());
}
@Override
- public String toJsonExpression(Object value) {
- return StringQuoter.quote(((BigDecimal) value).toString());
+ public Splittable encode(Object value) {
+ return StringQuoter.create(((BigDecimal) value).toString());
}
},
BIG_INTEGER(BigInteger.class) {
@@ -54,31 +53,46 @@
}
@Override
- public BigInteger decode(Class<?> clazz, String value) {
- return new BigInteger(value);
+ public BigInteger decode(Class<?> clazz, Splittable value) {
+ return new BigInteger(value.asString());
}
@Override
- public String toJsonExpression(Object value) {
- return StringQuoter.quote(((BigInteger) value).toString());
+ public Splittable encode(Object value) {
+ return StringQuoter.create(((BigInteger) value).toString());
}
},
BOOLEAN(Boolean.class, boolean.class, false) {
@Override
- public Boolean decode(Class<?> clazz, String value) {
- return Boolean.valueOf(value);
+ public Boolean decode(Class<?> clazz, Splittable value) {
+ return value.asBoolean();
+ }
+
+ @Override
+ public Splittable encode(Object value) {
+ return StringQuoter.create((Boolean) value);
}
},
BYTE(Byte.class, byte.class, (byte) 0) {
@Override
- public Byte decode(Class<?> clazz, String value) {
- return Byte.valueOf(value);
+ public Byte decode(Class<?> clazz, Splittable value) {
+ return (byte) value.asNumber();
+ }
+
+ @Override
+ public Splittable encode(Object value) {
+ return StringQuoter.create((Byte) value);
}
},
CHARACTER(Character.class, char.class, (char) 0) {
@Override
- public Character decode(Class<?> clazz, String value) {
- return value.charAt(0);
+ public Character decode(Class<?> clazz, Splittable value) {
+ return value.asString().charAt(0);
+ }
+
+ @Override
+ public Splittable encode(Object value) {
+ return StringQuoter.create(String.valueOf((Character) value));
}
},
DATE(Date.class) {
@@ -88,80 +102,111 @@
}
@Override
- public Date decode(Class<?> clazz, String value) {
- return StringQuoter.tryParseDate(value);
+ public Date decode(Class<?> clazz, Splittable value) {
+ return StringQuoter.tryParseDate(value.asString());
}
@Override
- public String toJsonExpression(Object value) {
- return String.valueOf(((Date) value).getTime());
+ public Splittable encode(Object value) {
+ return StringQuoter.create(String.valueOf(((Date) value).getTime()));
}
},
DOUBLE(Double.class, double.class, 0d) {
@Override
- public Double decode(Class<?> clazz, String value) {
- return Double.valueOf(value);
+ public Double decode(Class<?> clazz, Splittable value) {
+ return value.asNumber();
+ }
+
+ @Override
+ public Splittable encode(Object value) {
+ return StringQuoter.create((Double) value);
}
},
ENUM(Enum.class) {
@Override
- public Enum<?> decode(Class<?> clazz, String value) {
- return (Enum<?>) clazz.getEnumConstants()[Integer.valueOf(value)];
+ public Enum<?> decode(Class<?> clazz, Splittable value) {
+ return (Enum<?>) clazz.getEnumConstants()[(int) value.asNumber()];
}
@Override
- public String toJsonExpression(Object value) {
- return String.valueOf(((Enum<?>) value).ordinal());
+ public Splittable encode(Object value) {
+ return StringQuoter.create(((Enum<?>) value).ordinal());
}
},
FLOAT(Float.class, float.class, 0f) {
@Override
- public Float decode(Class<?> clazz, String value) {
- return Float.valueOf(value);
+ public Float decode(Class<?> clazz, Splittable value) {
+ return (float) value.asNumber();
+ }
+
+ @Override
+ public Splittable encode(Object value) {
+ return StringQuoter.create((Float) value);
}
},
INTEGER(Integer.class, int.class, 0) {
@Override
- public Integer decode(Class<?> clazz, String value) {
- return Integer.valueOf(value);
+ public Integer decode(Class<?> clazz, Splittable value) {
+ return Integer.valueOf((int) value.asNumber());
+ }
+
+ @Override
+ public Splittable encode(Object value) {
+ return StringQuoter.create((Integer) value);
}
},
LONG(Long.class, long.class, 0L) {
@Override
- public Long decode(Class<?> clazz, String value) {
- return Long.valueOf(value);
+ public Long decode(Class<?> clazz, Splittable value) {
+ return Long.parseLong(value.asString());
}
@Override
- public String toJsonExpression(Object value) {
- // Longs cannot be expressed as a JS double
- if (value instanceof Long) {
- return StringQuoter.quote(String.valueOf(value));
- } else {
- throw new IllegalArgumentException("value should be a Long");
- }
+ public Splittable encode(Object value) {
+ return StringQuoter.create(String.valueOf((Long) value));
}
},
SHORT(Short.class, short.class, (short) 0) {
@Override
- public Short decode(Class<?> clazz, String value) {
- return Short.valueOf(value);
+ public Short decode(Class<?> clazz, Splittable value) {
+ return (short) value.asNumber();
+ }
+
+ @Override
+ public Splittable encode(Object value) {
+ return StringQuoter.create((Short) value);
}
},
STRING(String.class) {
@Override
- public String decode(Class<?> clazz, String value) {
+ public String decode(Class<?> clazz, Splittable value) {
+ return value.asString();
+ }
+
+ @Override
+ public Splittable encode(Object value) {
+ return StringQuoter.create((String) value);
+ }
+ },
+ SPLITTABLE(Splittable.class) {
+ @Override
+ public Splittable decode(Class<?> clazz, Splittable value) {
return value;
}
@Override
- public String toJsonExpression(Object value) {
- return StringQuoter.quote((String) value);
+ public Splittable encode(Object value) {
+ return (Splittable) value;
}
},
VOID(Void.class, void.class, null) {
@Override
- public Void decode(Class<?> clazz, String value) {
+ public Void decode(Class<?> clazz, Splittable value) {
+ return null;
+ }
+
+ @Override
+ public Splittable encode(Object value) {
return null;
}
};
@@ -190,7 +235,9 @@
return false;
}
- public abstract Object decode(Class<?> clazz, String value);
+ public abstract Object decode(Class<?> clazz, Splittable value);
+
+ public abstract Splittable encode(Object value);
public Object getDefaultValue() {
return defaultValue;
@@ -203,10 +250,6 @@
public Class<?> getType() {
return type;
}
-
- public String toJsonExpression(Object value) {
- return String.valueOf(value);
- }
}
private static final Set<Class<?>> ALL_VALUE_TYPES;
@@ -237,19 +280,23 @@
return ValueCodexHelper.canDecode(clazz);
}
+ @SuppressWarnings("unchecked")
public static <T> T decode(Class<T> clazz, Splittable split) {
- if (split == null || split == LazySplittable.NULL) {
+ if (split == null || split == Splittable.NULL) {
return null;
}
- return decode(clazz, split.asString());
+ return (T) getTypeOrDie(clazz).decode(clazz, split);
}
- @SuppressWarnings("unchecked")
+ /**
+ * No callers in GWT codebase.
+ *
+ * @deprecated use {@link #decode(Class, Splittable)} instead.
+ * @throws UnsupportedOperationException
+ */
+ @Deprecated
public static <T> T decode(Class<T> clazz, String string) {
- if (string == null) {
- return null;
- }
- return (T) getTypeOrDie(clazz).decode(clazz, string);
+ throw new UnsupportedOperationException();
}
/**
@@ -258,14 +305,14 @@
*/
public static Splittable encode(Class<?> clazz, Object obj) {
if (obj == null) {
- return LazySplittable.NULL;
+ return Splittable.NULL;
}
- return new LazySplittable(getTypeOrDie(clazz).toJsonExpression(obj));
+ return getTypeOrDie(clazz).encode(obj);
}
public static Splittable encode(Object obj) {
if (obj == null) {
- return LazySplittable.NULL;
+ return Splittable.NULL;
}
Type t = findType(obj.getClass());
// Try upcasting
@@ -280,7 +327,7 @@
if (t == null) {
throw new UnsupportedOperationException(obj.getClass().getName());
}
- return new LazySplittable(t.toJsonExpression(obj));
+ return t.encode(obj);
}
/**
diff --git a/user/src/com/google/gwt/autobean/shared/impl/AbstractAutoBean.java b/user/src/com/google/gwt/autobean/shared/impl/AbstractAutoBean.java
index ad2dc20..d45da29 100644
--- a/user/src/com/google/gwt/autobean/shared/impl/AbstractAutoBean.java
+++ b/user/src/com/google/gwt/autobean/shared/impl/AbstractAutoBean.java
@@ -20,6 +20,9 @@
import com.google.gwt.autobean.shared.AutoBeanUtils;
import com.google.gwt.autobean.shared.AutoBeanVisitor;
import com.google.gwt.autobean.shared.AutoBeanVisitor.Context;
+import com.google.gwt.autobean.shared.Splittable;
+import com.google.gwt.autobean.shared.impl.AutoBeanCodexImpl.Coder;
+import com.google.gwt.autobean.shared.impl.AutoBeanCodexImpl.EncodeState;
import com.google.gwt.core.client.impl.WeakMapping;
import java.util.HashMap;
@@ -32,7 +35,7 @@
*
* @param <T> the wrapper type
*/
-public abstract class AbstractAutoBean<T> implements AutoBean<T> {
+public abstract class AbstractAutoBean<T> implements AutoBean<T>, HasSplittable {
/**
* Used to avoid cycles when visiting.
*/
@@ -44,64 +47,50 @@
}
}
+ public static final String UNSPLITTABLE_VALUES_KEY = "__unsplittableValues";
protected static final Object[] EMPTY_OBJECT = new Object[0];
/**
* Used by {@link #createSimplePeer()}.
*/
- protected final Map<String, Object> values;
-
+ protected Splittable data;
+ protected T wrapped;
private final AutoBeanFactory factory;
private boolean frozen;
-
/**
* Lazily initialized by {@link #setTag(String, Object)} because not all
* instances will make use of tags.
*/
private Map<String, Object> tags;
private final boolean usingSimplePeer;
- private T wrapped;
/**
* Constructor that will use a generated simple peer.
*/
protected AbstractAutoBean(AutoBeanFactory factory) {
+ this(factory, StringQuoter.createSplittable());
+ }
+
+ /**
+ * Constructor that will use a generated simple peer, backed with existing
+ * data.
+ */
+ protected AbstractAutoBean(AutoBeanFactory factory, Splittable data) {
+ this.data = data;
this.factory = factory;
usingSimplePeer = true;
- values = new HashMap<String, Object>();
+ wrapped = createSimplePeer();
}
/**
- * Clone constructor.
+ * Constructor that wraps an existing object. The parameters on this method
+ * are reversed to avoid conflicting with the other two-arg constructor for
+ * {@code AutoBean<Splittable>} instances.
*/
- protected AbstractAutoBean(AbstractAutoBean<T> toClone, boolean deep) {
- this.factory = toClone.factory;
- if (!toClone.usingSimplePeer) {
- throw new IllegalStateException("Cannot clone wrapped bean");
- }
- if (toClone.tags != null) {
- tags = new HashMap<String, Object>(toClone.tags);
- }
- usingSimplePeer = true;
- values = new HashMap<String, Object>(toClone.values);
-
- if (deep) {
- for (Map.Entry<String, Object> entry : values.entrySet()) {
- AutoBean<?> auto = AutoBeanUtils.getAutoBean(entry.getValue());
- if (auto != null) {
- entry.setValue(auto.clone(true).as());
- }
- }
- }
- }
-
- /**
- * Constructor that wraps an existing object.
- */
- protected AbstractAutoBean(AutoBeanFactory factory, T wrapped) {
+ protected AbstractAutoBean(T wrapped, AutoBeanFactory factory) {
this.factory = factory;
usingSimplePeer = false;
- values = null;
+ data = null;
this.wrapped = wrapped;
// Used by AutoBeanUtils
@@ -114,17 +103,31 @@
public abstract T as();
- public abstract AutoBean<T> clone(boolean deep);
+ public AutoBean<T> clone(boolean deep) {
+ throw new UnsupportedOperationException();
+ }
public AutoBeanFactory getFactory() {
return factory;
}
+ public Splittable getSplittable() {
+ return data;
+ }
+
@SuppressWarnings("unchecked")
public <Q> Q getTag(String tagName) {
return tags == null ? null : (Q) tags.get(tagName);
}
+ /**
+ * Indicates that the value returned from {@link #getSplittable()} may not
+ * contain all of the data encapsulated by the AutoBean.
+ */
+ public boolean hasUnsplittableValues() {
+ return data.isReified(UNSPLITTABLE_VALUES_KEY);
+ }
+
public boolean isFrozen() {
return frozen;
}
@@ -133,6 +136,16 @@
return !usingSimplePeer;
}
+ public void setData(Splittable data) {
+ assert data != null : "null data";
+ this.data = data;
+ /*
+ * The simple peer aliases the data object from the enclosing bean to avoid
+ * needing to call up the this.this$0 chain.
+ */
+ wrapped = createSimplePeer();
+ }
+
public void setFrozen(boolean frozen) {
this.frozen = frozen;
}
@@ -208,11 +221,30 @@
return AutoBeanUtils.<W, W> getAutoBean(obj).as();
}
- protected T getWrapped() {
- if (wrapped == null) {
- assert usingSimplePeer : "checkWrapped should have failed";
- wrapped = createSimplePeer();
+ /**
+ * Native getters and setters for primitive properties are generated for each
+ * type to ensure inlining.
+ */
+ protected <Q> Q getOrReify(String propertyName) {
+ checkWrapped();
+ if (data.isReified(propertyName)) {
+ @SuppressWarnings("unchecked")
+ Q temp = (Q) data.getReified(propertyName);
+ return temp;
}
+ if (data.isNull(propertyName)) {
+ return null;
+ }
+ data.setReified(propertyName, null);
+ Coder coder = AutoBeanCodexImpl.doCoderFor(this, propertyName);
+ @SuppressWarnings("unchecked")
+ Q toReturn = (Q) coder.decode(EncodeState.forDecode(factory), data.get(propertyName));
+ data.setReified(propertyName, toReturn);
+ return toReturn;
+ }
+
+ protected T getWrapped() {
+ checkWrapped();
return wrapped;
}
@@ -233,6 +265,27 @@
protected void set(String method, Object value) {
}
- protected abstract void traverseProperties(AutoBeanVisitor visitor,
- OneShotContext ctx);
+ protected void setProperty(String propertyName, Object value) {
+ checkWrapped();
+ checkFrozen();
+ data.setReified(propertyName, value);
+ if (value == null) {
+ Splittable.NULL.assign(data, propertyName);
+ return;
+ }
+ Coder coder = AutoBeanCodexImpl.doCoderFor(this, propertyName);
+ Splittable backing = coder.extractSplittable(EncodeState.forDecode(factory), value);
+ if (backing == null) {
+ /*
+ * External data type, such as an ArrayList or a concrete implementation
+ * of a setter's interface type. This means that a slow serialization pass
+ * is necessary.
+ */
+ data.setReified(UNSPLITTABLE_VALUES_KEY, true);
+ } else {
+ backing.assign(data, propertyName);
+ }
+ }
+
+ protected abstract void traverseProperties(AutoBeanVisitor visitor, OneShotContext ctx);
}
diff --git a/user/src/com/google/gwt/autobean/shared/impl/AutoBeanCodexImpl.java b/user/src/com/google/gwt/autobean/shared/impl/AutoBeanCodexImpl.java
new file mode 100644
index 0000000..3fbb4d8
--- /dev/null
+++ b/user/src/com/google/gwt/autobean/shared/impl/AutoBeanCodexImpl.java
@@ -0,0 +1,613 @@
+/*
+ * 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.autobean.shared.impl;
+
+import com.google.gwt.autobean.shared.AutoBean;
+import com.google.gwt.autobean.shared.AutoBeanFactory;
+import com.google.gwt.autobean.shared.AutoBeanUtils;
+import com.google.gwt.autobean.shared.AutoBeanVisitor;
+import com.google.gwt.autobean.shared.AutoBeanVisitor.ParameterizationVisitor;
+import com.google.gwt.autobean.shared.Splittable;
+import com.google.gwt.autobean.shared.ValueCodex;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+
+/**
+ * Contains the implementation details of AutoBeanCodex. This type was factored
+ * out of AutoBeanCodex so that various implementation details can be accessed
+ * without polluting a public API.
+ */
+public class AutoBeanCodexImpl {
+
+ /**
+ * Describes a means of encoding or decoding a particular type of data to or
+ * from a wire format representation. Any given instance of a Coder should be
+ * stateless; any state required for operation must be maintained in an
+ * {@link EncodeState}.
+ */
+ public interface Coder {
+ Object decode(EncodeState state, Splittable data);
+
+ void encode(EncodeState state, Object value);
+
+ Splittable extractSplittable(EncodeState state, Object value);
+ }
+
+ /**
+ * Contains transient state for Coder operation.
+ */
+ public static class EncodeState {
+ /**
+ * Constructs a state object used for decoding payloads.
+ */
+ public static EncodeState forDecode(AutoBeanFactory factory) {
+ return new EncodeState(factory, null);
+ }
+
+ /**
+ * Constructs a state object used for encoding payloads.
+ */
+ public static EncodeState forEncode(AutoBeanFactory factory, StringBuilder sb) {
+ return new EncodeState(factory, sb);
+ }
+
+ /**
+ * Constructs a "stateless" state for testing Coders that do not require
+ * AutoBean implementation details.
+ */
+ public static EncodeState forTesting() {
+ return new EncodeState(null, null);
+ }
+
+ final EnumMap enumMap;
+ final AutoBeanFactory factory;
+ final StringBuilder sb;
+ final Stack<AutoBean<?>> seen;
+
+ private EncodeState(AutoBeanFactory factory, StringBuilder sb) {
+ this.factory = factory;
+ enumMap = factory instanceof EnumMap ? (EnumMap) factory : null;
+ this.sb = sb;
+ this.seen = sb == null ? null : new Stack<AutoBean<?>>();
+ }
+ }
+
+ /**
+ * Dynamically creates a Coder that is capable of operating on a particular
+ * parameterization of a datastructure (e.g. {@code Map<String, List<String>>}
+ * ).
+ */
+ static class CoderCreator extends ParameterizationVisitor {
+ private Stack<Coder> stack = new Stack<Coder>();
+
+ @Override
+ public void endVisitType(Class<?> type) {
+ if (List.class.equals(type) || Set.class.equals(type)) {
+ stack.push(collectionCoder(type, stack.pop()));
+ } else if (Map.class.equals(type)) {
+ // Note that the parameters are passed in reverse order
+ stack.push(mapCoder(stack.pop(), stack.pop()));
+ } else if (Splittable.class.equals(type)) {
+ stack.push(splittableCoder());
+ } else if (type.getEnumConstants() != null) {
+ @SuppressWarnings(value = {"unchecked"})
+ Class<Enum<?>> enumType = (Class<Enum<?>>) type;
+ stack.push(enumCoder(enumType));
+ } else if (ValueCodex.canDecode(type)) {
+ stack.push(valueCoder(type));
+ } else {
+ stack.push(objectCoder(type));
+ }
+ }
+
+ public Coder getCoder() {
+ assert stack.size() == 1 : "Incorrect size: " + stack.size();
+ return stack.pop();
+ }
+ }
+
+ /**
+ * Constructs one of the lightweight collection types.
+ */
+ static class CollectionCoder implements Coder {
+ private final Coder elementDecoder;
+ private final Class<?> type;
+
+ public CollectionCoder(Class<?> type, Coder elementDecoder) {
+ this.elementDecoder = elementDecoder;
+ this.type = type;
+ }
+
+ public Object decode(EncodeState state, Splittable data) {
+ Collection<Object> collection;
+ if (List.class.equals(type)) {
+ collection = new SplittableList<Object>(data, elementDecoder, state);
+ } else if (Set.class.equals(type)) {
+ collection = new SplittableSet<Object>(data, elementDecoder, state);
+ } else {
+ // Should not reach here
+ throw new RuntimeException(type.getName());
+ }
+ return collection;
+ }
+
+ public void encode(EncodeState state, Object value) {
+ if (value == null) {
+ state.sb.append("null");
+ return;
+ }
+
+ Iterator<?> it = ((Collection<?>) value).iterator();
+ state.sb.append("[");
+ if (it.hasNext()) {
+ elementDecoder.encode(state, it.next());
+ while (it.hasNext()) {
+ state.sb.append(",");
+ elementDecoder.encode(state, it.next());
+ }
+ }
+ state.sb.append("]");
+ }
+
+ public Splittable extractSplittable(EncodeState state, Object value) {
+ return tryExtractSplittable(value);
+ }
+ }
+
+ /**
+ * Produces enums.
+ *
+ * @param <E>
+ */
+ static class EnumCoder<E extends Enum<?>> implements Coder {
+ private final Class<E> type;
+
+ public EnumCoder(Class<E> type) {
+ this.type = type;
+ }
+
+ public Object decode(EncodeState state, Splittable data) {
+ return state.enumMap.getEnum(type, data.asString());
+ }
+
+ public void encode(EncodeState state, Object value) {
+ if (value == null) {
+ state.sb.append("null");
+ return;
+ }
+ state.sb.append(StringQuoter.quote(state.enumMap.getToken((Enum<?>) value)));
+ }
+
+ public Splittable extractSplittable(EncodeState state, Object value) {
+ return StringQuoter.split(StringQuoter.quote(state.enumMap.getToken((Enum<?>) value)));
+ }
+ }
+
+ /**
+ * Used to stop processing.
+ */
+ static class HaltException extends RuntimeException {
+ public HaltException(RuntimeException cause) {
+ super(cause);
+ }
+
+ @Override
+ public RuntimeException getCause() {
+ return (RuntimeException) super.getCause();
+ }
+ }
+
+ /**
+ * Constructs one of the lightweight Map types, depending on the key type.
+ */
+ static class MapCoder implements Coder {
+ private final Coder keyDecoder;
+ private final Coder valueDecoder;
+
+ /**
+ * Parameters in reversed order to accommodate stack-based setup.
+ */
+ public MapCoder(Coder valueDecoder, Coder keyDecoder) {
+ this.keyDecoder = keyDecoder;
+ this.valueDecoder = valueDecoder;
+ }
+
+ public Object decode(EncodeState state, Splittable data) {
+ Map<Object, Object> toReturn;
+ if (data.isIndexed()) {
+ assert data.size() == 2 : "Wrong data size: " + data.size();
+ toReturn = new SplittableComplexMap<Object, Object>(data, keyDecoder, valueDecoder, state);
+ } else {
+ toReturn = new SplittableSimpleMap<Object, Object>(data, keyDecoder, valueDecoder, state);
+ }
+ return toReturn;
+ }
+
+ public void encode(EncodeState state, Object value) {
+ if (value == null) {
+ state.sb.append("null");
+ return;
+ }
+
+ Map<?, ?> map = (Map<?, ?>) value;
+ boolean isSimpleMap = keyDecoder instanceof ValueCoder;
+ if (isSimpleMap) {
+ boolean first = true;
+ state.sb.append("{");
+ for (Map.Entry<?, ?> entry : map.entrySet()) {
+ Object mapKey = entry.getKey();
+ if (mapKey == null) {
+ // A null key in a simple map is meaningless
+ continue;
+ }
+ Object mapValue = entry.getValue();
+
+ if (first) {
+ first = false;
+ } else {
+ state.sb.append(",");
+ }
+
+ keyDecoder.encode(state, mapKey);
+ state.sb.append(":");
+ if (mapValue == null) {
+ // Null values must be preserved
+ state.sb.append("null");
+ } else {
+ valueDecoder.encode(state, mapValue);
+ }
+ }
+ state.sb.append("}");
+ } else {
+ List<Object> keys = new ArrayList<Object>(map.size());
+ List<Object> values = new ArrayList<Object>(map.size());
+ for (Map.Entry<?, ?> entry : map.entrySet()) {
+ keys.add(entry.getKey());
+ values.add(entry.getValue());
+ }
+ state.sb.append("[");
+ collectionCoder(List.class, keyDecoder).encode(state, keys);
+ state.sb.append(",");
+ collectionCoder(List.class, valueDecoder).encode(state, values);
+ state.sb.append("]");
+ }
+ }
+
+ public Splittable extractSplittable(EncodeState state, Object value) {
+ return tryExtractSplittable(value);
+ }
+ }
+
+ /**
+ * Recurses into {@link AutoBeanCodexImpl}.
+ */
+ static class ObjectCoder implements Coder {
+ private final Class<?> type;
+
+ public ObjectCoder(Class<?> type) {
+ this.type = type;
+ }
+
+ public Object decode(EncodeState state, Splittable data) {
+ AutoBean<?> bean = doDecode(state, type, data);
+ return bean == null ? null : bean.as();
+ }
+
+ public void encode(EncodeState state, Object value) {
+ if (value == null) {
+ state.sb.append("null");
+ return;
+ }
+ doEncode(state, AutoBeanUtils.getAutoBean(value));
+ }
+
+ public Splittable extractSplittable(EncodeState state, Object value) {
+ return tryExtractSplittable(value);
+ }
+ }
+
+ static class PropertyCoderCreator extends AutoBeanVisitor {
+ private AutoBean<?> bean;
+
+ @Override
+ public boolean visit(AutoBean<?> bean, Context ctx) {
+ this.bean = bean;
+ return true;
+ }
+
+ @Override
+ public boolean visitReferenceProperty(String propertyName, AutoBean<?> value,
+ PropertyContext ctx) {
+ maybeCreateCoder(propertyName, ctx);
+ return false;
+ }
+
+ @Override
+ public boolean visitValueProperty(String propertyName, Object value, PropertyContext ctx) {
+ maybeCreateCoder(propertyName, ctx);
+ return false;
+ }
+
+ private void maybeCreateCoder(String propertyName, PropertyContext ctx) {
+ CoderCreator creator = new CoderCreator();
+ ctx.accept(creator);
+ coderFor.put(key(bean, propertyName), creator.getCoder());
+ }
+ }
+
+ /**
+ * Extracts properties from a bean and turns them into JSON text.
+ */
+ static class PropertyGetter extends AutoBeanVisitor {
+ private boolean first = true;
+ private final EncodeState state;
+
+ public PropertyGetter(EncodeState state) {
+ this.state = state;
+ }
+
+ @Override
+ public void endVisit(AutoBean<?> bean, Context ctx) {
+ state.sb.append("}");
+ state.seen.pop();
+ }
+
+ @Override
+ public boolean visit(AutoBean<?> bean, Context ctx) {
+ if (state.seen.contains(bean)) {
+ throw new HaltException(new UnsupportedOperationException("Cycles not supported"));
+ }
+ state.seen.push(bean);
+ state.sb.append("{");
+ return true;
+ }
+
+ @Override
+ public boolean visitReferenceProperty(String propertyName, AutoBean<?> value,
+ PropertyContext ctx) {
+ if (value != null) {
+ encodeProperty(propertyName, value.as(), ctx);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitValueProperty(String propertyName, Object value, PropertyContext ctx) {
+ if (value != null && !value.equals(ValueCodex.getUninitializedFieldValue(ctx.getType()))) {
+ encodeProperty(propertyName, value, ctx);
+ }
+ return false;
+ }
+
+ private void encodeProperty(String propertyName, Object value, PropertyContext ctx) {
+ CoderCreator pd = new CoderCreator();
+ ctx.accept(pd);
+ Coder decoder = pd.getCoder();
+ if (first) {
+ first = false;
+ } else {
+ state.sb.append(",");
+ }
+ state.sb.append(StringQuoter.quote(propertyName));
+ state.sb.append(":");
+ decoder.encode(state, value);
+ }
+ }
+
+ /**
+ * Populates beans with data extracted from an evaluated JSON payload.
+ */
+ static class PropertySetter extends AutoBeanVisitor {
+ private Splittable data;
+ private EncodeState state;
+
+ public void decodeInto(EncodeState state, Splittable data, AutoBean<?> bean) {
+ this.data = data;
+ this.state = state;
+ bean.accept(this);
+ }
+
+ @Override
+ public boolean visitReferenceProperty(String propertyName, AutoBean<?> value,
+ PropertyContext ctx) {
+ decodeProperty(propertyName, ctx);
+ return false;
+ }
+
+ @Override
+ public boolean visitValueProperty(String propertyName, Object value, PropertyContext ctx) {
+ decodeProperty(propertyName, ctx);
+ return false;
+ }
+
+ protected void decodeProperty(String propertyName, PropertyContext ctx) {
+ if (!data.isNull(propertyName)) {
+ CoderCreator pd = new CoderCreator();
+ ctx.accept(pd);
+ Coder decoder = pd.getCoder();
+ Object propertyValue = decoder.decode(state, data.get(propertyName));
+ ctx.set(propertyValue);
+ }
+ }
+ }
+
+ /**
+ * A passthrough Coder.
+ */
+ static class SplittableCoder implements Coder {
+ static final Coder INSTANCE = new SplittableCoder();
+
+ public Object decode(EncodeState state, Splittable data) {
+ return data;
+ }
+
+ public void encode(EncodeState state, Object value) {
+ if (value == null) {
+ state.sb.append("null");
+ return;
+ }
+ state.sb.append(((Splittable) value).getPayload());
+ }
+
+ public Splittable extractSplittable(EncodeState state, Object value) {
+ return (Splittable) value;
+ }
+ }
+
+ /**
+ * Delegates to ValueCodex.
+ */
+ static class ValueCoder implements Coder {
+ private final Class<?> type;
+
+ public ValueCoder(Class<?> type) {
+ assert type.getEnumConstants() == null : "Should use EnumTypeCodex";
+ this.type = type;
+ }
+
+ public Object decode(EncodeState state, Splittable propertyValue) {
+ if (propertyValue == null || propertyValue == Splittable.NULL) {
+ return ValueCodex.getUninitializedFieldValue(type);
+ }
+ return ValueCodex.decode(type, propertyValue);
+ }
+
+ public void encode(EncodeState state, Object value) {
+ state.sb.append(ValueCodex.encode(type, value).getPayload());
+ }
+
+ public Splittable extractSplittable(EncodeState state, Object value) {
+ return ValueCodex.encode(type, value);
+ }
+ }
+
+ /**
+ * A map of AutoBean interface+property names to the Coder for that property.
+ */
+ private static final Map<String, Coder> coderFor = new HashMap<String, Coder>();
+ /**
+ * A map of types to a Coder that handles the type.
+ */
+ private static final Map<Class<?>, Coder> coders = new HashMap<Class<?>, Coder>();
+
+ public static Coder collectionCoder(Class<?> type, Coder elementCoder) {
+ return new CollectionCoder(type, elementCoder);
+ }
+
+ public static Coder doCoderFor(AutoBean<?> bean, String propertyName) {
+ String key = key(bean, propertyName);
+ Coder toReturn = coderFor.get(key);
+ if (toReturn == null) {
+ bean.accept(new PropertyCoderCreator());
+ toReturn = coderFor.get(key);
+ if (toReturn == null) {
+ throw new IllegalArgumentException(propertyName);
+ }
+ }
+ return toReturn;
+ }
+
+ public static <T> AutoBean<T> doDecode(EncodeState state, Class<T> clazz, Splittable data) {
+ /*
+ * If we decode the same Splittable twice, re-use the ProxyAutoBean to
+ * maintain referential integrity. If we didn't do this, either facade would
+ * update the same backing data, yet not be the same object via ==
+ * comparison.
+ */
+ @SuppressWarnings("unchecked")
+ AutoBean<T> toReturn = (AutoBean<T>) data.getReified(AutoBeanCodexImpl.class.getName());
+ if (toReturn != null) {
+ return toReturn;
+ }
+ toReturn = state.factory.create(clazz);
+ data.setReified(AutoBeanCodexImpl.class.getName(), toReturn);
+ if (toReturn == null) {
+ throw new IllegalArgumentException(clazz.getName());
+ }
+ ((AbstractAutoBean<T>) toReturn).setData(data);
+ return toReturn;
+ }
+
+ public static void doDecodeInto(EncodeState state, Splittable data, AutoBean<?> bean) {
+ new PropertySetter().decodeInto(state, data, bean);
+ }
+
+ public static void doEncode(EncodeState state, AutoBean<?> bean) {
+ PropertyGetter e = new PropertyGetter(state);
+ try {
+ bean.accept(e);
+ } catch (HaltException ex) {
+ throw ex.getCause();
+ }
+ }
+
+ public static <E extends Enum<?>> Coder enumCoder(Class<E> type) {
+ Coder toReturn = coders.get(type);
+ if (toReturn == null) {
+ toReturn = new EnumCoder<E>(type);
+ coders.put(type, toReturn);
+ }
+ return toReturn;
+ }
+
+ public static Coder mapCoder(Coder valueCoder, Coder keyCoder) {
+ return new MapCoder(valueCoder, keyCoder);
+ }
+
+ public static Coder objectCoder(Class<?> type) {
+ Coder toReturn = coders.get(type);
+ if (toReturn == null) {
+ toReturn = new ObjectCoder(type);
+ coders.put(type, toReturn);
+ }
+ return toReturn;
+ }
+
+ public static Coder splittableCoder() {
+ return SplittableCoder.INSTANCE;
+ }
+
+ public static Coder valueCoder(Class<?> type) {
+ Coder toReturn = coders.get(type);
+ if (toReturn == null) {
+ toReturn = new ValueCoder(type);
+ coders.put(type, toReturn);
+ }
+ return toReturn;
+ }
+
+ static Splittable tryExtractSplittable(Object value) {
+ AutoBean<?> bean = AutoBeanUtils.getAutoBean(value);
+ if (bean != null) {
+ value = bean;
+ }
+ if (bean instanceof HasSplittable) {
+ return ((HasSplittable) bean).getSplittable();
+ }
+ return null;
+ }
+
+ private static String key(AutoBean<?> bean, String propertyName) {
+ return bean.getType().getName() + ":" + propertyName;
+ }
+}
diff --git a/user/src/com/google/gwt/autobean/shared/impl/EnumMap.java b/user/src/com/google/gwt/autobean/shared/impl/EnumMap.java
index 3b53d1b..0a3d298 100644
--- a/user/src/com/google/gwt/autobean/shared/impl/EnumMap.java
+++ b/user/src/com/google/gwt/autobean/shared/impl/EnumMap.java
@@ -27,7 +27,7 @@
Class<? extends Enum<?>>[] value();
}
- <E extends Enum<E>> E getEnum(Class<E> clazz, String token);
+ <E extends Enum<?>> E getEnum(Class<E> clazz, String token);
String getToken(Enum<?> e);
}
diff --git a/user/src/com/google/gwt/autobean/shared/impl/HasSplittable.java b/user/src/com/google/gwt/autobean/shared/impl/HasSplittable.java
new file mode 100644
index 0000000..7ae4a33
--- /dev/null
+++ b/user/src/com/google/gwt/autobean/shared/impl/HasSplittable.java
@@ -0,0 +1,26 @@
+/*
+ * 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.autobean.shared.impl;
+
+import com.google.gwt.autobean.shared.Splittable;
+
+/**
+ * Allows reified type facades to return their underlying Splittable
+ * datastructure.
+ */
+public interface HasSplittable {
+ Splittable getSplittable();
+}
diff --git a/user/src/com/google/gwt/autobean/shared/impl/LazySplittable.java b/user/src/com/google/gwt/autobean/shared/impl/LazySplittable.java
deleted file mode 100644
index 2fb4749..0000000
--- a/user/src/com/google/gwt/autobean/shared/impl/LazySplittable.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * 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.gwt.autobean.shared.impl;
-
-import com.google.gwt.autobean.shared.Splittable;
-
-import java.util.List;
-
-/**
- * Holds a string payload with the expectation that the object will be used only
- * for creating a larger payload.
- */
-public class LazySplittable implements Splittable {
- public static final Splittable NULL = new LazySplittable("null");
-
- private final String payload;
- private Splittable split;
-
- public LazySplittable(String payload) {
- this.payload = payload;
- }
-
- public String asString() {
- maybeSplit();
- return split.asString();
- }
-
- public Splittable get(int index) {
- maybeSplit();
- return split.get(index);
- }
-
- public Splittable get(String key) {
- maybeSplit();
- return split.get(key);
- }
-
- public String getPayload() {
- return payload;
- }
-
- public List<String> getPropertyKeys() {
- maybeSplit();
- return split.getPropertyKeys();
- }
-
- public boolean isIndexed() {
- return payload.charAt(0) == '[';
- }
-
- public boolean isKeyed() {
- return payload.charAt(0) == '{';
- }
-
- public boolean isNull(int index) {
- maybeSplit();
- return split.isNull(index);
- }
-
- public boolean isNull(String key) {
- maybeSplit();
- return split.isNull(key);
- }
-
- public boolean isString() {
- return payload.charAt(0) == '\"';
- }
-
- public int size() {
- maybeSplit();
- return split.size();
- }
-
- private void maybeSplit() {
- if (split == null) {
- split = StringQuoter.split(payload);
- }
- }
-}
diff --git a/user/src/com/google/gwt/autobean/shared/impl/SplittableComplexMap.java b/user/src/com/google/gwt/autobean/shared/impl/SplittableComplexMap.java
new file mode 100644
index 0000000..4225512
--- /dev/null
+++ b/user/src/com/google/gwt/autobean/shared/impl/SplittableComplexMap.java
@@ -0,0 +1,200 @@
+/*
+ * 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.autobean.shared.impl;
+
+import com.google.gwt.autobean.shared.Splittable;
+import com.google.gwt.autobean.shared.impl.AutoBeanCodexImpl.Coder;
+import com.google.gwt.autobean.shared.impl.AutoBeanCodexImpl.EncodeState;
+
+import java.util.AbstractCollection;
+import java.util.AbstractSet;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A Map implementation for complex keys.
+ *
+ * @param <K> the key type
+ * @param <V> the value type
+ */
+public class SplittableComplexMap<K, V> implements Map<K, V>, HasSplittable {
+ private final Splittable data;
+ private final List<K> keys;
+ private final List<V> values;
+
+ public SplittableComplexMap(Splittable data, Coder keyCoder, Coder valueCoder, EncodeState state) {
+ this.data = data;
+ this.keys = new SplittableList<K>(data.get(0), keyCoder, state);
+ this.values = new SplittableList<V>(data.get(1), valueCoder, state);
+ assert this.keys.size() == this.values.size();
+ }
+
+ public void clear() {
+ // Trigger ConcurrentModificationExceptions for any outstanding Iterators
+ keys.clear();
+ values.clear();
+ }
+
+ public boolean containsKey(Object key) {
+ return keys.contains(key);
+ }
+
+ public boolean containsValue(Object value) {
+ return values.contains(value);
+ }
+
+ public Set<java.util.Map.Entry<K, V>> entrySet() {
+ return new AbstractSet<Map.Entry<K, V>>() {
+
+ @Override
+ public Iterator<java.util.Map.Entry<K, V>> iterator() {
+ return new Iterator<Map.Entry<K, V>>() {
+ Iterator<K> keyIt = keys.iterator();
+ ListIterator<V> valueIt = values.listIterator();
+
+ public boolean hasNext() {
+ assert keyIt.hasNext() == valueIt.hasNext();
+ return keyIt.hasNext();
+ }
+
+ public java.util.Map.Entry<K, V> next() {
+ return new Map.Entry<K, V>() {
+ final K key = keyIt.next();
+ final V value = valueIt.next();
+
+ public K getKey() {
+ return key;
+ }
+
+ public V getValue() {
+ return value;
+ }
+
+ public V setValue(V value) {
+ valueIt.set(value);
+ return value;
+ }
+ };
+ }
+
+ public void remove() {
+ keyIt.remove();
+ valueIt.remove();
+ }
+ };
+ }
+
+ @Override
+ public int size() {
+ return keys.size();
+ }
+ };
+ }
+
+ public V get(Object key) {
+ int idx = keys.indexOf(key);
+ if (idx == -1) {
+ return null;
+ }
+ return values.get(idx);
+ }
+
+ public Splittable getSplittable() {
+ return data;
+ }
+
+ public boolean isEmpty() {
+ return keys.isEmpty();
+ }
+
+ public Set<K> keySet() {
+ return new AbstractSet<K>() {
+ @Override
+ public Iterator<K> iterator() {
+ return keys.iterator();
+ }
+
+ @Override
+ public int size() {
+ return keys.size();
+ }
+ };
+ }
+
+ public V put(K key, V value) {
+ int idx = keys.indexOf(key);
+ if (idx == -1) {
+ keys.add(key);
+ values.add(value);
+ return null;
+ }
+ return values.set(idx, value);
+ }
+
+ public void putAll(Map<? extends K, ? extends V> m) {
+ for (Map.Entry<? extends K, ? extends V> entry : m.entrySet()) {
+ put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ public V remove(Object key) {
+ int idx = keys.indexOf(key);
+ if (idx == -1) {
+ return null;
+ }
+ keys.remove(idx);
+ return values.remove(idx);
+ }
+
+ public int size() {
+ return keys.size();
+ }
+
+ public Collection<V> values() {
+ return new AbstractCollection<V>() {
+ @Override
+ public Iterator<V> iterator() {
+ return new Iterator<V>() {
+ final Iterator<K> keyIt = keys.iterator();
+ final Iterator<V> valueIt = values.iterator();
+
+ public boolean hasNext() {
+ return keyIt.hasNext();
+ }
+
+ public V next() {
+ keyIt.next();
+ return valueIt.next();
+ }
+
+ public void remove() {
+ keyIt.remove();
+ valueIt.remove();
+ }
+ };
+ }
+
+ @Override
+ public int size() {
+ return keys.size();
+ }
+ };
+ }
+}
diff --git a/user/src/com/google/gwt/autobean/shared/impl/SplittableList.java b/user/src/com/google/gwt/autobean/shared/impl/SplittableList.java
new file mode 100644
index 0000000..0a7df50
--- /dev/null
+++ b/user/src/com/google/gwt/autobean/shared/impl/SplittableList.java
@@ -0,0 +1,114 @@
+/*
+ * 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.autobean.shared.impl;
+
+import com.google.gwt.autobean.shared.Splittable;
+import com.google.gwt.autobean.shared.impl.AutoBeanCodexImpl.Coder;
+import com.google.gwt.autobean.shared.impl.AutoBeanCodexImpl.EncodeState;
+
+import java.util.AbstractList;
+
+/**
+ * A list implementation that lazily reifies its constituent elements.
+ *
+ * @param <E> the element type
+ */
+public class SplittableList<E> extends AbstractList<E> implements HasSplittable {
+ static <Q> Q reify(EncodeState state, Splittable data, int index, Coder coder) {
+ if (data.isNull(index)) {
+ return null;
+ }
+ @SuppressWarnings("unchecked")
+ Q toReturn = (Q) coder.decode(state, data.get(index));
+ data.setReified(String.valueOf(index), toReturn);
+ return toReturn;
+ }
+
+ static void set(EncodeState state, Splittable data, int index, Coder coder, Object value) {
+ data.setReified(String.valueOf(index), value);
+ if (value == null) {
+ Splittable.NULL.assign(data, index);
+ return;
+ }
+ Splittable backing = coder.extractSplittable(state, value);
+ if (backing == null) {
+ /*
+ * External data type, such as an ArrayList or a concrete implementation
+ * of a setter's interface type. This means that a slow serialization pass
+ * is necessary.
+ */
+ data.setReified(AbstractAutoBean.UNSPLITTABLE_VALUES_KEY, true);
+ } else {
+ backing.assign(data, index);
+ }
+ }
+
+ private Splittable data;
+ private final Coder elementCoder;
+ private final EncodeState state;
+
+ public SplittableList(Splittable data, Coder elementCoder, EncodeState state) {
+ assert data.isIndexed() : "Expecting indexed data";
+ this.data = data;
+ this.elementCoder = elementCoder;
+ this.state = state;
+ }
+
+ @Override
+ public void add(int index, E element) {
+ set(state, data, index, elementCoder, element);
+ }
+
+ @Override
+ public E get(int index) {
+ if (data.isReified(String.valueOf(index))) {
+ @SuppressWarnings("unchecked")
+ E toReturn = (E) data.getReified(String.valueOf(index));
+ return toReturn;
+ }
+ // javac generics bug
+ return SplittableList.<E> reify(state, data, index, elementCoder);
+ }
+
+ public Splittable getSplittable() {
+ return data;
+ }
+
+ @Override
+ public E remove(int index) {
+ E toReturn = get(index);
+ // XXX This is terrible, use Array.splice
+ int newSize = data.size() - 1;
+ for (int i = index; i < newSize; i++) {
+ data.get(i + 1).assign(data, i);
+ data.setReified(String.valueOf(i), data.getReified(String.valueOf(i + 1)));
+ }
+ data.setSize(newSize);
+ return toReturn;
+ }
+
+ @Override
+ public E set(int index, E element) {
+ E previous = get(index);
+ set(state, data, index, elementCoder, element);
+ return previous;
+ }
+
+ @Override
+ public int size() {
+ return data.size();
+ }
+}
diff --git a/user/src/com/google/gwt/autobean/shared/impl/SplittableSet.java b/user/src/com/google/gwt/autobean/shared/impl/SplittableSet.java
new file mode 100644
index 0000000..fbd3f5d
--- /dev/null
+++ b/user/src/com/google/gwt/autobean/shared/impl/SplittableSet.java
@@ -0,0 +1,71 @@
+/*
+ * 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.autobean.shared.impl;
+
+import com.google.gwt.autobean.shared.Splittable;
+import com.google.gwt.autobean.shared.impl.AutoBeanCodexImpl.Coder;
+import com.google.gwt.autobean.shared.impl.AutoBeanCodexImpl.EncodeState;
+
+import java.util.AbstractSet;
+import java.util.Iterator;
+
+/**
+ * This type is optimized for the read-only case and has {@code O(n)} insertion
+ * / lookup performance since computing hashcodes for the elements would require
+ * up-front reification.
+ *
+ * @param <E> the element type
+ */
+public class SplittableSet<E> extends AbstractSet<E> implements HasSplittable {
+ private SplittableList<E> data;
+
+ public SplittableSet(Splittable data, Coder elementCoder, EncodeState state) {
+ this.data = new SplittableList<E>(data, elementCoder, state);
+ }
+
+ @Override
+ public boolean add(E e) {
+ if (!data.contains(e)) {
+ data.add(e);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void clear() {
+ data.clear();
+ }
+
+ public Splittable getSplittable() {
+ return data.getSplittable();
+ }
+
+ @Override
+ public Iterator<E> iterator() {
+ return data.iterator();
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ return data.remove(o);
+ }
+
+ @Override
+ public int size() {
+ return data.size();
+ }
+}
diff --git a/user/src/com/google/gwt/autobean/shared/impl/SplittableSimpleMap.java b/user/src/com/google/gwt/autobean/shared/impl/SplittableSimpleMap.java
new file mode 100644
index 0000000..edfeff4
--- /dev/null
+++ b/user/src/com/google/gwt/autobean/shared/impl/SplittableSimpleMap.java
@@ -0,0 +1,255 @@
+/*
+ * 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.autobean.shared.impl;
+
+import com.google.gwt.autobean.shared.Splittable;
+import com.google.gwt.autobean.shared.impl.AutoBeanCodexImpl.Coder;
+import com.google.gwt.autobean.shared.impl.AutoBeanCodexImpl.EncodeState;
+
+import java.util.AbstractCollection;
+import java.util.AbstractSet;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A Map implementation for regular JSON maps with value-type keys.
+ *
+ * @param <K> the key type
+ * @param <V> the value type
+ */
+public class SplittableSimpleMap<K, V> implements Map<K, V>, HasSplittable {
+ private final Splittable data;
+ private final Coder keyCoder;
+ private final EncodeState state;
+ private final Coder valueCoder;
+ /**
+ * Don't hang the reified data from {@link #data} since we can't tell the
+ * __reified field from the actual data.
+ */
+ private Splittable reified = StringQuoter.createSplittable();
+
+ public SplittableSimpleMap(Splittable data, Coder keyCoder, Coder valueCoder, EncodeState state) {
+ this.data = data;
+ this.keyCoder = keyCoder;
+ this.state = state;
+ this.valueCoder = valueCoder;
+ }
+
+ public void clear() {
+ for (String key : data.getPropertyKeys()) {
+ Splittable.NULL.assign(data, key);
+ reified.setReified(key, null);
+ }
+ }
+
+ public boolean containsKey(Object key) {
+ String encodedKey = encodedKey(key);
+ return !data.isUndefined(encodedKey) || reified.isReified(encodedKey);
+ }
+
+ public boolean containsValue(Object value) {
+ return values().contains(value);
+ }
+
+ public Set<java.util.Map.Entry<K, V>> entrySet() {
+ return new AbstractSet<Map.Entry<K, V>>() {
+ final List<String> keys = data.getPropertyKeys();
+
+ @Override
+ public Iterator<java.util.Map.Entry<K, V>> iterator() {
+ return new Iterator<Map.Entry<K, V>>() {
+ Iterator<String> keyIterator = keys.iterator();
+ String encodedKey;
+
+ public boolean hasNext() {
+ return keyIterator.hasNext();
+ }
+
+ public java.util.Map.Entry<K, V> next() {
+ encodedKey = keyIterator.next();
+ return new Map.Entry<K, V>() {
+ @SuppressWarnings("unchecked")
+ final K key = (K) keyCoder.decode(state, StringQuoter.split(StringQuoter
+ .quote(encodedKey)));
+ @SuppressWarnings("unchecked")
+ final V value = (V) valueCoder.decode(state, data.get(encodedKey));
+
+ public K getKey() {
+ return key;
+ }
+
+ public V getValue() {
+ return value;
+ }
+
+ public V setValue(V newValue) {
+ return put(key, newValue);
+ }
+ };
+ }
+
+ public void remove() {
+ Splittable.NULL.assign(data, encodedKey);
+ reified.setReified(encodedKey, null);
+ }
+ };
+ }
+
+ @Override
+ public int size() {
+ return keys.size();
+ }
+ };
+ }
+
+ public V get(Object key) {
+ String encodedKey = encodedKey(key);
+ return getRaw(encodedKey);
+ }
+
+ public Splittable getSplittable() {
+ return data;
+ }
+
+ public boolean isEmpty() {
+ return data.getPropertyKeys().isEmpty();
+ }
+
+ public Set<K> keySet() {
+ return new AbstractSet<K>() {
+ final List<String> keys = data.getPropertyKeys();
+
+ @Override
+ public Iterator<K> iterator() {
+ return new Iterator<K>() {
+ final Iterator<String> it = keys.iterator();
+ String lastEncodedKey;
+
+ public boolean hasNext() {
+ return it.hasNext();
+ }
+
+ public K next() {
+ lastEncodedKey = it.next();
+ @SuppressWarnings("unchecked")
+ K toReturn =
+ (K) keyCoder.decode(state, StringQuoter.split(StringQuoter.quote(lastEncodedKey)));
+ return toReturn;
+ }
+
+ public void remove() {
+ Splittable.NULL.assign(data, lastEncodedKey);
+ reified.setReified(lastEncodedKey, null);
+ }
+ };
+ }
+
+ @Override
+ public int size() {
+ return keys.size();
+ }
+ };
+ }
+
+ public V put(K key, V value) {
+ V toReturn = get(key);
+ String encodedKey = encodedKey(key);
+ reified.setReified(encodedKey, value);
+ Splittable encodedValue = valueCoder.extractSplittable(state, value);
+ if (encodedValue == null) {
+ // External datastructure
+ reified.setReified(AbstractAutoBean.UNSPLITTABLE_VALUES_KEY, true);
+ } else {
+ encodedValue.assign(data, encodedKey);
+ }
+ return toReturn;
+ }
+
+ public void putAll(Map<? extends K, ? extends V> m) {
+ for (Map.Entry<? extends K, ? extends V> entry : m.entrySet()) {
+ put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ public V remove(Object key) {
+ V toReturn = get(key);
+ String encodedKey = encodedKey(key);
+ reified.setReified(encodedKey, null);
+ Splittable.NULL.assign(data, encodedKey);
+ return toReturn;
+ }
+
+ public int size() {
+ return data.getPropertyKeys().size();
+ }
+
+ public Collection<V> values() {
+ return new AbstractCollection<V>() {
+ final List<String> keys = data.getPropertyKeys();
+
+ @Override
+ public Iterator<V> iterator() {
+ return new Iterator<V>() {
+ final Iterator<String> it = keys.iterator();
+ String lastEncodedKey;
+
+ public boolean hasNext() {
+ return it.hasNext();
+ }
+
+ public V next() {
+ lastEncodedKey = it.next();
+ return getRaw(lastEncodedKey);
+ }
+
+ public void remove() {
+ Splittable.NULL.assign(data, lastEncodedKey);
+ reified.setReified(lastEncodedKey, null);
+ }
+ };
+ }
+
+ @Override
+ public int size() {
+ return keys.size();
+ }
+ };
+ }
+
+ private String encodedKey(Object key) {
+ return keyCoder.extractSplittable(state, key).asString();
+ }
+
+ private V getRaw(String encodedKey) {
+ if (reified.isReified(encodedKey)) {
+ @SuppressWarnings("unchecked")
+ V toReturn = (V) reified.getReified(encodedKey);
+ return toReturn;
+ }
+ // Both undefined or an explicit null should return null here
+ if (data.isNull(encodedKey)) {
+ return null;
+ }
+ Splittable value = data.get(encodedKey);
+ @SuppressWarnings("unchecked")
+ V toReturn = (V) valueCoder.decode(state, value);
+ reified.setReified(encodedKey, toReturn);
+ return toReturn;
+ }
+}
diff --git a/user/src/com/google/gwt/autobean/shared/impl/StringQuoter.java b/user/src/com/google/gwt/autobean/shared/impl/StringQuoter.java
index 899ed52..559e2af 100644
--- a/user/src/com/google/gwt/autobean/shared/impl/StringQuoter.java
+++ b/user/src/com/google/gwt/autobean/shared/impl/StringQuoter.java
@@ -31,12 +31,36 @@
*/
public class StringQuoter {
private static final String ISO8601_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSz";
- private static final DateFormat ISO8601 = new SimpleDateFormat(
- ISO8601_PATTERN, Locale.getDefault());
+ private static final DateFormat ISO8601 = new SimpleDateFormat(ISO8601_PATTERN, Locale
+ .getDefault());
private static final String RFC2822_PATTERN = "EEE, d MMM yyyy HH:mm:ss Z";
- private static final DateFormat RFC2822 = new SimpleDateFormat(
- RFC2822_PATTERN, Locale.getDefault());
+ private static final DateFormat RFC2822 = new SimpleDateFormat(RFC2822_PATTERN, Locale
+ .getDefault());
+
+ public static Splittable create(boolean value) {
+ return JsonSplittable.create(String.valueOf(value));
+ }
+
+ public static Splittable create(double value) {
+ return JsonSplittable.create(String.valueOf(value));
+ }
+
+ public static Splittable create(String value) {
+ return JsonSplittable.create(quote(value));
+ }
+
+ public static Splittable createIndexed() {
+ return JsonSplittable.createIndexed();
+ }
+
+ public static Splittable createSplittable() {
+ return JsonSplittable.create();
+ }
+
+ public static Splittable nullValue() {
+ return JsonSplittable.createNull();
+ }
/**
* Create a quoted JSON string.
diff --git a/user/src/com/google/gwt/core/client/JsonUtils.java b/user/src/com/google/gwt/core/client/JsonUtils.java
index e44bb35..7088266 100644
--- a/user/src/com/google/gwt/core/client/JsonUtils.java
+++ b/user/src/com/google/gwt/core/client/JsonUtils.java
@@ -60,17 +60,17 @@
try {
return JSON.parse(json);
} catch (e) {
- return @com.google.gwt.core.client.JsonUtils::throwIllegalArgumentException(Ljava/lang/String;)("Error parsing JSON: " + e);
+ return @com.google.gwt.core.client.JsonUtils::throwIllegalArgumentException(*)("Error parsing JSON: " + e, json);
}
} else {
if (!@com.google.gwt.core.client.JsonUtils::safeToEval(Ljava/lang/String;)(json)) {
- return @com.google.gwt.core.client.JsonUtils::throwIllegalArgumentException(Ljava/lang/String;)("Illegal character in JSON string");
+ return @com.google.gwt.core.client.JsonUtils::throwIllegalArgumentException(*)("Illegal character in JSON string", json);
}
json = @com.google.gwt.core.client.JsonUtils::escapeJsonForEval(Ljava/lang/String;)(json);
try {
return eval('(' + json + ')');
} catch (e) {
- return @com.google.gwt.core.client.JsonUtils::throwIllegalArgumentException(Ljava/lang/String;)("Error parsing JSON: " + e);
+ return @com.google.gwt.core.client.JsonUtils::throwIllegalArgumentException(*)("Error parsing JSON: " + e, json);
}
}
}-*/;
@@ -110,12 +110,12 @@
try {
return eval('(' + escaped + ')');
} catch (e) {
- return @com.google.gwt.core.client.JsonUtils::throwIllegalArgumentException(Ljava/lang/String;)("Error parsing JSON: " + e);
+ return @com.google.gwt.core.client.JsonUtils::throwIllegalArgumentException(*)("Error parsing JSON: " + e, json);
}
}-*/;
- static void throwIllegalArgumentException(String message) {
- throw new IllegalArgumentException(message);
+ static void throwIllegalArgumentException(String message, String data) {
+ throw new IllegalArgumentException(message + "\n" + data);
}
private static native String escapeChar(String c) /*-{
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequestContext.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequestContext.java
index 6487320..429dd6f 100644
--- a/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequestContext.java
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequestContext.java
@@ -26,8 +26,8 @@
import com.google.gwt.autobean.shared.AutoBeanVisitor;
import com.google.gwt.autobean.shared.Splittable;
import com.google.gwt.autobean.shared.ValueCodex;
+import com.google.gwt.autobean.shared.impl.AbstractAutoBean;
import com.google.gwt.autobean.shared.impl.EnumMap;
-import com.google.gwt.autobean.shared.impl.LazySplittable;
import com.google.gwt.autobean.shared.impl.StringQuoter;
import com.google.gwt.event.shared.UmbrellaException;
import com.google.web.bindery.requestfactory.shared.BaseProxy;
@@ -36,13 +36,13 @@
import com.google.web.bindery.requestfactory.shared.EntityProxyId;
import com.google.web.bindery.requestfactory.shared.Receiver;
import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.RequestTransport.TransportReceiver;
import com.google.web.bindery.requestfactory.shared.ServerFailure;
-import com.google.web.bindery.requestfactory.shared.ValueProxy;
import com.google.web.bindery.requestfactory.shared.Violation;
import com.google.web.bindery.requestfactory.shared.WriteOperation;
-import com.google.web.bindery.requestfactory.shared.RequestTransport.TransportReceiver;
import com.google.web.bindery.requestfactory.shared.impl.posers.DatePoser;
import com.google.web.bindery.requestfactory.shared.messages.IdMessage;
+import com.google.web.bindery.requestfactory.shared.messages.IdMessage.Strength;
import com.google.web.bindery.requestfactory.shared.messages.InvocationMessage;
import com.google.web.bindery.requestfactory.shared.messages.JsonRpcRequest;
import com.google.web.bindery.requestfactory.shared.messages.MessageFactory;
@@ -51,7 +51,6 @@
import com.google.web.bindery.requestfactory.shared.messages.ResponseMessage;
import com.google.web.bindery.requestfactory.shared.messages.ServerFailureMessage;
import com.google.web.bindery.requestfactory.shared.messages.ViolationMessage;
-import com.google.web.bindery.requestfactory.shared.messages.IdMessage.Strength;
import java.util.ArrayList;
import java.util.Collection;
@@ -67,8 +66,7 @@
/**
* Base implementations for RequestContext services.
*/
-public abstract class AbstractRequestContext implements RequestContext,
- EntityCodex.EntitySource {
+public abstract class AbstractRequestContext implements RequestContext, EntityCodex.EntitySource {
/**
* Allows the payload dialect to be injected into the AbstractRequestContext
* without the caller needing to be concerned with how the implementation
@@ -110,8 +108,7 @@
* DialectImpl interface and restored to to AbstractRequestContext.
*/
if (!invocations.isEmpty()) {
- throw new RuntimeException(
- "Only one invocation per request, pending backend support");
+ throw new RuntimeException("Only one invocation per request, pending backend support");
}
invocations.add(request);
for (Object arg : request.getRequestData().getOrderedParameters()) {
@@ -152,20 +149,23 @@
if (!raw.isNull("error")) {
Splittable error = raw.get("error");
- ServerFailure failure = new ServerFailure(
- error.get("message").asString(), error.get("code").asString(),
- payload, true);
+ ServerFailure failure =
+ new ServerFailure(error.get("message").asString(), error.get("code").asString(),
+ payload, true);
fail(receiver, failure);
return;
}
Splittable result = raw.get("result");
@SuppressWarnings("unchecked")
- Class<BaseProxy> target = (Class<BaseProxy>) invocations.get(0).getRequestData().getReturnType();
+ Class<BaseProxy> target =
+ (Class<BaseProxy>) invocations.get(0).getRequestData().getReturnType();
SimpleProxyId<BaseProxy> id = getRequestFactory().allocateId(target);
AutoBean<BaseProxy> bean = createProxy(target, id);
- AutoBeanCodex.decodeInto(result, bean);
+ // XXX expose this as a proper API
+ ((AbstractAutoBean<?>) bean).setData(result);
+ // AutoBeanCodex.decodeInto(result, bean);
if (callback != null) {
callback.onSuccess(bean.as());
@@ -178,9 +178,8 @@
Splittable encode(Object obj) {
Splittable value;
if (obj == null) {
- return LazySplittable.NULL;
- } else if (obj.getClass().isEnum()
- && getAutoBeanFactory() instanceof EnumMap) {
+ return Splittable.NULL;
+ } else if (obj.getClass().isEnum() && getAutoBeanFactory() instanceof EnumMap) {
value = ValueCodex.encode(((EnumMap) getAutoBeanFactory()).getToken((Enum<?>) obj));
} else if (ValueCodex.canDecode(obj.getClass())) {
value = ValueCodex.encode(obj);
@@ -233,13 +232,13 @@
}
public void processPayload(final Receiver<Void> receiver, String payload) {
- ResponseMessage response = AutoBeanCodex.decode(
- MessageFactoryHolder.FACTORY, ResponseMessage.class, payload).as();
+ ResponseMessage response =
+ AutoBeanCodex.decode(MessageFactoryHolder.FACTORY, ResponseMessage.class, payload).as();
if (response.getGeneralFailure() != null) {
ServerFailureMessage failure = response.getGeneralFailure();
- ServerFailure fail = new ServerFailure(failure.getMessage(),
- failure.getExceptionType(), failure.getStackTrace(),
- failure.isFatal());
+ ServerFailure fail =
+ new ServerFailure(failure.getMessage(), failure.getExceptionType(), failure
+ .getStackTrace(), failure.isFatal());
fail(receiver, fail);
return;
@@ -266,13 +265,12 @@
if (response.getStatusCodes().get(i)) {
invocations.get(i).onSuccess(response.getInvocationResults().get(i));
} else {
- ServerFailureMessage failure = AutoBeanCodex.decode(
- MessageFactoryHolder.FACTORY, ServerFailureMessage.class,
- response.getInvocationResults().get(i)).as();
+ ServerFailureMessage failure =
+ AutoBeanCodex.decode(MessageFactoryHolder.FACTORY, ServerFailureMessage.class,
+ response.getInvocationResults().get(i)).as();
invocations.get(i).onFail(
- new ServerFailure(failure.getMessage(),
- failure.getExceptionType(), failure.getStackTrace(),
- failure.isFatal()));
+ new ServerFailure(failure.getMessage(), failure.getExceptionType(), failure
+ .getStackTrace(), failure.isFatal()));
}
} catch (Throwable t) {
if (causes == null) {
@@ -368,12 +366,14 @@
* Objects are placed into this map by being passed into {@link #edit} or as
* an invocation argument.
*/
- private final Map<SimpleProxyId<?>, AutoBean<? extends BaseProxy>> editedProxies = new LinkedHashMap<SimpleProxyId<?>, AutoBean<? extends BaseProxy>>();
+ private final Map<SimpleProxyId<?>, AutoBean<? extends BaseProxy>> editedProxies =
+ new LinkedHashMap<SimpleProxyId<?>, AutoBean<? extends BaseProxy>>();
/**
* A map that contains the canonical instance of an entity to return in the
* return graph, since this is built from scratch.
*/
- private final Map<SimpleProxyId<?>, AutoBean<?>> returnedProxies = new HashMap<SimpleProxyId<?>, AutoBean<?>>();
+ private final Map<SimpleProxyId<?>, AutoBean<?>> returnedProxies =
+ new HashMap<SimpleProxyId<?>, AutoBean<?>>();
/**
* A map that allows us to handle the case where the server has sent back an
@@ -381,12 +381,12 @@
* client will need to swap out the request-local ids with a regular
* client-allocated id.
*/
- private final Map<Integer, SimpleProxyId<?>> syntheticIds = new HashMap<Integer, SimpleProxyId<?>>();
+ private final Map<Integer, SimpleProxyId<?>> syntheticIds =
+ new HashMap<Integer, SimpleProxyId<?>>();
private final DialectImpl dialect;
- protected AbstractRequestContext(AbstractRequestFactory factory,
- Dialect dialect) {
+ protected AbstractRequestContext(AbstractRequestFactory factory, Dialect dialect) {
this.requestFactory = factory;
this.dialect = dialect.create(this);
}
@@ -405,12 +405,10 @@
/**
* Creates a new proxy with an assigned ID.
*/
- public <T extends BaseProxy> AutoBean<T> createProxy(Class<T> clazz,
- SimpleProxyId<T> id) {
+ public <T extends BaseProxy> AutoBean<T> createProxy(Class<T> clazz, SimpleProxyId<T> id) {
AutoBean<T> created = getAutoBeanFactory().create(clazz);
if (created == null) {
- throw new IllegalArgumentException("Unknown proxy type "
- + clazz.getName());
+ throw new IllegalArgumentException("Unknown proxy type " + clazz.getName());
}
created.setTag(STABLE_ID, id);
return created;
@@ -481,10 +479,9 @@
/**
* EntityCodex support.
*/
- public <Q extends BaseProxy> AutoBean<Q> getBeanForPayload(
- Splittable serializedProxyId) {
- IdMessage ref = AutoBeanCodex.decode(MessageFactoryHolder.FACTORY,
- IdMessage.class, serializedProxyId).as();
+ public <Q extends BaseProxy> AutoBean<Q> getBeanForPayload(Splittable serializedProxyId) {
+ IdMessage ref =
+ AutoBeanCodex.decode(MessageFactoryHolder.FACTORY, IdMessage.class, serializedProxyId).as();
@SuppressWarnings("unchecked")
SimpleProxyId<Q> id = (SimpleProxyId<Q>) getId(ref);
return getProxyForReturnPayloadGraph(id);
@@ -566,8 +563,7 @@
protected void fail(Receiver<Void> receiver, ServerFailure failure) {
reuse();
Set<Throwable> causes = null;
- for (AbstractRequest<?> request : new ArrayList<AbstractRequest<?>>(
- invocations)) {
+ for (AbstractRequest<?> request : new ArrayList<AbstractRequest<?>>(invocations)) {
try {
request.onFail(failure);
} catch (Throwable t) {
@@ -606,8 +602,7 @@
protected void violation(final Receiver<Void> receiver, Set<Violation> errors) {
reuse();
Set<Throwable> causes = null;
- for (AbstractRequest<?> request : new ArrayList<AbstractRequest<?>>(
- invocations)) {
+ for (AbstractRequest<?> request : new ArrayList<AbstractRequest<?>>(invocations)) {
try {
request.onViolation(errors);
} catch (Throwable t) {
@@ -640,16 +635,14 @@
if (Strength.SYNTHETIC.equals(op.getStrength())) {
return allocateSyntheticId(op.getTypeToken(), op.getSyntheticId());
}
- return requestFactory.getId(op.getTypeToken(), op.getServerId(),
- op.getClientId());
+ return requestFactory.getId(op.getTypeToken(), op.getServerId(), op.getClientId());
}
/**
* Creates or retrieves a new canonical AutoBean to represent the given id in
* the returned payload.
*/
- <Q extends BaseProxy> AutoBean<Q> getProxyForReturnPayloadGraph(
- SimpleProxyId<Q> id) {
+ <Q extends BaseProxy> AutoBean<Q> getProxyForReturnPayloadGraph(SimpleProxyId<Q> id) {
@SuppressWarnings("unchecked")
AutoBean<Q> bean = (AutoBean<Q>) returnedProxies.get(id);
if (bean == null) {
@@ -665,8 +658,8 @@
* Create a single OperationMessage that encapsulates the state of a proxy
* AutoBean.
*/
- AutoBean<OperationMessage> makeOperationMessage(
- SimpleProxyId<BaseProxy> stableId, AutoBean<?> proxyBean, boolean useDelta) {
+ AutoBean<OperationMessage> makeOperationMessage(SimpleProxyId<BaseProxy> stableId,
+ AutoBean<?> proxyBean, boolean useDelta) {
// The OperationMessages describes operations on exactly one entity
AutoBean<OperationMessage> toReturn = MessageFactoryHolder.FACTORY.operation();
@@ -710,8 +703,9 @@
Map<String, Object> diff = Collections.emptyMap();
if (isEntityType(stableId.getProxyClass())) {
// Compute what's changed on the client
- diff = useDelta ? AutoBeanUtils.diff(parent, proxyBean)
- : AutoBeanUtils.getAllProperties(proxyBean);
+ diff =
+ useDelta ? AutoBeanUtils.diff(parent, proxyBean) : AutoBeanUtils
+ .getAllProperties(proxyBean);
} else if (isValueType(stableId.getProxyClass())) {
// Send everything
diff = AutoBeanUtils.getAllProperties(proxyBean);
@@ -720,8 +714,7 @@
if (!diff.isEmpty()) {
Map<String, Splittable> propertyMap = new HashMap<String, Splittable>();
for (Map.Entry<String, Object> entry : diff.entrySet()) {
- propertyMap.put(entry.getKey(),
- EntityCodex.encode(this, entry.getValue()));
+ propertyMap.put(entry.getKey(), EntityCodex.encode(this, entry.getValue()));
}
operation.setPropertyMap(propertyMap);
}
@@ -735,8 +728,8 @@
* @param returnRecord the JSON map containing property/value pairs
* @param operations the WriteOperation eventns to broadcast over the EventBus
*/
- <Q extends BaseProxy> Q processReturnOperation(SimpleProxyId<Q> id,
- OperationMessage op, WriteOperation... operations) {
+ <Q extends BaseProxy> Q processReturnOperation(SimpleProxyId<Q> id, OperationMessage op,
+ WriteOperation... operations) {
AutoBean<Q> toMutate = getProxyForReturnPayloadGraph(id);
toMutate.setTag(Constants.VERSION_PROPERTY_B64, op.getVersion());
@@ -746,15 +739,16 @@
// Apply updates
toMutate.accept(new AutoBeanVisitor() {
@Override
- public boolean visitReferenceProperty(String propertyName,
- AutoBean<?> value, PropertyContext ctx) {
+ public boolean visitReferenceProperty(String propertyName, AutoBean<?> value,
+ PropertyContext ctx) {
if (ctx.canSet()) {
if (properties.containsKey(propertyName)) {
Splittable raw = properties.get(propertyName);
- Class<?> elementType = ctx instanceof CollectionPropertyContext
- ? ((CollectionPropertyContext) ctx).getElementType() : null;
- Object decoded = EntityCodex.decode(AbstractRequestContext.this,
- ctx.getType(), elementType, raw);
+ Class<?> elementType =
+ ctx instanceof CollectionPropertyContext ? ((CollectionPropertyContext) ctx)
+ .getElementType() : null;
+ Object decoded =
+ EntityCodex.decode(AbstractRequestContext.this, ctx.getType(), elementType, raw);
ctx.set(decoded);
}
}
@@ -762,8 +756,7 @@
}
@Override
- public boolean visitValueProperty(String propertyName, Object value,
- PropertyContext ctx) {
+ public boolean visitValueProperty(String propertyName, Object value, PropertyContext ctx) {
if (ctx.canSet()) {
if (properties.containsKey(propertyName)) {
Splittable raw = properties.get(propertyName);
@@ -799,8 +792,8 @@
continue;
}
requestFactory.getEventBus().fireEventFromSource(
- new EntityProxyChange<EntityProxy>((EntityProxy) proxy,
- writeOperation), id.getProxyClass());
+ new EntityProxyChange<EntityProxy>((EntityProxy) proxy, writeOperation),
+ id.getProxyClass());
}
}
return proxy;
@@ -811,8 +804,8 @@
*
* @see #syntheticIds
*/
- private <Q extends BaseProxy> SimpleProxyId<Q> allocateSyntheticId(
- String typeToken, int syntheticId) {
+ private <Q extends BaseProxy> SimpleProxyId<Q> allocateSyntheticId(String typeToken,
+ int syntheticId) {
@SuppressWarnings("unchecked")
SimpleProxyId<Q> toReturn = (SimpleProxyId<Q>) syntheticIds.get(syntheticId);
if (toReturn == null) {
@@ -861,19 +854,25 @@
* Shallow-clones an autobean and makes duplicates of the collection types. A
* regular {@link AutoBean#clone} won't duplicate reference properties.
*/
- private <T extends BaseProxy> AutoBean<T> cloneBeanAndCollections(
- AutoBean<T> toClone) {
- AutoBean<T> clone = toClone.clone(false);
+ private <T extends BaseProxy> AutoBean<T> cloneBeanAndCollections(final AutoBean<T> toClone) {
+ AutoBean<T> clone = toClone.getFactory().create(toClone.getType());
+ clone.setTag(STABLE_ID, toClone.getTag(STABLE_ID));
+ clone.setTag(Constants.VERSION_PROPERTY_B64, toClone.getTag(Constants.VERSION_PROPERTY_B64));
/*
* Take ownership here to prevent cycles in value objects from overflowing
* the stack.
*/
takeOwnership(clone);
clone.accept(new AutoBeanVisitor() {
+ final Map<String, Object> values = AutoBeanUtils.getAllProperties(toClone);
@Override
- public boolean visitCollectionProperty(String propertyName,
- AutoBean<Collection<?>> value, CollectionPropertyContext ctx) {
+ public boolean visitCollectionProperty(String propertyName, AutoBean<Collection<?>> value,
+ CollectionPropertyContext ctx) {
+ // javac generics bug
+ value =
+ AutoBeanUtils.<Collection<?>, Collection<?>> getAutoBean((Collection<?>) values
+ .get(propertyName));
if (value != null) {
Collection<Object> collection;
if (List.class == ctx.getType()) {
@@ -885,20 +884,20 @@
throw new IllegalArgumentException(ctx.getType().getName());
}
- if (isValueType(ctx.getElementType())) {
+ if (isValueType(ctx.getElementType()) || isEntityType(ctx.getElementType())) {
/*
- * Value proxies must be cloned upfront, since the value is replaced
- * outright.
+ * Proxies must be edited up-front so that the elements in the
+ * collection have stable identity.
*/
for (Object o : value.as()) {
if (o == null) {
collection.add(null);
} else {
- collection.add(editProxy((ValueProxy) o));
+ collection.add(editProxy((BaseProxy) o));
}
}
} else {
- // For entities and simple values, just alias the values
+ // For simple values, just copy the values
collection.addAll(value.as());
}
@@ -908,21 +907,30 @@
}
@Override
- public boolean visitReferenceProperty(String propertyName,
- AutoBean<?> value, PropertyContext ctx) {
+ public boolean visitReferenceProperty(String propertyName, AutoBean<?> value,
+ PropertyContext ctx) {
+ value = AutoBeanUtils.getAutoBean(values.get(propertyName));
if (value != null) {
- if (isValueType(ctx.getType())) {
+ if (isValueType(ctx.getType()) || isEntityType(ctx.getType())) {
/*
* Value proxies must be cloned upfront, since the value is replaced
* outright.
*/
@SuppressWarnings("unchecked")
- AutoBean<ValueProxy> valueBean = (AutoBean<ValueProxy>) value;
+ AutoBean<BaseProxy> valueBean = (AutoBean<BaseProxy>) value;
ctx.set(editProxy(valueBean.as()));
+ } else {
+ ctx.set(value.as());
}
}
return false;
}
+
+ @Override
+ public boolean visitValueProperty(String propertyName, Object value, PropertyContext ctx) {
+ ctx.set(values.get(propertyName));
+ return false;
+ }
});
return clone;
}
@@ -988,8 +996,7 @@
}
// Parameter values or references
- List<Splittable> parameters = new ArrayList<Splittable>(
- data.getOrderedParameters().length);
+ List<Splittable> parameters = new ArrayList<Splittable>(data.getOrderedParameters().length);
for (Object param : data.getOrderedParameters()) {
parameters.add(EntityCodex.encode(this, param));
}
@@ -1008,8 +1015,8 @@
private List<OperationMessage> makePayloadOperations() {
List<OperationMessage> operations = new ArrayList<OperationMessage>();
for (AutoBean<? extends BaseProxy> currentView : editedProxies.values()) {
- OperationMessage operation = makeOperationMessage(
- BaseProxyCategory.stableId(currentView), currentView, true).as();
+ OperationMessage operation =
+ makeOperationMessage(BaseProxyCategory.stableId(currentView), currentView, true).as();
operations.add(operation);
}
return operations;
@@ -1080,7 +1087,7 @@
*/
private <T extends BaseProxy> T takeOwnership(AutoBean<T> bean) {
editedProxies.put(stableId(bean), bean);
- bean.setTag(REQUEST_CONTEXT, AbstractRequestContext.this);
+ bean.setTag(REQUEST_CONTEXT, this);
return bean.as();
}
}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/EntityCodex.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/EntityCodex.java
index 5566589..8aca80f 100644
--- a/user/src/com/google/web/bindery/requestfactory/shared/impl/EntityCodex.java
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/EntityCodex.java
@@ -19,7 +19,6 @@
import com.google.gwt.autobean.shared.AutoBeanUtils;
import com.google.gwt.autobean.shared.Splittable;
import com.google.gwt.autobean.shared.ValueCodex;
-import com.google.gwt.autobean.shared.impl.LazySplittable;
import com.google.gwt.autobean.shared.impl.StringQuoter;
import com.google.web.bindery.requestfactory.shared.BaseProxy;
import com.google.web.bindery.requestfactory.shared.EntityProxyId;
@@ -42,8 +41,7 @@
* Expects an encoded
* {@link com.google.web.bindery.requestfactory.shared.messages.IdMessage}.
*/
- <Q extends BaseProxy> AutoBean<Q> getBeanForPayload(
- Splittable serializedIdMessage);
+ <Q extends BaseProxy> AutoBean<Q> getBeanForPayload(Splittable serializedIdMessage);
/**
* Should return an encoded
@@ -59,9 +57,9 @@
/**
* Collection support is limited to value types and resolving ids.
*/
- public static Object decode(EntitySource source, Class<?> type,
- Class<?> elementType, Splittable split) {
- if (split == null || split == LazySplittable.NULL) {
+ public static Object decode(EntitySource source, Class<?> type, Class<?> elementType,
+ Splittable split) {
+ if (split == null || split == Splittable.NULL) {
return null;
}
@@ -99,8 +97,7 @@
return collection;
}
- if (source.isEntityType(type) || source.isValueType(type)
- || EntityProxyId.class.equals(type)) {
+ if (source.isEntityType(type) || source.isValueType(type) || EntityProxyId.class.equals(type)) {
return source.getBeanForPayload(split).as();
}
@@ -111,8 +108,8 @@
/**
* Collection support is limited to value types and resolving ids.
*/
- public static Object decode(EntitySource source, Class<?> type,
- Class<?> elementType, String jsonPayload) {
+ public static Object decode(EntitySource source, Class<?> type, Class<?> elementType,
+ String jsonPayload) {
Splittable split = StringQuoter.split(jsonPayload);
return decode(source, type, elementType, split);
}
@@ -122,7 +119,7 @@
*/
public static Splittable encode(EntitySource source, Object value) {
if (value == null) {
- return LazySplittable.NULL;
+ return Splittable.NULL;
}
if (value instanceof Poser<?>) {
@@ -146,7 +143,7 @@
}
}
toReturn.append(']');
- return new LazySplittable(toReturn.toString());
+ return StringQuoter.split(toReturn.toString());
}
if (value instanceof BaseProxy) {
diff --git a/user/super/com/google/gwt/autobean/super/com/google/gwt/autobean/shared/impl/StringQuoter.java b/user/super/com/google/gwt/autobean/super/com/google/gwt/autobean/shared/impl/StringQuoter.java
index cb331b6..bfd0300 100644
--- a/user/super/com/google/gwt/autobean/super/com/google/gwt/autobean/shared/impl/StringQuoter.java
+++ b/user/super/com/google/gwt/autobean/super/com/google/gwt/autobean/shared/impl/StringQuoter.java
@@ -17,6 +17,8 @@
import com.google.gwt.autobean.client.impl.JsoSplittable;
import com.google.gwt.autobean.shared.Splittable;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.GwtScriptOnly;
import com.google.gwt.core.client.JavaScriptException;
import com.google.gwt.core.client.JsDate;
import com.google.gwt.core.client.JsonUtils;
@@ -27,18 +29,44 @@
/**
* This a super-source version with a client-only implementation.
*/
+@GwtScriptOnly
public class StringQuoter {
+ public static Splittable create(boolean value) {
+ return JsoSplittable.create(value);
+ }
+
+ public static Splittable create(double value) {
+ return JsoSplittable.create(value);
+ }
+
+ public static Splittable create(String value) {
+ return JsoSplittable.create(value);
+ }
+
+ public static Splittable createIndexed() {
+ return JsoSplittable.createIndexed();
+ }
+
+ public static Splittable createSplittable() {
+ return JsoSplittable.create();
+ }
+
+ public static Splittable nullValue() {
+ return JsoSplittable.nullValue();
+ }
+
public static String quote(String raw) {
return JsonUtils.escapeValue(raw);
}
public static Splittable split(String payload) {
- boolean isString = payload.charAt(0) == '\"';
- if (isString) {
+ char c = payload.charAt(0);
+ boolean isSimple = c != '{' && c != '[';
+ if (isSimple) {
payload = "[" + payload + "]";
}
- Splittable toReturn = JsoSplittable.create(JsonUtils.safeEval(payload));
- if (isString) {
+ Splittable toReturn = JsonUtils.safeEval(payload).<JsoSplittable> cast();
+ if (isSimple) {
toReturn = toReturn.get(0);
}
return toReturn;
diff --git a/user/test/com/google/gwt/autobean/AutoBeanSuite.java b/user/test/com/google/gwt/autobean/AutoBeanSuite.java
index 491e91c..f8f8d6e 100644
--- a/user/test/com/google/gwt/autobean/AutoBeanSuite.java
+++ b/user/test/com/google/gwt/autobean/AutoBeanSuite.java
@@ -18,7 +18,9 @@
import com.google.gwt.autobean.client.AutoBeanTest;
import com.google.gwt.autobean.server.AutoBeanCodexJreTest;
import com.google.gwt.autobean.server.AutoBeanJreTest;
+import com.google.gwt.autobean.server.SplittableJreTest;
import com.google.gwt.autobean.shared.AutoBeanCodexTest;
+import com.google.gwt.autobean.shared.SplittableTest;
import com.google.gwt.junit.tools.GWTTestSuite;
import junit.framework.Test;
@@ -34,6 +36,8 @@
suite.addTestSuite(AutoBeanCodexTest.class);
suite.addTestSuite(AutoBeanJreTest.class);
suite.addTestSuite(AutoBeanTest.class);
+ suite.addTestSuite(SplittableJreTest.class);
+ suite.addTestSuite(SplittableTest.class);
return suite;
}
}
diff --git a/user/test/com/google/gwt/autobean/client/AutoBeanTest.java b/user/test/com/google/gwt/autobean/client/AutoBeanTest.java
index cc61684..e83e46d 100644
--- a/user/test/com/google/gwt/autobean/client/AutoBeanTest.java
+++ b/user/test/com/google/gwt/autobean/client/AutoBeanTest.java
@@ -263,46 +263,6 @@
assertEquals("Blah", more.as().getString());
}
- public void testClone() {
- AutoBean<Intf> a1 = factory.intf();
-
- Intf i1 = a1.as();
- i1.setInt(42);
-
- AutoBean<Intf> a2 = a1.clone(false);
- Intf i2 = a2.as();
-
- // Copies have the same values
- assertNotSame(i1, i2);
- assertFalse(i1.equals(i2));
- assertEquals(i1.getInt(), i2.getInt());
- assertTrue(AutoBeanUtils.deepEquals(a1, a2));
-
- // Cloned instances do not affect one another
- i1.setInt(41);
- assertEquals(41, i1.getInt());
- assertEquals(42, i2.getInt());
- }
-
- public void testCloneDeep() {
- AutoBean<OtherIntf> a1 = factory.otherIntf();
- OtherIntf o1 = a1.as();
-
- o1.setIntf(factory.intf().as());
- o1.getIntf().setInt(42);
-
- AutoBean<OtherIntf> a2 = a1.clone(true);
- assertTrue(AutoBeanUtils.deepEquals(a1, a2));
-
- OtherIntf o2 = a2.as();
-
- assertNotSame(o1.getIntf(), o2.getIntf());
- assertEquals(o1.getIntf().getInt(), o2.getIntf().getInt());
-
- o1.getIntf().setInt(41);
- assertEquals(42, o2.getIntf().getInt());
- }
-
public void testDiff() {
AutoBean<Intf> a1 = factory.intf();
AutoBean<Intf> a2 = factory.intf();
@@ -315,24 +275,6 @@
assertEquals(42, diff.get("int"));
}
- /**
- * Tests that lists are in fact aliased between AutoBeans after a shallow
- * clone.
- */
- public void testDiffWithCloneAndListMutation() {
- AutoBean<HasList> a1 = factory.hasList();
- List<Intf> list = new ArrayList<Intf>();
- list.add(factory.intf().as());
- a1.as().setList(list);
-
- AutoBean<HasList> a2 = a1.clone(false);
- assertTrue(AutoBeanUtils.diff(a1, a2).isEmpty());
-
- // Lists are aliased between AutoBeans
- assertSame(a1.as().getList(), a2.as().getList());
- assertEquals(a1.as().getList(), a2.as().getList());
- }
-
public void testDiffWithListPropertyAssignment() {
AutoBean<HasList> a1 = factory.hasList();
AutoBean<HasList> a2 = factory.hasList();
@@ -435,14 +377,14 @@
}
@Override
- public void endVisitCollectionProperty(String propertyName,
- AutoBean<Collection<?>> value, CollectionPropertyContext ctx) {
+ public void endVisitCollectionProperty(String propertyName, AutoBean<Collection<?>> value,
+ CollectionPropertyContext ctx) {
check(propertyName, ctx);
}
@Override
- public void endVisitMapProperty(String propertyName,
- AutoBean<Map<?, ?>> value, MapPropertyContext ctx) {
+ public void endVisitMapProperty(String propertyName, AutoBean<Map<?, ?>> value,
+ MapPropertyContext ctx) {
check(propertyName, ctx);
}
@@ -458,14 +400,12 @@
} else if ("listOfMap".equals(propertyName)) {
// List<Map<String, Intf>>
assertEquals(List.class.getName() + "<" + Map.class.getName() + "<"
- + String.class.getName() + "," + Intf.class.getName() + ">>",
- sb.toString());
+ + String.class.getName() + "," + Intf.class.getName() + ">>", sb.toString());
} else if ("map".equals(propertyName)) {
// Map<Map<String, String>, List<List<Intf>>>
assertEquals(Map.class.getName() + "<" + Map.class.getName() + "<"
- + String.class.getName() + "," + String.class.getName() + ">,"
- + List.class.getName() + "<" + List.class.getName() + "<"
- + Intf.class.getName() + ">>>", sb.toString());
+ + String.class.getName() + "," + String.class.getName() + ">," + List.class.getName()
+ + "<" + List.class.getName() + "<" + Intf.class.getName() + ">>>", sb.toString());
} else {
throw new RuntimeException(propertyName);
}
@@ -512,8 +452,8 @@
boolean seenOther;
@Override
- public void endVisitReferenceProperty(String propertyName,
- AutoBean<?> value, PropertyContext ctx) {
+ public void endVisitReferenceProperty(String propertyName, AutoBean<?> value,
+ PropertyContext ctx) {
if ("hasBoolean".equals(propertyName)) {
assertSame(hasBoolean, value);
assertEquals(HasBoolean.class, ctx.getType());
@@ -529,8 +469,7 @@
}
@Override
- public void endVisitValueProperty(String propertyName, Object value,
- PropertyContext ctx) {
+ public void endVisitValueProperty(String propertyName, Object value, PropertyContext ctx) {
if ("int".equals(propertyName)) {
assertEquals(42, value);
assertEquals(int.class, ctx.getType());
diff --git a/user/test/com/google/gwt/autobean/server/SplittableJreTest.java b/user/test/com/google/gwt/autobean/server/SplittableJreTest.java
new file mode 100644
index 0000000..af1c93a
--- /dev/null
+++ b/user/test/com/google/gwt/autobean/server/SplittableJreTest.java
@@ -0,0 +1,28 @@
+/*
+ * 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.autobean.server;
+
+import com.google.gwt.autobean.shared.SplittableTest;
+
+/**
+ * A JRE-only version of SplittableTest.
+ */
+public class SplittableJreTest extends SplittableTest {
+ @Override
+ public String getModuleName() {
+ return null;
+ }
+}
diff --git a/user/test/com/google/gwt/autobean/shared/AutoBeanCodexTest.java b/user/test/com/google/gwt/autobean/shared/AutoBeanCodexTest.java
index 8e686b8..43bfb35 100644
--- a/user/test/com/google/gwt/autobean/shared/AutoBeanCodexTest.java
+++ b/user/test/com/google/gwt/autobean/shared/AutoBeanCodexTest.java
@@ -200,7 +200,7 @@
assertEquals(MyEnum.BAR, map.getEnum(MyEnum.class, "BAR"));
assertEquals(MyEnum.BAZ, map.getEnum(MyEnum.class, "quux"));
- List<MyEnum> arrayValue = Arrays.asList(MyEnum.FOO, MyEnum.BAR, MyEnum.BAZ);
+ List<MyEnum> arrayValue = Arrays.asList(MyEnum.FOO, MyEnum.BAR, null, MyEnum.BAZ);
Map<MyEnum, Integer> mapValue = new HashMap<MyEnum, Integer>();
mapValue.put(MyEnum.FOO, 0);
mapValue.put(MyEnum.BAR, 1);
@@ -219,6 +219,13 @@
assertEquals(MyEnum.BAZ, decoded.as().getEnum());
assertEquals(arrayValue, decoded.as().getEnums());
assertEquals(mapValue, decoded.as().getMap());
+
+ assertEquals(MyEnum.BAZ, AutoBeanUtils.getAllProperties(bean).get("enum"));
+ bean.as().setEnum(null);
+ assertNull(bean.as().getEnum());
+ assertNull(AutoBeanUtils.getAllProperties(bean).get("enum"));
+ decoded = checkEncode(bean);
+ assertNull(decoded.as().getEnum());
}
/**
@@ -229,14 +236,13 @@
EnumMap map = (EnumMap) f;
assertEquals("FOO_LIST", map.getToken(EnumReachableThroughList.FOO_LIST));
assertEquals("FOO_KEY", map.getToken(EnumReachableThroughMapKey.FOO_KEY));
- assertEquals("FOO_VALUE",
- map.getToken(EnumReachableThroughMapValue.FOO_VALUE));
- assertEquals(EnumReachableThroughList.FOO_LIST,
- map.getEnum(EnumReachableThroughList.class, "FOO_LIST"));
- assertEquals(EnumReachableThroughMapKey.FOO_KEY,
- map.getEnum(EnumReachableThroughMapKey.class, "FOO_KEY"));
- assertEquals(EnumReachableThroughMapValue.FOO_VALUE,
- map.getEnum(EnumReachableThroughMapValue.class, "FOO_VALUE"));
+ assertEquals("FOO_VALUE", map.getToken(EnumReachableThroughMapValue.FOO_VALUE));
+ assertEquals(EnumReachableThroughList.FOO_LIST, map.getEnum(EnumReachableThroughList.class,
+ "FOO_LIST"));
+ assertEquals(EnumReachableThroughMapKey.FOO_KEY, map.getEnum(EnumReachableThroughMapKey.class,
+ "FOO_KEY"));
+ assertEquals(EnumReachableThroughMapValue.FOO_VALUE, map.getEnum(
+ EnumReachableThroughMapValue.class, "FOO_VALUE"));
}
public void testMap() {
@@ -267,8 +273,7 @@
}
assertEquals(5, complex.size());
for (Map.Entry<Simple, Simple> entry : complex.entrySet()) {
- assertEquals(entry.getKey().getString(),
- String.valueOf(entry.getValue().getInt()));
+ assertEquals(entry.getKey().getString(), String.valueOf(entry.getValue().getInt()));
}
}
@@ -282,7 +287,8 @@
Map<String, String> value = new HashMap<String, String>();
value.put("c", "d");
- Map<Map<String, String>, Map<String, String>> test = new HashMap<Map<String, String>, Map<String, String>>();
+ Map<Map<String, String>, Map<String, String>> test =
+ new HashMap<Map<String, String>, Map<String, String>>();
test.put(key, value);
AutoBean<HasMap> bean = f.hasMap();
@@ -316,8 +322,8 @@
AutoBean<HasSimple> decodedBean2 = checkEncode(bean2);
assertNotNull(decodedBean2.as().getSimple());
- assertTrue(AutoBeanUtils.diff(bean,
- AutoBeanUtils.getAutoBean(decodedBean2.as().getSimple())).isEmpty());
+ assertTrue(AutoBeanUtils.diff(bean, AutoBeanUtils.getAutoBean(decodedBean2.as().getSimple()))
+ .isEmpty());
AutoBean<HasList> bean3 = f.hasList();
bean3.as().setIntList(Arrays.asList(1, 2, 3, null, 4, 5));
@@ -325,8 +331,7 @@
AutoBean<HasList> decodedBean3 = checkEncode(bean3);
assertNotNull(decodedBean3.as().getIntList());
- assertEquals(Arrays.asList(1, 2, 3, null, 4, 5),
- decodedBean3.as().getIntList());
+ assertEquals(Arrays.asList(1, 2, 3, null, 4, 5), decodedBean3.as().getIntList());
assertNotNull(decodedBean3.as().getList());
assertEquals(1, decodedBean3.as().getList().size());
assertTrue(AutoBeanUtils.diff(bean,
@@ -339,30 +344,25 @@
AutoBean<HasSplittable> bean = f.hasAutoBean();
bean.as().setSimple(AutoBeanCodex.encode(simple));
bean.as().setString(ValueCodex.encode("Hello ['\"] world"));
- List<Splittable> testList = Arrays.asList(AutoBeanCodex.encode(simple),
- null, AutoBeanCodex.encode(simple));
+ List<Splittable> testList =
+ Arrays.asList(AutoBeanCodex.encode(simple), null, AutoBeanCodex.encode(simple));
bean.as().setSimpleList(testList);
- Map<Splittable, Splittable> testMap = Collections.singletonMap(
- ValueCodex.encode("12345"), ValueCodex.encode("5678"));
+ Map<Splittable, Splittable> testMap =
+ Collections.singletonMap(ValueCodex.encode("12345"), ValueCodex.encode("5678"));
bean.as().setSplittableMap(testMap);
AutoBean<HasSplittable> decoded = checkEncode(bean);
Splittable toDecode = decoded.as().getSimple();
- AutoBean<Simple> decodedSimple = AutoBeanCodex.decode(f, Simple.class,
- toDecode);
+ AutoBean<Simple> decodedSimple = AutoBeanCodex.decode(f, Simple.class, toDecode);
assertEquals("Simple", decodedSimple.as().getString());
- assertEquals("Hello ['\"] world",
- ValueCodex.decode(String.class, decoded.as().getString()));
- assertEquals("12345",
- decoded.as().getSplittableMap().keySet().iterator().next().asString());
- assertEquals("5678",
- decoded.as().getSplittableMap().values().iterator().next().asString());
+ assertEquals("Hello ['\"] world", ValueCodex.decode(String.class, decoded.as().getString()));
+ assertEquals("12345", decoded.as().getSplittableMap().keySet().iterator().next().asString());
+ assertEquals("5678", decoded.as().getSplittableMap().values().iterator().next().asString());
List<Splittable> list = decoded.as().getSimpleList();
assertEquals(3, list.size());
assertNull(list.get(1));
- assertEquals("Simple",
- AutoBeanCodex.decode(f, Simple.class, list.get(2)).as().getString());
+ assertEquals("Simple", AutoBeanCodex.decode(f, Simple.class, list.get(2)).as().getString());
}
@Override
diff --git a/user/test/com/google/gwt/autobean/shared/SplittableTest.java b/user/test/com/google/gwt/autobean/shared/SplittableTest.java
new file mode 100644
index 0000000..46c515b
--- /dev/null
+++ b/user/test/com/google/gwt/autobean/shared/SplittableTest.java
@@ -0,0 +1,330 @@
+/*
+ * 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.autobean.shared;
+
+import com.google.gwt.autobean.client.impl.JsoSplittable;
+import com.google.gwt.autobean.shared.impl.AutoBeanCodexImpl;
+import com.google.gwt.autobean.shared.impl.AutoBeanCodexImpl.Coder;
+import com.google.gwt.autobean.shared.impl.AutoBeanCodexImpl.EncodeState;
+import com.google.gwt.autobean.shared.impl.SplittableList;
+import com.google.gwt.autobean.shared.impl.SplittableSimpleMap;
+import com.google.gwt.autobean.shared.impl.StringQuoter;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Tests for the underlying Splittable implementation. This test class is not
+ * indicative of code that users would write, it's simply doing spot-checks of
+ * functionality that AbstractAutoBean depends on.
+ */
+public class SplittableTest extends GWTTestCase {
+
+ /**
+ *
+ */
+ private static final EncodeState testState = EncodeState.forTesting();
+
+ @Override
+ public String getModuleName() {
+ return "com.google.gwt.autobean.AutoBean";
+ }
+
+ public void testBasicProperties() {
+ Splittable data = StringQuoter.split("{\"a\":true, \"b\":3, \"c\":\"string\", \"d\":null}");
+ assertTrue("isBoolean", data.get("a").isBoolean());
+ assertTrue("asBoolean", data.get("a").asBoolean());
+ assertTrue("isNumber", data.get("b").isNumber());
+ assertEquals(3.0, data.get("b").asNumber());
+ assertTrue("isString", data.get("c").isString());
+ assertEquals("string", data.get("c").asString());
+ assertTrue("isNull", data.isNull("d"));
+ assertNull("should be null", data.get("d"));
+ }
+
+ /**
+ * Ensure that hashcodes don't leak into the payload.
+ */
+ public void testHashCode() {
+ Splittable data = StringQuoter.split("{\"a\":\"b\"}");
+ int hash = data.hashCode();
+ String payload = data.getPayload();
+ assertFalse(payload, payload.contains("$H"));
+ assertFalse(payload, payload.contains(String.valueOf(hash)));
+ assertEquals(hash, data.hashCode());
+ }
+
+ /**
+ * Splittables are implemented by a couple of different concrete types. We'll
+ * use this method to make sure that the correct implementation type is being
+ * used in various circumstances.
+ */
+ public void testImplementationChoice() {
+ Splittable s = StringQuoter.split("[1,false,\"true\"]");
+ if (GWT.isScript()) {
+ assertTrue("s should be JsoSplittable", s instanceof JsoSplittable);
+ assertTrue("s[0] should be JsoSplittable", s.get(0) instanceof JsoSplittable);
+ assertTrue("s[1] should be JsoSplittable", s.get(1) instanceof JsoSplittable);
+ assertTrue("s[2] should be JsoSplittable", s.get(2) instanceof JsoSplittable);
+ } else {
+ // Using the same types in both pure-JRE and DevMode to avoid JSNI
+ // overhead
+ assertTrue("s should be JsonSplittable", s.getClass().getName().endsWith("JsonSplittable"));
+ assertTrue("s[0] should be JsonSplittable", s.get(0).getClass().getName().endsWith(
+ "JsonSplittable"));
+ assertTrue("s[1] should be JsonSplittable", s.get(1).getClass().getName().endsWith(
+ "JsonSplittable"));
+ assertTrue("s[2] should be JsonSplittable", s.get(2).getClass().getName().endsWith(
+ "JsonSplittable"));
+ }
+ }
+
+ public void testIndexed() {
+ Splittable s = StringQuoter.createIndexed();
+ assertTrue(s.isIndexed());
+ assertFalse(s.isKeyed());
+ assertFalse(s.isString());
+ assertEquals(0, s.size());
+
+ string("foo").assign(s, 0);
+ string("bar").assign(s, 1);
+ string("baz").assign(s, 2);
+
+ assertEquals(3, s.size());
+ assertEquals("[\"foo\",\"bar\",\"baz\"]", s.getPayload());
+
+ string("quux").assign(s, 1);
+ assertEquals("[\"foo\",\"quux\",\"baz\"]", s.getPayload());
+
+ Splittable s2 = s.deepCopy();
+ assertNotSame(s, s2);
+ assertEquals(s.size(), s2.size());
+ for (int i = 0, j = s.size(); i < j; i++) {
+ assertEquals(s.get(i).asString(), s2.get(i).asString());
+ }
+
+ s.setSize(2);
+ assertEquals(2, s.size());
+ assertEquals("[\"foo\",\"quux\"]", s.getPayload());
+
+ // Make sure reified values aren't in the payload
+ Object o = new Object();
+ s.setReified("reified", o);
+ assertFalse(s.getPayload().contains("reified"));
+ assertFalse(s.getPayload().contains("__s"));
+ assertSame(o, s.getReified("reified"));
+ }
+
+ public void testKeyed() {
+ Splittable s = StringQuoter.createSplittable();
+ assertFalse(s.isIndexed());
+ assertTrue(s.isKeyed());
+ assertFalse(s.isString());
+ assertTrue(s.getPropertyKeys().isEmpty());
+
+ string("bar").assign(s, "foo");
+ string("quux").assign(s, "baz");
+
+ // Actual iteration order is undefined
+ assertEquals(new HashSet<String>(Arrays.asList("foo", "baz")), new HashSet<String>(s
+ .getPropertyKeys()));
+
+ assertFalse(s.isNull("foo"));
+ assertTrue(s.isNull("bar"));
+
+ assertEquals("bar", s.get("foo").asString());
+ assertEquals("quux", s.get("baz").asString());
+
+ String payload = s.getPayload();
+ assertTrue(payload.startsWith("{"));
+ assertTrue(payload.endsWith("}"));
+ assertTrue(payload.contains("\"foo\":\"bar\""));
+ assertTrue(payload.contains("\"baz\":\"quux\""));
+
+ Splittable s2 = s.deepCopy();
+ assertNotSame(s, s2);
+ assertEquals("bar", s2.get("foo").asString());
+ assertEquals("quux", s2.get("baz").asString());
+
+ // Make sure reified values aren't in the payload
+ Object o = new Object();
+ s.setReified("reified", o);
+ assertFalse("Should not see reified in " + s.getPayload(), s.getPayload().contains("reified"));
+ assertFalse("Should not see __s in " + s.getPayload(), s.getPayload().contains("__s"));
+ assertSame(o, s.getReified("reified"));
+ }
+
+ public void testNested() {
+ Splittable s = StringQuoter.split("{\"a\":{\"foo\":\"bar\"}}");
+ Splittable a = s.get("a");
+ assertNotNull(a);
+ assertEquals("bar", a.get("foo").asString());
+ assertSame(a, s.get("a"));
+ assertEquals(a, s.get("a"));
+ }
+
+ /**
+ * Tests attributes of the {@link Splittable#NULL} field.
+ */
+ public void testNull() {
+ Splittable n = Splittable.NULL;
+ if (GWT.isScript()) {
+ assertNull(n);
+ } else {
+ assertNotNull(n);
+ }
+ assertFalse("boolean", n.isBoolean());
+ assertFalse("indexed", n.isIndexed());
+ assertFalse("keyed", n.isKeyed());
+ assertFalse("string", n.isString());
+ assertEquals("null", n.getPayload());
+ }
+
+ /**
+ * Extra tests in here due to potential to confuse {@code false} and
+ * {@code null} values.
+ */
+ public void testSplittableListBoolean() {
+ Coder boolCoder = AutoBeanCodexImpl.valueCoder(Boolean.class);
+ Splittable s = StringQuoter.createIndexed();
+ bool(false).assign(s, 0);
+ assertFalse("0 should not be null", s.isNull(0));
+ assertTrue("s[0] should be a boolean", s.get(0).isBoolean());
+ assertFalse("s[0] should be false", s.get(0).asBoolean());
+ assertNotNull("Null decode", ValueCodex.decode(Boolean.class, s.get(0)));
+ Object decodedBoolean = boolCoder.decode(testState, s.get(0));
+ assertNotNull("decode should not return null", decodedBoolean);
+ assertFalse("decoded value should be false", (Boolean) decodedBoolean);
+
+ bool(true).assign(s, 1);
+ assertTrue("s[1] should be a boolean", s.get(1).isBoolean());
+ assertTrue("s[1] should be true", s.get(1).asBoolean());
+ assertTrue("boolCoder 1", (Boolean) boolCoder.decode(testState, s.get(1)));
+
+ Splittable.NULL.assign(s, 2);
+ assertTrue("3 should be null", s.isNull(3));
+ assertEquals("payload", "[false,true,null]", s.getPayload());
+ List<Boolean> boolList = new SplittableList<Boolean>(s, boolCoder, testState);
+ assertEquals("boolList", Arrays.<Boolean> asList(false, true, null), boolList);
+ }
+
+ /**
+ * Extra tests in here due to potential to confuse 0 and {@code null} values.
+ */
+ public void testSplittableListNumbers() {
+ Coder intCoder = AutoBeanCodexImpl.valueCoder(Integer.class);
+ Coder doubleCoder = AutoBeanCodexImpl.valueCoder(Double.class);
+ Splittable s = StringQuoter.createIndexed();
+ number(0).assign(s, 0);
+ assertFalse("0 should not be null", s.isNull(0));
+ assertTrue("s[0] should be a number", s.get(0).isNumber());
+ assertNotNull("Null decode", ValueCodex.decode(Integer.class, s.get(0)));
+ Object decodedInt = intCoder.decode(testState, s.get(0));
+ assertNotNull("decode should not return null", decodedInt);
+ assertEquals("intCoder 0", Integer.valueOf(0), decodedInt);
+ assertEquals("doubleCoder 0", Double.valueOf(0), doubleCoder.decode(testState, s.get(0)));
+
+ number(3.141592).assign(s, 1);
+ assertEquals("intCoder 1", Integer.valueOf(3), intCoder.decode(testState, s.get(1)));
+ assertEquals("doubleCoder 1", Double.valueOf(3.141592), doubleCoder.decode(testState, s.get(1)));
+
+ number(42).assign(s, 2);
+ Splittable.NULL.assign(s, 3);
+ assertTrue("3 should be null", s.isNull(3));
+ assertEquals("payload", "[0,3.141592,42,null]", s.getPayload());
+ List<Double> doubleList = new SplittableList<Double>(s, doubleCoder, testState);
+ assertEquals(Double.valueOf(0), doubleList.get(0));
+ assertEquals("doubleList", Arrays.<Double> asList(0d, 3.141592, 42d, null), doubleList);
+
+ // Don't share backing data between lists
+ s = StringQuoter.split("[0,3.141592,42,null]");
+ List<Integer> intList = new SplittableList<Integer>(s, intCoder, testState);
+ assertEquals("intList", Arrays.<Integer> asList(0, 3, 42, null), intList);
+ }
+
+ public void testSplittableListString() {
+ Splittable data = StringQuoter.split("[\"Hello\",\"World\"]");
+ SplittableList<String> list =
+ new SplittableList<String>(data, AutoBeanCodexImpl.valueCoder(String.class), testState);
+ assertEquals(2, list.size());
+ assertEquals(Arrays.asList("Hello", "World"), list);
+ list.set(0, "Goodbye");
+ assertEquals(Arrays.asList("Goodbye", "World"), list);
+ assertEquals("[\"Goodbye\",\"World\"]", data.getPayload());
+ list.remove(0);
+ assertEquals(Arrays.asList("World"), list);
+ assertEquals("[\"World\"]", data.getPayload());
+ list.add("Wide");
+ list.add("Web");
+ assertEquals(Arrays.asList("World", "Wide", "Web"), list);
+ assertEquals("[\"World\",\"Wide\",\"Web\"]", data.getPayload());
+ }
+
+ public void testSplittableMapStringString() {
+ Splittable data = StringQuoter.split("{\"foo\":\"bar\",\"baz\":\"quux\",\"isNull\":null}");
+ assertTrue("isNull should be null", data.isNull("isNull"));
+ assertFalse("isNull should not be undefined", data.isUndefined("isNull"));
+ Map<String, String> map =
+ new SplittableSimpleMap<String, String>(data, AutoBeanCodexImpl.valueCoder(String.class),
+ AutoBeanCodexImpl.valueCoder(String.class), testState);
+ assertEquals(3, map.size());
+ assertEquals("bar", map.get("foo"));
+ assertEquals("quux", map.get("baz"));
+ assertTrue("Map should have isNull key", map.containsKey("isNull"));
+ assertNull(map.get("isNull"));
+ assertFalse("Map should not have unknown key", map.containsKey("unknown"));
+
+ map.put("bar", "foo2");
+ assertEquals("foo2", map.get("bar"));
+ String payload = data.getPayload();
+ assertTrue(payload.contains("\"bar\":\"foo2\""));
+ assertTrue(payload.contains("\"isNull\":null"));
+ }
+
+ public void testString() {
+ Splittable s = string("Hello '\" World!");
+ assertFalse(s.isIndexed());
+ assertFalse(s.isKeyed());
+ assertTrue(s.isString());
+ assertEquals("Hello '\" World!", s.asString());
+ assertEquals("\"Hello '\\\" World!\"", s.getPayload());
+ }
+
+ public void testStringEmpty() {
+ Splittable s = string("");
+ assertFalse(s.isIndexed());
+ assertFalse(s.isKeyed());
+ assertTrue(s.isString());
+ assertEquals("", s.asString());
+ assertEquals("\"\"", s.getPayload());
+ }
+
+ private Splittable bool(boolean value) {
+ return StringQuoter.split(String.valueOf(value));
+ }
+
+ private Splittable number(double number) {
+ return StringQuoter.split(String.valueOf(number));
+ }
+
+ private Splittable string(String value) {
+ return StringQuoter.split(StringQuoter.quote(value));
+ }
+}