Roll back AutoBeans change due to serialization problems with Safari4 on XP.
Patch by: bobv
Review by: rchandia (TBR)


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9958 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 5eb9763..11f6998 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 || implementingType.isAnnotationPresent(GwtScriptOnly.class)) {
+          if (implementingType == null) {
             /*
              * 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 7041d0b..88cb8f5 100644
--- a/tools/api-checker/config/gwt22_23userApi.conf
+++ b/tools/api-checker/config/gwt22_23userApi.conf
@@ -16,9 +16,7 @@
 :**/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 f8f9e3e..0310304 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 getEnum(Class<E> clazz, String token) {
+  public <E extends Enum<E>> 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 620c9ec..73a077a 100644
--- a/user/src/com/google/gwt/autobean/client/impl/ClientPropertyContext.java
+++ b/user/src/com/google/gwt/autobean/client/impl/ClientPropertyContext.java
@@ -18,8 +18,6 @@
 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;
@@ -29,20 +27,18 @@
 /**
  * Provides base methods for generated implementations of PropertyContext.
  */
-public final class ClientPropertyContext implements PropertyContext, CollectionPropertyContext,
-    MapPropertyContext {
+public final class ClientPropertyContext implements CollectionPropertyContext, MapPropertyContext {
 
   /**
    * A reference to an instance setter method.
    */
   public static final class Setter extends JavaScriptObject {
     /**
-     * Create a trivial Setter that calls {@link AbstractAutoBean#setProperty()}
-     * .
+     * Create a trivial Setter that calls {@link Map#put(Object, Object)}.
      */
-    public static native Setter beanSetter(AbstractAutoBean<?> bean, String key) /*-{
+    public static native Setter mapSetter(Map<String, Object> values, String key) /*-{
       return function(value) {
-        bean.@com.google.gwt.autobean.shared.impl.AbstractAutoBean::setProperty(*)(key, value);
+        values.@java.util.Map::put(*)(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 f6d3f3f..6a86993 100644
--- a/user/src/com/google/gwt/autobean/client/impl/JsoSplittable.java
+++ b/user/src/com/google/gwt/autobean/client/impl/JsoSplittable.java
@@ -16,11 +16,8 @@
 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;
@@ -28,106 +25,91 @@
 
 /**
  * Implements the EntityCodex.Splittable interface using a raw JavaScriptObject.
- * <p>
- * A string value represented by a JsoSplittable can't use the string object
- * directly, since {@code String.prototype} is overridden, so instead a
- * temporary wrapper object is used to encapsulate the string data.
  */
-@GwtScriptOnly
-public final class JsoSplittable extends JavaScriptObject implements Splittable, HasSplittable {
-  public static native JsoSplittable create() /*-{
-    return {};
-  }-*/;
+public final class JsoSplittable extends JavaScriptObject implements Splittable {
+  /**
+   * This type is used because we can't treat Strings as JSOs.
+   */
+  public static class StringSplittable implements Splittable {
+    private final String value;
 
-  public static Splittable create(boolean value) {
-    return create0(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(double value) {
-    return create0(value);
+  public static Splittable create(Object object) {
+    if (object instanceof String) {
+      return new StringSplittable((String) object);
+    }
+    return create0(object);
   }
 
-  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) /*-{
+  private static native Splittable create0(Object object) /*-{
     return object;
   }-*/;
 
-  private static native Splittable create0(double object) /*-{
-    return object;
-  }-*/;
-
-  private static native Splittable create0(String object) /*-{
-    return {
-      __s : object
-    };
-  }-*/;
-
-  private static native boolean stringifyFastSupported() /*-{
-    return $wnd.JSON && $wnd.JSON.stringify;
-  }-*/;
-
   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 this.__s;
+    return String(this);
   }-*/;
 
-  public Splittable deepCopy() {
-    return StringQuoter.split(getPayload());
+  public Splittable get(int index) {
+    return create(get0(index));
   }
 
-  public JsoSplittable get(int index) {
-    return getRaw(index);
-  }
-
-  public JsoSplittable get(String key) {
-    return getRaw(key);
+  public Splittable get(String key) {
+    return create(get0(key));
   }
 
   public String getPayload() {
-    if (isString()) {
-      return JsonUtils.escapeValue(asString());
-    }
-    if (stringifyFastSupported()) {
-      return stringifyFast();
-    }
-    return stringifySlow();
+    throw new UnsupportedOperationException("Cannot convert JsoSplittable to payload");
   }
 
   public List<String> getPropertyKeys() {
@@ -136,28 +118,12 @@
     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 typeof (this) == 'boolean' || this instanceof Boolean;
-  }-*/;
-
-  public native boolean isFunction() /*-{
-    return typeof this == 'function';
-  }-*/;
-
   public native boolean isIndexed() /*-{
     return this instanceof Array;
   }-*/;
 
   public boolean isKeyed() {
-    return this != NULL && !isString() && !isIndexed() && !isFunction();
+    return !isString() && !isIndexed();
   }
 
   public native boolean isNull(int index) /*-{
@@ -168,50 +134,20 @@
     return this[key] == null;
   }-*/;
 
-  public native boolean isNumber() /*-{
-    return typeof (this) == 'number' || this instanceof Number;
-  }-*/;
-
-  public native boolean isReified(String key) /*-{
-    return !!(this.__reified && this.__reified.hasOwnProperty(':' + key));
-  }-*/;
-
   public native boolean isString() /*-{
-    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;
+    return typeof (this) == 'string' || this instanceof String;
   }-*/;
 
   public native int size() /*-{
     return this.length;
   }-*/;
 
-  private native void assign0(Splittable parent, int index, Splittable value) /*-{
-    parent[index] = value;
+  private native Object get0(int index) /*-{
+    return Object(this[index]);
   }-*/;
 
-  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 Object get0(String key) /*-{
+    return Object(this[key]);
   }-*/;
 
   private native void getPropertyKeys0(List<String> list) /*-{
@@ -221,83 +157,4 @@
       }
     }
   }-*/;
-
-  private native JsoSplittable getRaw(int index) /*-{
-    _ = this[index];
-    if (_ == null) {
-      return null;
-    }
-    if (typeof _ == 'string' || _ instanceof String) {
-      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 (typeof _ == 'string' || _ instanceof String) {
-      return @com.google.gwt.autobean.client.impl.JsoSplittable::create(Ljava/lang/String;)(_);
-    }
-    return Object(_);
-  }-*/;
-
-  private native String stringifyFast() /*-{
-    return $wnd.JSON.stringify(this);
-  }-*/;
-
-  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()) {
-        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 1ec4f78..a07758d 100644
--- a/user/src/com/google/gwt/autobean/rebind/AutoBeanFactoryGenerator.java
+++ b/user/src/com/google/gwt/autobean/rebind/AutoBeanFactoryGenerator.java
@@ -27,7 +27,6 @@
 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;
@@ -186,16 +185,28 @@
           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(wrapped, factory);");
+    sw.indentln("super(factory, wrapped);");
     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());
@@ -219,49 +230,45 @@
     // 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: {
-          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());
+          // 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("}");
           } else {
-            // return (ReturnType) values.getOrReify(\"foo\");
-            castType = ModelUtils.getQualifiedBaseSourceName(returnType);
-            sw.println("return (%s) getOrReify(\"%s\");", castType, method.getPropertyName());
+            // return (ReturnType) values.get(\"foo\");
+            sw.println("return (%s) values.get(\"%s\");", ModelUtils
+                .getQualifiedBaseSourceName(jmethod.getReturnType()), method.getPropertyName());
           }
         }
           break;
         case SET:
-        case SET_BUILDER: {
-          JParameter param = jmethod.getParameters()[0];
-          // setProperty("foo", parameter);
-          sw.println("setProperty(\"%s\", %s);", method.getPropertyName(), param.getName());
+        case SET_BUILDER:
+          // values.put("foo", parameter);
+          sw.println("values.put(\"%s\", %s);", method.getPropertyName(),
+              jmethod.getParameters()[0].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 (!returnType.equals(JPrimitiveType.VOID)) {
+          if (!jmethod.getReturnType().equals(JPrimitiveType.VOID)) {
             sw.print("return ");
           }
           sw.print("%s.%s(%s.this", staticImpl.getEnclosingType().getQualifiedSourceName(),
@@ -499,15 +506,19 @@
       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.
            */
-          sw.println("%s toReturn = %s.this.getWrapped().%s();", ModelUtils
-              .getQualifiedBaseSourceName(jmethod.getReturnType()), type.getSimpleSourceName(),
-              methodName);
+          // 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());
 
           // Non-value types might need to be wrapped
           writeReturnWrapper(sw, type, method);
@@ -515,6 +526,7 @@
           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());
@@ -584,6 +596,7 @@
     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)) {
@@ -649,9 +662,9 @@
           referencedSetters.add(setter);
         } else {
           // Create a function that will update the values map
-          // CPContext.beanSetter(FooBeanImpl.this, "foo");
-          sw.println("%s.beanSetter(%s.this, \"%s\"),", ClientPropertyContext.Setter.class
-              .getCanonicalName(), type.getSimpleSourceName(), method.getPropertyName());
+          // CPContext.mapSetter(values, "foo");
+          sw.println("%s.mapSetter(values, \"%s\"),", ClientPropertyContext.Setter.class
+              .getCanonicalName(), 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 32b4a1b..132393f 100644
--- a/user/src/com/google/gwt/autobean/server/impl/BeanMethod.java
+++ b/user/src/com/google/gwt/autobean/server/impl/BeanMethod.java
@@ -16,7 +16,6 @@
 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;
@@ -39,7 +38,8 @@
     }
 
     @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) && !method.isAnnotationPresent(PropertyName.class)) {
+      if (name.startsWith(IS_PREFIX)) {
         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().getOrReify(propertyName);
+      Object toReturn = handler.getBean().getValues().get(propertyName);
       if (toReturn == null && method.getReturnType().isPrimitive()) {
         toReturn = TypeUtils.getDefaultPrimitiveValue(method.getReturnType());
       }
@@ -80,14 +80,15 @@
     @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;
         }
       }
@@ -100,7 +101,7 @@
   SET {
     @Override
     Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args) {
-      handler.getBean().setProperty(inferName(method), args[0]);
+      handler.getBean().getValues().put(inferName(method), args[0]);
       return null;
     }
 
@@ -108,7 +109,8 @@
     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);
     }
   },
   /**
@@ -119,15 +121,15 @@
   SET_BUILDER {
     @Override
     Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args) {
-      ProxyAutoBean<?> bean = handler.getBean();
-      bean.setProperty(inferName(method), args[0]);
-      return bean.as();
+      handler.getBean().getValues().put(inferName(method), args[0]);
+      return handler.getBean().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());
     }
@@ -142,7 +144,8 @@
     }
 
     @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;
       }
@@ -187,9 +190,8 @@
           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;
         }
@@ -203,8 +205,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) {
@@ -221,10 +223,6 @@
   }
 
   public String inferName(Method method) {
-    PropertyName prop = method.getAnnotation(PropertyName.class);
-    if (prop != null) {
-      return prop.value();
-    }
     return decapitalize(method.getName().substring(3));
   }
 
@@ -238,11 +236,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 c1f4be9..f17e879 100644
--- a/user/src/com/google/gwt/autobean/server/impl/BeanPropertyContext.java
+++ b/user/src/com/google/gwt/autobean/server/impl/BeanPropertyContext.java
@@ -16,19 +16,20 @@
 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
@@ -41,6 +42,6 @@
     Class<?> maybeAutobox = TypeUtils.maybeAutobox(getType());
     assert value == null || maybeAutobox.isInstance(value) : value.getClass().getCanonicalName()
         + " is not assignable to " + maybeAutobox.getCanonicalName();
-    bean.setProperty(propertyName, maybeAutobox.cast(value));
+    map.put(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 04b77a6..51e7902 100644
--- a/user/src/com/google/gwt/autobean/server/impl/JsonSplittable.java
+++ b/user/src/com/google/gwt/autobean/server/impl/JsonSplittable.java
@@ -16,39 +16,21 @@
 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, 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 class JsonSplittable implements Splittable {
   public static Splittable create(String payload) {
     try {
       switch (payload.charAt(0)) {
@@ -57,8 +39,8 @@
         case '[':
           return new JsonSplittable(new JSONArray(payload));
         case '"':
-          return new JsonSplittable(new JSONArray("[" + payload + "]").getString(0));
-        case '-':
+          return new JsonSplittable(
+              new JSONArray("[" + payload + "]").getString(0));
         case '0':
         case '1':
         case '2':
@@ -69,31 +51,19 @@
         case '7':
         case '8':
         case '9':
-          return new JsonSplittable(Double.parseDouble(payload));
-        case 't':
-        case 'f':
-          return new JsonSplittable(Boolean.parseBoolean(payload));
-        case 'n':
-          return null;
+          return new JsonSplittable(payload);
         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. Used to represent a null value.
+   * Private equivalent of org.json.JSONObject.getNames(JSONObject)
+   * since that method is not available in Android 2.2.
    */
   private static String[] getNames(JSONObject json) {
     int length = json.length();
@@ -109,38 +79,20 @@
     return names;
   }
 
-  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 final JSONArray array;
+  private final JSONObject obj;
+  private final String string;
 
   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) {
@@ -149,38 +101,10 @@
     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));
@@ -198,9 +122,6 @@
   }
 
   public String getPayload() {
-    if (isNull) {
-      return "null";
-    }
     if (obj != null) {
       return obj.toString();
     }
@@ -210,12 +131,6 @@
     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");
   }
 
@@ -228,18 +143,6 @@
     }
   }
 
-  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;
   }
@@ -257,39 +160,10 @@
     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();
   }
@@ -299,53 +173,23 @@
    */
   @Override
   public String toString() {
-    return getPayload();
-  }
-
-  private synchronized JsonSplittable makeSplittable(Object object) {
-    if (JSONObject.NULL.equals(object)) {
-      return null;
-    }
-    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 seen;
-  }
-
-  private Object value() {
-    if (isNull) {
-      return null;
-    }
     if (obj != null) {
-      return obj;
-    }
-    if (array != null) {
-      return array;
-    }
-    if (string != null) {
+      return obj.toString();
+    } else if (array != null) {
+      return array.toString();
+    } else if (string != null) {
       return string;
     }
-    if (number != null) {
-      return number;
-    }
-    if (bool != null) {
-      return bool;
-    }
-    throw new RuntimeException("No data");
+    return "<Uninitialized>";
   }
-}
\ No newline at end of file
+
+  private JsonSplittable makeSplittable(Object object) {
+    if (object instanceof JSONObject) {
+      return new JsonSplittable((JSONObject) object);
+    }
+    if (object instanceof JSONArray) {
+      return new JsonSplittable((JSONArray) object);
+    }
+    return new JsonSplittable(object.toString());
+  }
+}
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 de65b5e..4f8e3be 100644
--- a/user/src/com/google/gwt/autobean/server/impl/ProxyAutoBean.java
+++ b/user/src/com/google/gwt/autobean/server/impl/ProxyAutoBean.java
@@ -66,14 +66,15 @@
       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) {
@@ -116,11 +117,13 @@
   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;
@@ -129,20 +132,36 @@
   }
 
   @SuppressWarnings("unchecked")
-  public ProxyAutoBean(AutoBeanFactory factory, Class<?> beanType, Configuration configuration,
-      T toWrap) {
-    super(toWrap, factory);
+  public ProxyAutoBean(AutoBeanFactory factory, Class<?> beanType,
+      Configuration configuration, T toWrap) {
+    super(factory, toWrap);
+    if (Proxy.isProxyClass(toWrap.getClass())) {
+      System.out.println("blah");
+    }
     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;
   }
@@ -175,13 +194,9 @@
     super.checkWrapped();
   }
 
-  /**
-   * Not used in this implementation. Instead, the simple implementation is
-   * created lazily in {@link #getWrapped()}.
-   */
   @Override
   protected T createSimplePeer() {
-    return null;
+    return ProxyAutoBean.makeProxy(beanType, new SimpleBeanHandler<T>(this));
   }
 
   /**
@@ -193,11 +208,10 @@
   }
 
   /**
-   * Allow access by BeanMethod.
+   * Allow access by {@link BeanMethod}.
    */
-  @Override
-  protected <V> V getOrReify(String propertyName) {
-    return super.<V> getOrReify(propertyName);
+  protected Map<String, Object> getValues() {
+    return values;
   }
 
   /**
@@ -205,9 +219,6 @@
    */
   @Override
   protected T getWrapped() {
-    if (wrapped == null && isUsingSimplePeer()) {
-      wrapped = (T) ProxyAutoBean.makeProxy(beanType, new SimpleBeanHandler<T>(this));
-    }
     return super.getWrapped();
   }
 
@@ -219,11 +230,6 @@
     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) {
@@ -251,9 +257,8 @@
       }
 
       // 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: {
@@ -308,8 +313,13 @@
     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 dc920a7..23c190d 100644
--- a/user/src/com/google/gwt/autobean/server/impl/ShimHandler.java
+++ b/user/src/com/google/gwt/autobean/server/impl/ShimHandler.java
@@ -19,7 +19,6 @@
 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;
 
@@ -32,9 +31,11 @@
 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()) {
@@ -66,35 +67,36 @@
     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);
-    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();
+    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);
     }
     return toReturn;
   }
@@ -108,11 +110,9 @@
     if (toReturn == null) {
       return null;
     }
-    AutoBean<?> returnBean = AutoBeanUtils.getAutoBean(toReturn);
-    if (returnBean != null) {
-      return returnBean.as();
-    }
-    if (TypeUtils.isValueType(intf) || TypeUtils.isValueType(toReturn.getClass())
+    if (TypeUtils.isValueType(intf)
+        || TypeUtils.isValueType(toReturn.getClass())
+        || AutoBeanUtils.getAutoBean(toReturn) != null
         || 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 766d26b..d25468d 100644
--- a/user/src/com/google/gwt/autobean/server/impl/SimpleBeanHandler.java
+++ b/user/src/com/google/gwt/autobean/server/impl/SimpleBeanHandler.java
@@ -37,7 +37,8 @@
   /**
    * 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);
@@ -46,12 +47,12 @@
     }
     throw new RuntimeException("Unhandled invocation " + method.getName());
   }
-  
+
   /**
    * For debugging use only.
    */
   @Override
   public String toString() {
-    return bean.getSplittable().getPayload();
+    return bean.getPropertyMap().toString();
   }
 }
\ 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 2503696..cf769c1 100644
--- a/user/src/com/google/gwt/autobean/server/impl/TypeUtils.java
+++ b/user/src/com/google/gwt/autobean/server/impl/TypeUtils.java
@@ -164,9 +164,8 @@
     return false;
   }
 
-  public static <V> Class<V> maybeAutobox(Class<V> domainType) {
-    @SuppressWarnings("unchecked")
-    Class<V> autoBoxType = (Class<V>) AUTOBOX_MAP.get(domainType);
+  public static Class<?> maybeAutobox(Class<?> domainType) {
+    Class<?> autoBoxType = 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 3af5128..8b8613f 100644
--- a/user/src/com/google/gwt/autobean/shared/AutoBean.java
+++ b/user/src/com/google/gwt/autobean/shared/AutoBean.java
@@ -58,21 +58,16 @@
   T as();
 
   /**
-   * 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.
+   * Creates a copy of the AutoBean.
    * <p>
-   * 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}.
+   * 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>.
    * 
-   * @throws UnsupportedOperationException
-   * @deprecated with no replacement
+   * @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
    */
-  @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 9797984..b7e4d52 100644
--- a/user/src/com/google/gwt/autobean/shared/AutoBeanCodex.java
+++ b/user/src/com/google/gwt/autobean/shared/AutoBeanCodex.java
@@ -15,10 +15,21 @@
  */
 package com.google.gwt.autobean.shared;
 
-import com.google.gwt.autobean.shared.impl.AutoBeanCodexImpl;
-import com.google.gwt.autobean.shared.impl.AutoBeanCodexImpl.EncodeState;
+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.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
@@ -27,16 +38,363 @@
 public class AutoBeanCodex {
 
   /**
-   * 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
+   * Describes a means of encoding or decoding a particular type of data to or
+   * from a wire format representation.
    */
+  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 AutoBeanCodexImpl.doDecode(EncodeState.forDecode(factory), clazz, data);
+    return new AutoBeanCodex(factory).doDecode(clazz, data);
   }
 
   /**
@@ -46,8 +404,7 @@
    * @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 Splittable#getPayload()
-   *          .getPayload()}.
+   *          {@link #encode(AutoBean)}
    * @return an AutoBean containing the payload contents
    */
   public static <T> AutoBean<T> decode(AutoBeanFactory factory, Class<T> clazz, String payload) {
@@ -63,7 +420,7 @@
    * @param bean the target AutoBean
    */
   public static void decodeInto(Splittable data, AutoBean<?> bean) {
-    AutoBeanCodexImpl.doDecodeInto(EncodeState.forDecode(bean.getFactory()), data, bean);
+    new AutoBeanCodex(bean.getFactory()).doDecodeInto(data, bean);
   }
 
   /**
@@ -75,12 +432,42 @@
    */
   public static Splittable encode(AutoBean<?> bean) {
     if (bean == null) {
-      return Splittable.NULL;
+      return LazySplittable.NULL;
     }
 
     StringBuilder sb = new StringBuilder();
-    EncodeState state = EncodeState.forEncode(bean.getFactory(), sb);
-    AutoBeanCodexImpl.doEncode(state, bean);
-    return StringQuoter.split(sb.toString());
+    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();
+    }
   }
 }
diff --git a/user/src/com/google/gwt/autobean/shared/AutoBeanUtils.java b/user/src/com/google/gwt/autobean/shared/AutoBeanUtils.java
index ca8de60..e617ddf 100644
--- a/user/src/com/google/gwt/autobean/shared/AutoBeanUtils.java
+++ b/user/src/com/google/gwt/autobean/shared/AutoBeanUtils.java
@@ -314,7 +314,8 @@
    */
   private static boolean sameOrEquals(Collection<?> collection, Collection<?> otherCollection,
       Map<PendingComparison, Comparison> pending, Map<Object, Object> pairs) {
-    if (collection.size() != otherCollection.size()) {
+    if (collection.size() != otherCollection.size()
+        || !collection.getClass().equals(otherCollection.getClass())) {
       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 9011fc7..65a2286 100644
--- a/user/src/com/google/gwt/autobean/shared/Splittable.java
+++ b/user/src/com/google/gwt/autobean/shared/Splittable.java
@@ -15,8 +15,6 @@
  */
 package com.google.gwt.autobean.shared;
 
-import com.google.gwt.autobean.shared.impl.StringQuoter;
-
 import java.util.List;
 
 /**
@@ -26,41 +24,11 @@
  */
 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);
@@ -82,16 +50,6 @@
   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.
    */
@@ -104,49 +62,23 @@
   boolean isKeyed();
 
   /**
-   * Indicates if the nth element of a list is null or undefined.
+   * Indicates if the nth element of a list is null.
    */
   boolean isNull(int index);
 
   /**
-   * Indicates if the named property is null or undefined.
+   * Indicates if the named property is null.
    */
   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 {@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.
+   * Returns the size of the list.
    */
   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 8234ee6..b5e103d 100644
--- a/user/src/com/google/gwt/autobean/shared/ValueCodex.java
+++ b/user/src/com/google/gwt/autobean/shared/ValueCodex.java
@@ -15,6 +15,7 @@
  */
 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;
@@ -37,13 +38,13 @@
       }
 
       @Override
-      public BigDecimal decode(Class<?> clazz, Splittable value) {
-        return new BigDecimal(value.asString());
+      public BigDecimal decode(Class<?> clazz, String value) {
+        return new BigDecimal(value);
       }
 
       @Override
-      public Splittable encode(Object value) {
-        return StringQuoter.create(((BigDecimal) value).toString());
+      public String toJsonExpression(Object value) {
+        return StringQuoter.quote(((BigDecimal) value).toString());
       }
     },
     BIG_INTEGER(BigInteger.class) {
@@ -53,46 +54,31 @@
       }
 
       @Override
-      public BigInteger decode(Class<?> clazz, Splittable value) {
-        return new BigInteger(value.asString());
+      public BigInteger decode(Class<?> clazz, String value) {
+        return new BigInteger(value);
       }
 
       @Override
-      public Splittable encode(Object value) {
-        return StringQuoter.create(((BigInteger) value).toString());
+      public String toJsonExpression(Object value) {
+        return StringQuoter.quote(((BigInteger) value).toString());
       }
     },
     BOOLEAN(Boolean.class, boolean.class, false) {
       @Override
-      public Boolean decode(Class<?> clazz, Splittable value) {
-        return value.asBoolean();
-      }
-
-      @Override
-      public Splittable encode(Object value) {
-        return StringQuoter.create((Boolean) value);
+      public Boolean decode(Class<?> clazz, String value) {
+        return Boolean.valueOf(value);
       }
     },
     BYTE(Byte.class, byte.class, (byte) 0) {
       @Override
-      public Byte decode(Class<?> clazz, Splittable value) {
-        return (byte) value.asNumber();
-      }
-
-      @Override
-      public Splittable encode(Object value) {
-        return StringQuoter.create((Byte) value);
+      public Byte decode(Class<?> clazz, String value) {
+        return Byte.valueOf(value);
       }
     },
     CHARACTER(Character.class, char.class, (char) 0) {
       @Override
-      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));
+      public Character decode(Class<?> clazz, String value) {
+        return value.charAt(0);
       }
     },
     DATE(Date.class) {
@@ -102,111 +88,80 @@
       }
 
       @Override
-      public Date decode(Class<?> clazz, Splittable value) {
-        return StringQuoter.tryParseDate(value.asString());
+      public Date decode(Class<?> clazz, String value) {
+        return StringQuoter.tryParseDate(value);
       }
 
       @Override
-      public Splittable encode(Object value) {
-        return StringQuoter.create(String.valueOf(((Date) value).getTime()));
+      public String toJsonExpression(Object value) {
+        return String.valueOf(((Date) value).getTime());
       }
     },
     DOUBLE(Double.class, double.class, 0d) {
       @Override
-      public Double decode(Class<?> clazz, Splittable value) {
-        return value.asNumber();
-      }
-
-      @Override
-      public Splittable encode(Object value) {
-        return StringQuoter.create((Double) value);
+      public Double decode(Class<?> clazz, String value) {
+        return Double.valueOf(value);
       }
     },
     ENUM(Enum.class) {
       @Override
-      public Enum<?> decode(Class<?> clazz, Splittable value) {
-        return (Enum<?>) clazz.getEnumConstants()[(int) value.asNumber()];
+      public Enum<?> decode(Class<?> clazz, String value) {
+        return (Enum<?>) clazz.getEnumConstants()[Integer.valueOf(value)];
       }
 
       @Override
-      public Splittable encode(Object value) {
-        return StringQuoter.create(((Enum<?>) value).ordinal());
+      public String toJsonExpression(Object value) {
+        return String.valueOf(((Enum<?>) value).ordinal());
       }
     },
     FLOAT(Float.class, float.class, 0f) {
       @Override
-      public Float decode(Class<?> clazz, Splittable value) {
-        return (float) value.asNumber();
-      }
-
-      @Override
-      public Splittable encode(Object value) {
-        return StringQuoter.create((Float) value);
+      public Float decode(Class<?> clazz, String value) {
+        return Float.valueOf(value);
       }
     },
     INTEGER(Integer.class, int.class, 0) {
       @Override
-      public Integer decode(Class<?> clazz, Splittable value) {
-        return Integer.valueOf((int) value.asNumber());
-      }
-
-      @Override
-      public Splittable encode(Object value) {
-        return StringQuoter.create((Integer) value);
+      public Integer decode(Class<?> clazz, String value) {
+        return Integer.valueOf(value);
       }
     },
     LONG(Long.class, long.class, 0L) {
       @Override
-      public Long decode(Class<?> clazz, Splittable value) {
-        return Long.parseLong(value.asString());
+      public Long decode(Class<?> clazz, String value) {
+        return Long.valueOf(value);
       }
 
       @Override
-      public Splittable encode(Object value) {
-        return StringQuoter.create(String.valueOf((Long) value));
+      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");
+        }
       }
     },
     SHORT(Short.class, short.class, (short) 0) {
       @Override
-      public Short decode(Class<?> clazz, Splittable value) {
-        return (short) value.asNumber();
-      }
-
-      @Override
-      public Splittable encode(Object value) {
-        return StringQuoter.create((Short) value);
+      public Short decode(Class<?> clazz, String value) {
+        return Short.valueOf(value);
       }
     },
     STRING(String.class) {
       @Override
-      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) {
+      public String decode(Class<?> clazz, String value) {
         return value;
       }
 
       @Override
-      public Splittable encode(Object value) {
-        return (Splittable) value;
+      public String toJsonExpression(Object value) {
+        return StringQuoter.quote((String) value);
       }
     },
     VOID(Void.class, void.class, null) {
       @Override
-      public Void decode(Class<?> clazz, Splittable value) {
-        return null;
-      }
-
-      @Override
-      public Splittable encode(Object value) {
+      public Void decode(Class<?> clazz, String value) {
         return null;
       }
     };
@@ -235,9 +190,7 @@
       return false;
     }
 
-    public abstract Object decode(Class<?> clazz, Splittable value);
-
-    public abstract Splittable encode(Object value);
+    public abstract Object decode(Class<?> clazz, String value);
 
     public Object getDefaultValue() {
       return defaultValue;
@@ -250,6 +203,10 @@
     public Class<?> getType() {
       return type;
     }
+
+    public String toJsonExpression(Object value) {
+      return String.valueOf(value);
+    }
   }
 
   private static final Set<Class<?>> ALL_VALUE_TYPES;
@@ -280,23 +237,19 @@
     return ValueCodexHelper.canDecode(clazz);
   }
 
-  @SuppressWarnings("unchecked")
   public static <T> T decode(Class<T> clazz, Splittable split) {
-    if (split == null || split == Splittable.NULL) {
+    if (split == null || split == LazySplittable.NULL) {
       return null;
     }
-    return (T) getTypeOrDie(clazz).decode(clazz, split);
+    return decode(clazz, split.asString());
   }
 
-  /**
-   * No callers in GWT codebase.
-   * 
-   * @deprecated use {@link #decode(Class, Splittable)} instead.
-   * @throws UnsupportedOperationException
-   */
-  @Deprecated
+  @SuppressWarnings("unchecked")
   public static <T> T decode(Class<T> clazz, String string) {
-    throw new UnsupportedOperationException();
+    if (string == null) {
+      return null;
+    }
+    return (T) getTypeOrDie(clazz).decode(clazz, string);
   }
 
   /**
@@ -305,14 +258,14 @@
    */
   public static Splittable encode(Class<?> clazz, Object obj) {
     if (obj == null) {
-      return Splittable.NULL;
+      return LazySplittable.NULL;
     }
-    return getTypeOrDie(clazz).encode(obj);
+    return new LazySplittable(getTypeOrDie(clazz).toJsonExpression(obj));
   }
 
   public static Splittable encode(Object obj) {
     if (obj == null) {
-      return Splittable.NULL;
+      return LazySplittable.NULL;
     }
     Type t = findType(obj.getClass());
     // Try upcasting
@@ -327,7 +280,7 @@
     if (t == null) {
       throw new UnsupportedOperationException(obj.getClass().getName());
     }
-    return t.encode(obj);
+    return new LazySplittable(t.toJsonExpression(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 d45da29..ad2dc20 100644
--- a/user/src/com/google/gwt/autobean/shared/impl/AbstractAutoBean.java
+++ b/user/src/com/google/gwt/autobean/shared/impl/AbstractAutoBean.java
@@ -20,9 +20,6 @@
 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;
@@ -35,7 +32,7 @@
  * 
  * @param <T> the wrapper type
  */
-public abstract class AbstractAutoBean<T> implements AutoBean<T>, HasSplittable {
+public abstract class AbstractAutoBean<T> implements AutoBean<T> {
   /**
    * Used to avoid cycles when visiting.
    */
@@ -47,50 +44,64 @@
     }
   }
 
-  public static final String UNSPLITTABLE_VALUES_KEY = "__unsplittableValues";
   protected static final Object[] EMPTY_OBJECT = new Object[0];
 
   /**
    * Used by {@link #createSimplePeer()}.
    */
-  protected Splittable data;
-  protected T wrapped;
+  protected final Map<String, Object> values;
+
   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;
-    wrapped = createSimplePeer();
+    values = new HashMap<String, Object>();
   }
 
   /**
-   * 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.
+   * Clone constructor.
    */
-  protected AbstractAutoBean(T wrapped, AutoBeanFactory factory) {
+  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) {
     this.factory = factory;
     usingSimplePeer = false;
-    data = null;
+    values = null;
     this.wrapped = wrapped;
 
     // Used by AutoBeanUtils
@@ -103,31 +114,17 @@
 
   public abstract T as();
 
-  public AutoBean<T> clone(boolean deep) {
-    throw new UnsupportedOperationException();
-  }
+  public abstract AutoBean<T> clone(boolean deep);
 
   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;
   }
@@ -136,16 +133,6 @@
     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;
   }
@@ -221,30 +208,11 @@
     return AutoBeanUtils.<W, W> getAutoBean(obj).as();
   }
 
-  /**
-   * 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();
+    if (wrapped == null) {
+      assert usingSimplePeer : "checkWrapped should have failed";
+      wrapped = createSimplePeer();
+    }
     return wrapped;
   }
 
@@ -265,27 +233,6 @@
   protected void set(String method, Object value) {
   }
 
-  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);
+  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
deleted file mode 100644
index 88cd297..0000000
--- a/user/src/com/google/gwt/autobean/shared/impl/AutoBeanCodexImpl.java
+++ /dev/null
@@ -1,607 +0,0 @@
-/*
- * 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) {
-    @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 0a3d298..3b53d1b 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 getEnum(Class<E> clazz, String token);
+  <E extends Enum<E>> 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
deleted file mode 100644
index 7ae4a33..0000000
--- a/user/src/com/google/gwt/autobean/shared/impl/HasSplittable.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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
new file mode 100644
index 0000000..2fb4749
--- /dev/null
+++ b/user/src/com/google/gwt/autobean/shared/impl/LazySplittable.java
@@ -0,0 +1,92 @@
+/*
+ * 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
deleted file mode 100644
index 4225512..0000000
--- a/user/src/com/google/gwt/autobean/shared/impl/SplittableComplexMap.java
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 0a7df50..0000000
--- a/user/src/com/google/gwt/autobean/shared/impl/SplittableList.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * 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
deleted file mode 100644
index fbd3f5d..0000000
--- a/user/src/com/google/gwt/autobean/shared/impl/SplittableSet.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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
deleted file mode 100644
index edfeff4..0000000
--- a/user/src/com/google/gwt/autobean/shared/impl/SplittableSimpleMap.java
+++ /dev/null
@@ -1,255 +0,0 @@
-/*
- * 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 559e2af..899ed52 100644
--- a/user/src/com/google/gwt/autobean/shared/impl/StringQuoter.java
+++ b/user/src/com/google/gwt/autobean/shared/impl/StringQuoter.java
@@ -31,36 +31,12 @@
  */
 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());
-
-  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();
-  }
+  private static final DateFormat RFC2822 = new SimpleDateFormat(
+      RFC2822_PATTERN, Locale.getDefault());
 
   /**
    * 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 7088266..e44bb35 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(*)("Error parsing JSON: " + e, json);
+        return @com.google.gwt.core.client.JsonUtils::throwIllegalArgumentException(Ljava/lang/String;)("Error parsing JSON: " + e);
       }
     } else {
       if (!@com.google.gwt.core.client.JsonUtils::safeToEval(Ljava/lang/String;)(json)) {
-        return @com.google.gwt.core.client.JsonUtils::throwIllegalArgumentException(*)("Illegal character in JSON string", json);
+        return @com.google.gwt.core.client.JsonUtils::throwIllegalArgumentException(Ljava/lang/String;)("Illegal character in JSON string");
       }
       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(*)("Error parsing JSON: " + e, json);
+        return @com.google.gwt.core.client.JsonUtils::throwIllegalArgumentException(Ljava/lang/String;)("Error parsing JSON: " + e);
       }
     }
   }-*/;
@@ -110,12 +110,12 @@
     try {
       return eval('(' + escaped + ')');
     } catch (e) {
-      return @com.google.gwt.core.client.JsonUtils::throwIllegalArgumentException(*)("Error parsing JSON: " + e, json);
+      return @com.google.gwt.core.client.JsonUtils::throwIllegalArgumentException(Ljava/lang/String;)("Error parsing JSON: " + e);
     }
   }-*/;
 
-  static void throwIllegalArgumentException(String message, String data) {
-    throw new IllegalArgumentException(message + "\n" + data);
+  static void throwIllegalArgumentException(String message) {
+    throw new IllegalArgumentException(message);
   }
 
   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 429dd6f..6487320 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,6 +51,7 @@
 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;
@@ -66,7 +67,8 @@
 /**
  * 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
@@ -108,7 +110,8 @@
        * 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()) {
@@ -149,23 +152,20 @@
 
       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);
-      // XXX expose this as a proper API
-      ((AbstractAutoBean<?>) bean).setData(result);
-      // AutoBeanCodex.decodeInto(result, bean);
+      AutoBeanCodex.decodeInto(result, bean);
 
       if (callback != null) {
         callback.onSuccess(bean.as());
@@ -178,8 +178,9 @@
     Splittable encode(Object obj) {
       Splittable value;
       if (obj == null) {
-        return Splittable.NULL;
-      } else if (obj.getClass().isEnum() && getAutoBeanFactory() instanceof EnumMap) {
+        return LazySplittable.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);
@@ -232,13 +233,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;
@@ -265,12 +266,13 @@
           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) {
@@ -366,14 +368,12 @@
    * 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,10 +405,12 @@
   /**
    * 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;
@@ -479,9 +481,10 @@
   /**
    * 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);
@@ -563,7 +566,8 @@
   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) {
@@ -602,7 +606,8 @@
   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) {
@@ -635,14 +640,16 @@
     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) {
@@ -658,8 +665,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();
@@ -703,9 +710,8 @@
     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);
@@ -714,7 +720,8 @@
     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);
     }
@@ -728,8 +735,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());
@@ -739,16 +746,15 @@
       // 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);
             }
           }
@@ -756,7 +762,8 @@
         }
 
         @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);
@@ -792,8 +799,8 @@
           continue;
         }
         requestFactory.getEventBus().fireEventFromSource(
-            new EntityProxyChange<EntityProxy>((EntityProxy) proxy, writeOperation),
-            id.getProxyClass());
+            new EntityProxyChange<EntityProxy>((EntityProxy) proxy,
+                writeOperation), id.getProxyClass());
       }
     }
     return proxy;
@@ -804,8 +811,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) {
@@ -854,25 +861,19 @@
    * 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(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));
+  private <T extends BaseProxy> AutoBean<T> cloneBeanAndCollections(
+      AutoBean<T> toClone) {
+    AutoBean<T> clone = toClone.clone(false);
     /*
      * 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) {
-        // javac generics bug
-        value =
-            AutoBeanUtils.<Collection<?>, Collection<?>> getAutoBean((Collection<?>) values
-                .get(propertyName));
+      public boolean visitCollectionProperty(String propertyName,
+          AutoBean<Collection<?>> value, CollectionPropertyContext ctx) {
         if (value != null) {
           Collection<Object> collection;
           if (List.class == ctx.getType()) {
@@ -884,20 +885,20 @@
             throw new IllegalArgumentException(ctx.getType().getName());
           }
 
-          if (isValueType(ctx.getElementType()) || isEntityType(ctx.getElementType())) {
+          if (isValueType(ctx.getElementType())) {
             /*
-             * Proxies must be edited up-front so that the elements in the
-             * collection have stable identity.
+             * Value proxies must be cloned upfront, since the value is replaced
+             * outright.
              */
             for (Object o : value.as()) {
               if (o == null) {
                 collection.add(null);
               } else {
-                collection.add(editProxy((BaseProxy) o));
+                collection.add(editProxy((ValueProxy) o));
               }
             }
           } else {
-            // For simple values, just copy the values
+            // For entities and simple values, just alias the values
             collection.addAll(value.as());
           }
 
@@ -907,30 +908,21 @@
       }
 
       @Override
-      public boolean visitReferenceProperty(String propertyName, AutoBean<?> value,
-          PropertyContext ctx) {
-        value = AutoBeanUtils.getAutoBean(values.get(propertyName));
+      public boolean visitReferenceProperty(String propertyName,
+          AutoBean<?> value, PropertyContext ctx) {
         if (value != null) {
-          if (isValueType(ctx.getType()) || isEntityType(ctx.getType())) {
+          if (isValueType(ctx.getType())) {
             /*
              * Value proxies must be cloned upfront, since the value is replaced
              * outright.
              */
             @SuppressWarnings("unchecked")
-            AutoBean<BaseProxy> valueBean = (AutoBean<BaseProxy>) value;
+            AutoBean<ValueProxy> valueBean = (AutoBean<ValueProxy>) 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;
   }
@@ -996,7 +988,8 @@
       }
 
       // 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));
       }
@@ -1015,8 +1008,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;
@@ -1087,7 +1080,7 @@
    */
   private <T extends BaseProxy> T takeOwnership(AutoBean<T> bean) {
     editedProxies.put(stableId(bean), bean);
-    bean.setTag(REQUEST_CONTEXT, this);
+    bean.setTag(REQUEST_CONTEXT, AbstractRequestContext.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 8aca80f..5566589 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,6 +19,7 @@
 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;
@@ -41,7 +42,8 @@
      * 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
@@ -57,9 +59,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 == Splittable.NULL) {
+  public static Object decode(EntitySource source, Class<?> type,
+      Class<?> elementType, Splittable split) {
+    if (split == null || split == LazySplittable.NULL) {
       return null;
     }
 
@@ -97,7 +99,8 @@
       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();
     }
 
@@ -108,8 +111,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);
   }
@@ -119,7 +122,7 @@
    */
   public static Splittable encode(EntitySource source, Object value) {
     if (value == null) {
-      return Splittable.NULL;
+      return LazySplittable.NULL;
     }
 
     if (value instanceof Poser<?>) {
@@ -143,7 +146,7 @@
         }
       }
       toReturn.append(']');
-      return StringQuoter.split(toReturn.toString());
+      return new LazySplittable(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 bfd0300..cb331b6 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,8 +17,6 @@
 
 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;
@@ -29,44 +27,18 @@
 /**
  * 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) {
-    char c = payload.charAt(0);
-    boolean isSimple = c != '{' && c != '[';
-    if (isSimple) {
+    boolean isString = payload.charAt(0) == '\"';
+    if (isString) {
       payload = "[" + payload + "]";
     }
-    Splittable toReturn = JsonUtils.safeEval(payload).<JsoSplittable> cast();
-    if (isSimple) {
+    Splittable toReturn = JsoSplittable.create(JsonUtils.safeEval(payload));
+    if (isString) {
       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 f8f8d6e..491e91c 100644
--- a/user/test/com/google/gwt/autobean/AutoBeanSuite.java
+++ b/user/test/com/google/gwt/autobean/AutoBeanSuite.java
@@ -18,9 +18,7 @@
 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;
@@ -36,8 +34,6 @@
     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 e83e46d..cc61684 100644
--- a/user/test/com/google/gwt/autobean/client/AutoBeanTest.java
+++ b/user/test/com/google/gwt/autobean/client/AutoBeanTest.java
@@ -263,6 +263,46 @@
     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();
@@ -275,6 +315,24 @@
     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();
@@ -377,14 +435,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);
       }
 
@@ -400,12 +458,14 @@
         } 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);
         }
@@ -452,8 +512,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());
@@ -469,7 +529,8 @@
       }
 
       @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
deleted file mode 100644
index af1c93a..0000000
--- a/user/test/com/google/gwt/autobean/server/SplittableJreTest.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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 43bfb35..8e686b8 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, null, MyEnum.BAZ);
+    List<MyEnum> arrayValue = Arrays.asList(MyEnum.FOO, MyEnum.BAR, MyEnum.BAZ);
     Map<MyEnum, Integer> mapValue = new HashMap<MyEnum, Integer>();
     mapValue.put(MyEnum.FOO, 0);
     mapValue.put(MyEnum.BAR, 1);
@@ -219,13 +219,6 @@
     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());
   }
 
   /**
@@ -236,13 +229,14 @@
     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() {
@@ -273,7 +267,8 @@
     }
     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()));
     }
   }
 
@@ -287,8 +282,7 @@
     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();
@@ -322,8 +316,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));
@@ -331,7 +325,8 @@
 
     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,
@@ -344,25 +339,30 @@
     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
deleted file mode 100644
index 17fd846..0000000
--- a/user/test/com/google/gwt/autobean/shared/SplittableTest.java
+++ /dev/null
@@ -1,318 +0,0 @@
-/*
- * 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"));
-  }
-
-  /**
-   * 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));
-  }
-}