Move AutoBean package to com.google.web.bindery.autobean package.
http://code.google.com/p/google-web-toolkit/issues/detail?id=6253
Review at http://gwt-code-reviews.appspot.com/1414803
Patch by: bobv
Review by: rjrjr


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10007 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/web/bindery/autobean/AutoBean.gwt.xml b/user/src/com/google/web/bindery/autobean/AutoBean.gwt.xml
new file mode 100644
index 0000000..88b8981
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/AutoBean.gwt.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" ?>
+<!--
+  Copyright 2010 Google Inc. Licensed under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with the
+  License. You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
+  or agreed to in writing, software distributed under the License is
+  distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied. See the License for the specific language
+  governing permissions and limitations under the License.
+-->
+
+<!-- AutoBean framework -->
+<module>
+  <inherits name="com.google.gwt.core.Core" />
+  <inherits name="com.google.gwt.user.User" />
+  <source path="gwt/client" />
+  <source path="shared" />
+  <super-source path="super" />
+  <generate-with class="com.google.web.bindery.autobean.gwt.rebind.AutoBeanFactoryGenerator">
+    <when-type-assignable class="com.google.web.bindery.autobean.shared.AutoBeanFactory" />
+  </generate-with>
+</module>
diff --git a/user/src/com/google/web/bindery/autobean/gwt/client/impl/AbstractAutoBeanFactory.java b/user/src/com/google/web/bindery/autobean/gwt/client/impl/AbstractAutoBeanFactory.java
new file mode 100644
index 0000000..fc3a0cd
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/gwt/client/impl/AbstractAutoBeanFactory.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.gwt.client.impl;
+
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory;
+import com.google.web.bindery.autobean.shared.impl.EnumMap;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Provides base implementations of AutoBeanFactory methods.
+ */
+public abstract class AbstractAutoBeanFactory implements AutoBeanFactory, EnumMap {
+
+  protected Map<Enum<?>, String> enumToStringMap;
+  // This map is almost always one-to-one
+  protected Map<String, List<Enum<?>>> stringsToEnumsMap;
+  private JsniCreatorMap creatorMap;
+
+  public <T> AutoBean<T> create(Class<T> clazz) {
+    maybeInitializeCreatorMap();
+    return creatorMap.create(clazz, this);
+  }
+
+  public <T, U extends T> AutoBean<T> create(Class<T> clazz, U delegate) {
+    maybeInitializeCreatorMap();
+    return creatorMap.create(clazz, this, delegate);
+  }
+
+  /**
+   * EnumMap support.
+   */
+  public <E extends Enum<?>> E getEnum(Class<E> clazz, String token) {
+    maybeInitializeEnumMap();
+    List<Enum<?>> list = stringsToEnumsMap.get(token);
+    if (list == null) {
+      throw new IllegalArgumentException(token);
+    }
+    for (Enum<?> e : list) {
+      if (e.getDeclaringClass().equals(clazz)) {
+        @SuppressWarnings("unchecked")
+        E toReturn = (E) e;
+        return toReturn;
+      }
+    }
+    throw new IllegalArgumentException(clazz.getName());
+  }
+
+  /**
+   * EnumMap support.
+   */
+  public String getToken(Enum<?> e) {
+    maybeInitializeEnumMap();
+    String toReturn = enumToStringMap.get(e);
+    if (toReturn == null) {
+      throw new IllegalArgumentException(e.toString());
+    }
+    return toReturn;
+  }
+
+  protected abstract void initializeCreatorMap(JsniCreatorMap creatorMap);
+
+  protected abstract void initializeEnumMap();
+
+  private void maybeInitializeCreatorMap() {
+    if (creatorMap == null) {
+      creatorMap = JsniCreatorMap.createMap();
+      initializeCreatorMap(creatorMap);
+    }
+  }
+
+  private void maybeInitializeEnumMap() {
+    if (enumToStringMap == null) {
+      enumToStringMap = new HashMap<Enum<?>, String>();
+      stringsToEnumsMap = new HashMap<String, List<Enum<?>>>();
+      initializeEnumMap();
+    }
+  }
+}
diff --git a/user/src/com/google/web/bindery/autobean/gwt/client/impl/ClientPropertyContext.java b/user/src/com/google/web/bindery/autobean/gwt/client/impl/ClientPropertyContext.java
new file mode 100644
index 0000000..08c753c
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/gwt/client/impl/ClientPropertyContext.java
@@ -0,0 +1,161 @@
+/*
+ * 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.web.bindery.autobean.gwt.client.impl;
+
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor.CollectionPropertyContext;
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor.MapPropertyContext;
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor.ParameterizationVisitor;
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor.PropertyContext;
+import com.google.web.bindery.autobean.shared.impl.AbstractAutoBean;
+import com.google.gwt.core.client.JavaScriptObject;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Provides base methods for generated implementations of PropertyContext.
+ */
+public final class ClientPropertyContext implements PropertyContext, CollectionPropertyContext,
+    MapPropertyContext {
+
+  /**
+   * A reference to an instance setter method.
+   */
+  public static final class Setter extends JavaScriptObject {
+    /**
+     * Create a trivial Setter that calls {@link AbstractAutoBean#setProperty()}
+     * .
+     */
+    public static native Setter beanSetter(AbstractAutoBean<?> bean, String key) /*-{
+      return function(value) {
+        bean.@com.google.web.bindery.autobean.shared.impl.AbstractAutoBean::setProperty(*)(key, value);
+      };
+    }-*/;
+
+    protected Setter() {
+    }
+
+    public native void call(Object instance, Object value) /*-{
+      this.call(instance, value);
+    }-*/;
+  }
+
+  private final Object instance;
+  private final int[] paramCounts;
+  private final Class<?>[] paramTypes;
+  private final Setter setter;
+  private final Class<?> simpleType;
+
+  public ClientPropertyContext(Object instance, Setter setter, Class<?> type) {
+    this.instance = instance;
+    this.setter = setter;
+    this.simpleType = type;
+    this.paramTypes = null;
+    this.paramCounts = null;
+  }
+
+  public ClientPropertyContext(Object instance, Setter setter, Class<?>[] types, int[] paramCounts) {
+    this.instance = instance;
+    this.setter = setter;
+    this.simpleType = null;
+    this.paramTypes = types;
+    this.paramCounts = paramCounts;
+
+    /*
+     * Verify input arrays of same length and that the total parameter count,
+     * plus one for the root type, equals the total number of types passed in.
+     */
+    if (ClientPropertyContext.class.desiredAssertionStatus()) {
+      assert types.length == paramCounts.length : "Length mismatch " + types.length + " != "
+          + paramCounts.length;
+      int count = 1;
+      for (int i = 0, j = paramCounts.length; i < j; i++) {
+        count += paramCounts[i];
+      }
+      assert count == types.length : "Mismatch in total parameter count " + count + " != "
+          + types.length;
+    }
+  }
+
+  public void accept(ParameterizationVisitor visitor) {
+    traverse(visitor, 0);
+  }
+
+  public boolean canSet() {
+    return setter != null;
+  }
+
+  public Class<?> getElementType() {
+    if (paramTypes == null || paramTypes.length < 2) {
+      return null;
+    }
+    if (List.class.equals(paramTypes[0]) || Set.class.equals(paramTypes[0])) {
+      return paramTypes[1];
+    }
+    return null;
+  }
+
+  public Class<?> getKeyType() {
+    if (paramTypes == null || paramTypes.length < 3) {
+      return null;
+    }
+    if (Map.class.equals(paramTypes[0])) {
+      return paramTypes[1];
+    }
+    return null;
+  }
+
+  public Class<?> getType() {
+    return simpleType == null ? paramTypes[0] : simpleType;
+  }
+
+  public Class<?> getValueType() {
+    if (paramTypes == null || paramTypes.length < 3) {
+      return null;
+    }
+    if (Map.class.equals(paramTypes[0])) {
+      return paramTypes[2];
+    }
+    return null;
+  }
+
+  public void set(Object value) {
+    setter.call(instance, value);
+  }
+
+  private int traverse(ParameterizationVisitor visitor, int count) {
+    if (simpleType != null) {
+      visitor.visitType(simpleType);
+      visitor.endVisitType(simpleType);
+      return 0;
+    }
+
+    Class<?> type = paramTypes[count];
+    int paramCount = paramCounts[count];
+    ++count;
+    if (visitor.visitType(type)) {
+      for (int i = 0; i < paramCount; i++) {
+        if (visitor.visitParameter()) {
+          count = traverse(visitor, count);
+        }
+        visitor.endVisitParameter();
+      }
+    }
+    visitor.endVisitType(type);
+    return count;
+  }
+}
diff --git a/user/src/com/google/web/bindery/autobean/gwt/client/impl/JsniCreatorMap.java b/user/src/com/google/web/bindery/autobean/gwt/client/impl/JsniCreatorMap.java
new file mode 100644
index 0000000..d6e4468
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/gwt/client/impl/JsniCreatorMap.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.gwt.client.impl;
+
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+
+/**
+ * Used in prod-mode code to create instances of generated AutoBean subtypes via
+ * JSNI references to their constructor methods.
+ */
+public final class JsniCreatorMap extends JavaScriptObject {
+  public static JsniCreatorMap createMap() {
+    return JavaScriptObject.createObject().cast();
+  }
+
+  /*
+   * Structure is a string map of class literal names to the no-arg and one-arg
+   * constructors of a generated AutoBean subtype.
+   */
+  protected JsniCreatorMap() {
+  }
+
+  public void add(Class<?> clazz, JsArray<JavaScriptObject> constructors) {
+    assert constructors.length() == 2 : "Expecting two constructor references";
+    set(clazz.getName(), constructors);
+  }
+
+  public <T> AutoBean<T> create(Class<T> clazz, AbstractAutoBeanFactory factory) {
+    JsArray<JavaScriptObject> arr = get(clazz.getName());
+    if (arr != null && arr.get(0) != null) {
+      return invoke(arr.get(0), factory, null);
+    }
+    return null;
+  }
+
+  public <T> AutoBean<T> create(Class<T> clazz, AbstractAutoBeanFactory factory, Object delegate) {
+    JsArray<JavaScriptObject> arr = get(clazz.getName());
+    if (arr != null) {
+      assert arr.get(1) != null : "No delegate-based constructor";
+      return invoke(arr.get(1), factory, delegate);
+    }
+    return null;
+  }
+
+  private native JsArray<JavaScriptObject> get(String key) /*-{
+    return this[key];
+  }-*/;
+
+  private native <T> AutoBean<T> invoke(JavaScriptObject fn, Object arg1, Object arg2)/*-{
+    return fn(arg1, arg2);
+  }-*/;
+
+  private native void set(String key, JsArray<JavaScriptObject> arr) /*-{
+    this[key] = arr;
+  }-*/;
+}
diff --git a/user/src/com/google/web/bindery/autobean/gwt/client/impl/JsoSplittable.java b/user/src/com/google/web/bindery/autobean/gwt/client/impl/JsoSplittable.java
new file mode 100644
index 0000000..33eba24
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/gwt/client/impl/JsoSplittable.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.gwt.client.impl;
+
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.shared.impl.HasSplittable;
+import com.google.web.bindery.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;
+import java.util.List;
+
+/**
+ * Implements the EntityCodex.Splittable interface using a raw JavaScriptObject.
+ * <p>
+ * A string value represented by a JsoSplittable can't use the string object
+ * directly, since {@code String.prototype} is overridden, so instead a
+ * temporary wrapper object is used to encapsulate the string data.
+ */
+@GwtScriptOnly
+public final class JsoSplittable extends JavaScriptObject implements Splittable, HasSplittable {
+  private static boolean stringifyFastTested;
+  private static boolean stringifyFastResult;
+
+  public static native JsoSplittable create() /*-{
+    return {};
+  }-*/;
+
+  public static Splittable create(boolean value) {
+    return create0(value);
+  }
+
+  public static Splittable create(double value) {
+    return create0(value);
+  }
+
+  public static Splittable create(String value) {
+    return create0(value);
+  }
+
+  public static native JsoSplittable createIndexed() /*-{
+    return [];
+  }-*/;
+
+  public static native JsoSplittable nullValue() /*-{
+    return null;
+  }-*/;
+
+  private static native Splittable create0(boolean object) /*-{
+    return Boolean(object);
+  }-*/;
+
+  private static native Splittable create0(double object) /*-{
+    return Number(object);
+  }-*/;
+
+  private static native Splittable create0(String object) /*-{
+    return {
+      __s : object
+    };
+  }-*/;
+
+  private static native boolean isUnwrappedString(JavaScriptObject obj) /*-{
+    return Object.prototype.toString.call(obj) == '[object String]';
+  }-*/;
+
+  private static boolean stringifyFastSupported() {
+    if (stringifyFastTested) {
+      return stringifyFastResult;
+    }
+    stringifyFastTested = true;
+    return stringifyFastResult = stringifyFastSupported0();
+  }
+
+  /**
+   * Test that the JSON api is available and that it does not add function
+   * objects to the output. The test for function objects is for old versions of
+   * Safari.
+   */
+  private static native boolean stringifyFastSupported0() /*-{
+    return $wnd.JSON && $wnd.JSON.stringify && $wnd.JSON.stringify({
+      b : function() {
+      }
+    }) == '{}';
+  }-*/;
+
+  protected JsoSplittable() {
+  };
+
+  public native boolean asBoolean() /*-{
+    return this == 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;
+  }-*/;
+
+  public Splittable deepCopy() {
+    return StringQuoter.split(getPayload());
+  }
+
+  public JsoSplittable get(int index) {
+    return getRaw(index);
+  }
+
+  public JsoSplittable get(String key) {
+    return getRaw(key);
+  }
+
+  public String getPayload() {
+    if (isString()) {
+      return JsonUtils.escapeValue(asString());
+    }
+    if (stringifyFastSupported()) {
+      return stringifyFast();
+    }
+    return stringifySlow();
+  }
+
+  public List<String> getPropertyKeys() {
+    List<String> toReturn = new ArrayList<String>();
+    getPropertyKeys0(toReturn);
+    return Collections.unmodifiableList(toReturn);
+  }
+
+  public native Object getReified(String key) /*-{
+    return this.__reified && this.__reified[':' + key];
+  }-*/;
+
+  public Splittable getSplittable() {
+    return this;
+  }
+
+  public native boolean isBoolean() /*-{
+    return Object.prototype.toString.call(this) == '[object Boolean]';
+  }-*/;
+
+  public native boolean isFunction() /*-{
+    return Object.prototype.toString.call(this) == '[object Function]';
+  }-*/;
+
+  public native boolean isIndexed() /*-{
+    return Object.prototype.toString.call(this) == '[object Array]';
+  }-*/;
+
+  public boolean isKeyed() {
+    return this != NULL && !isString() && !isIndexed() && !isFunction();
+  }
+
+  public native boolean isNull(int index) /*-{
+    return this[index] == null;
+  }-*/;
+
+  public native boolean isNull(String key) /*-{
+    return this[key] == null;
+  }-*/;
+
+  public native boolean isNumber() /*-{
+    return Object.prototype.toString.call(this) == '[object Number]';
+  }-*/;
+
+  public native boolean isReified(String key) /*-{
+    return !!(this.__reified && this.__reified.hasOwnProperty(':' + key));
+  }-*/;
+
+  /**
+   * Returns whether or not the current object is a string-carrier.
+   */
+  public native boolean isString() /*-{
+    return this && this.__s != null;
+  }-*/;
+
+  public native boolean isUndefined(String key) /*-{
+    return this[key] === undefined;
+  }-*/;
+
+  public native void setReified(String key, Object object) /*-{
+    // Use a function object so native JSON.stringify will ignore
+    (this.__reified || (this.__reified = function() {
+    }))[':' + key] = object;
+  }-*/;
+
+  public native void setSize(int size) /*-{
+    this.length = size;
+  }-*/;
+
+  public native int size() /*-{
+    return this.length;
+  }-*/;
+
+  private native void assign0(Splittable parent, int index, Splittable value) /*-{
+    parent[index] = value;
+  }-*/;
+
+  private native void assign0(Splittable parent, int index, String value) /*-{
+    parent[index] = value;
+  }-*/;
+
+  private native void assign0(Splittable parent, String index, Splittable value) /*-{
+    parent[index] = value;
+  }-*/;
+
+  private native void assign0(Splittable parent, String index, String value) /*-{
+    parent[index] = value;
+  }-*/;
+
+  private native void getPropertyKeys0(List<String> list) /*-{
+    for (key in this) {
+      if (this.hasOwnProperty(key)) {
+        list.@java.util.List::add(Ljava/lang/Object;)(key);
+      }
+    }
+  }-*/;
+
+  private native JsoSplittable getRaw(int index) /*-{
+    _ = this[index];
+    if (_ == null) {
+      return null;
+    }
+    if (@com.google.web.bindery.autobean.gwt.client.impl.JsoSplittable::isUnwrappedString(*)(_)) {
+      return @com.google.web.bindery.autobean.gwt.client.impl.JsoSplittable::create(Ljava/lang/String;)(_);
+    }
+    return Object(_);
+  }-*/;
+
+  private native JsoSplittable getRaw(String index) /*-{
+    _ = this[index];
+    if (_ == null) {
+      return null;
+    }
+    if (@com.google.web.bindery.autobean.gwt.client.impl.JsoSplittable::isUnwrappedString(*)(_)) {
+      return @com.google.web.bindery.autobean.gwt.client.impl.JsoSplittable::create(Ljava/lang/String;)(_);
+    }
+    return Object(_);
+  }-*/;
+
+  /**
+   * The test for {@code $H} removes the key in the emitted JSON, however making
+   * a similar test for {@code __reified} causes the key to be emitted with an
+   * explicit {@code null} value.
+   */
+  private native String stringifyFast() /*-{
+    return $wnd.JSON.stringify(this, function(key, value) {
+      if (key == "$H") {
+        return;
+      }
+      return value;
+    });
+  }-*/;
+
+  private String stringifySlow() {
+    StringBuilder sb = new StringBuilder();
+    stringifySlow(sb);
+    return sb.toString();
+  }
+
+  private void stringifySlow(StringBuilder sb) {
+    if (this == NULL) {
+      sb.append("null");
+      return;
+    }
+    if (isBoolean()) {
+      sb.append(asBoolean());
+      return;
+    }
+    if (isNumber()) {
+      sb.append(asNumber());
+      return;
+    }
+    if (isString()) {
+      sb.append(JsonUtils.escapeValue(asString()));
+      return;
+    }
+    if (isIndexed()) {
+      sb.append("[");
+      for (int i = 0, j = size(); i < j; i++) {
+        if (i > 0) {
+          sb.append(",");
+        }
+        get(i).stringifySlow(sb);
+      }
+      sb.append("]");
+      return;
+    }
+
+    sb.append("{");
+    boolean needsComma = false;
+    for (String key : getPropertyKeys()) {
+      if (needsComma) {
+        sb.append(",");
+      } else {
+        needsComma = true;
+      }
+      JsoSplittable value = get(key);
+      if (!value.isFunction()) {
+        if ("$H".equals(key)) {
+          // Ignore hashcode
+          continue;
+        }
+        sb.append(JsonUtils.escapeValue(key));
+        sb.append(":");
+        value.stringifySlow(sb);
+      }
+    }
+    sb.append("}");
+  }
+}
diff --git a/user/src/com/google/web/bindery/autobean/gwt/rebind/AutoBeanFactoryGenerator.java b/user/src/com/google/web/bindery/autobean/gwt/rebind/AutoBeanFactoryGenerator.java
new file mode 100644
index 0000000..0ec39b4
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/gwt/rebind/AutoBeanFactoryGenerator.java
@@ -0,0 +1,726 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.gwt.rebind;
+
+import com.google.web.bindery.autobean.gwt.client.impl.AbstractAutoBeanFactory;
+import com.google.web.bindery.autobean.gwt.client.impl.ClientPropertyContext;
+import com.google.web.bindery.autobean.gwt.client.impl.JsniCreatorMap;
+import com.google.web.bindery.autobean.gwt.rebind.model.AutoBeanFactoryMethod;
+import com.google.web.bindery.autobean.gwt.rebind.model.AutoBeanFactoryModel;
+import com.google.web.bindery.autobean.gwt.rebind.model.AutoBeanMethod;
+import com.google.web.bindery.autobean.gwt.rebind.model.AutoBeanType;
+import com.google.web.bindery.autobean.gwt.rebind.model.JBeanMethod;
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory;
+import com.google.web.bindery.autobean.shared.AutoBeanUtils;
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor;
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.shared.impl.AbstractAutoBean;
+import com.google.web.bindery.autobean.shared.impl.AbstractAutoBean.OneShotContext;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.core.client.impl.WeakMapping;
+import com.google.gwt.core.ext.Generator;
+import com.google.gwt.core.ext.GeneratorContext;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JEnumConstant;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JParameter;
+import com.google.gwt.core.ext.typeinfo.JParameterizedType;
+import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
+import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.editor.rebind.model.ModelUtils;
+import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
+import com.google.gwt.user.rebind.SourceWriter;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Generates implementations of AutoBeanFactory.
+ */
+public class AutoBeanFactoryGenerator extends Generator {
+
+  private GeneratorContext context;
+  private String simpleSourceName;
+  private TreeLogger logger;
+  private AutoBeanFactoryModel model;
+
+  @Override
+  public String generate(TreeLogger logger, GeneratorContext context, String typeName)
+      throws UnableToCompleteException {
+    this.context = context;
+    this.logger = logger;
+
+    TypeOracle oracle = context.getTypeOracle();
+    JClassType toGenerate = oracle.findType(typeName).isInterface();
+    if (toGenerate == null) {
+      logger.log(TreeLogger.ERROR, typeName + " is not an interface type");
+      throw new UnableToCompleteException();
+    }
+
+    String packageName = toGenerate.getPackage().getName();
+    simpleSourceName = toGenerate.getName().replace('.', '_') + "Impl";
+    PrintWriter pw = context.tryCreate(logger, packageName, simpleSourceName);
+    if (pw == null) {
+      return packageName + "." + simpleSourceName;
+    }
+
+    model = new AutoBeanFactoryModel(logger, toGenerate);
+
+    ClassSourceFileComposerFactory factory =
+        new ClassSourceFileComposerFactory(packageName, simpleSourceName);
+    factory.setSuperclass(AbstractAutoBeanFactory.class.getCanonicalName());
+    factory.addImplementedInterface(typeName);
+    SourceWriter sw = factory.createSourceWriter(context, pw);
+    for (AutoBeanType type : model.getAllTypes()) {
+      writeAutoBean(type);
+    }
+    writeDynamicMethods(sw);
+    writeEnumSetup(sw);
+    writeMethods(sw);
+    sw.commit(logger);
+
+    return factory.getCreatedClassName();
+  }
+
+  /**
+   * Flattens a parameterized type into a simple list of types.
+   */
+  private void createTypeList(List<JType> accumulator, JType type) {
+    accumulator.add(type);
+    JParameterizedType hasParams = type.isParameterized();
+    if (hasParams != null) {
+      for (JClassType arg : hasParams.getTypeArgs()) {
+        createTypeList(accumulator, arg);
+      }
+    }
+  }
+
+  private String getBaseMethodDeclaration(JMethod jmethod) {
+    // Foo foo, Bar bar, Baz baz
+    StringBuilder parameters = new StringBuilder();
+    for (JParameter param : jmethod.getParameters()) {
+      parameters.append(",").append(ModelUtils.getQualifiedBaseSourceName(param.getType())).append(
+          " ").append(param.getName());
+    }
+    if (parameters.length() > 0) {
+      parameters = parameters.deleteCharAt(0);
+    }
+
+    StringBuilder throwsDeclaration = new StringBuilder();
+    if (jmethod.getThrows().length > 0) {
+      for (JType thrown : jmethod.getThrows()) {
+        throwsDeclaration.append(". ").append(ModelUtils.getQualifiedBaseSourceName(thrown));
+      }
+      throwsDeclaration.deleteCharAt(0);
+      throwsDeclaration.insert(0, "throws ");
+    }
+    String returnName = ModelUtils.getQualifiedBaseSourceName(jmethod.getReturnType());
+    assert !returnName.contains("extends");
+    return String.format("%s %s(%s) %s", returnName, jmethod.getName(), parameters,
+        throwsDeclaration);
+  }
+
+  /**
+   * Used by {@link #writeShim} to avoid duplicate declarations of Object
+   * methods.
+   */
+  private boolean isObjectMethodImplementedByShim(JMethod jmethod) {
+    String methodName = jmethod.getName();
+    JParameter[] parameters = jmethod.getParameters();
+    switch (parameters.length) {
+      case 0:
+        return methodName.equals("hashCode") || methodName.equals("toString");
+      case 1:
+        return methodName.equals("equals")
+            && parameters[0].getType().equals(context.getTypeOracle().getJavaLangObject());
+    }
+    return false;
+  }
+
+  private void writeAutoBean(AutoBeanType type) throws UnableToCompleteException {
+    PrintWriter pw = context.tryCreate(logger, type.getPackageNome(), type.getSimpleSourceName());
+    if (pw == null) {
+      // Previously-created
+      return;
+    }
+
+    ClassSourceFileComposerFactory factory =
+        new ClassSourceFileComposerFactory(type.getPackageNome(), type.getSimpleSourceName());
+    factory.setSuperclass(AbstractAutoBean.class.getCanonicalName() + "<"
+        + type.getPeerType().getQualifiedSourceName() + ">");
+    SourceWriter sw = factory.createSourceWriter(context, pw);
+
+    writeShim(sw, type);
+
+    // Instance initializer code to set the shim's association
+    sw.println("{ %s.set(shim, %s.class.getName(), this); }", WeakMapping.class.getCanonicalName(),
+        AutoBean.class.getCanonicalName());
+
+    // Only simple wrappers have a default constructor
+    if (type.isSimpleBean()) {
+      // public FooIntfAutoBean(AutoBeanFactory factory) {}
+      sw.println("public %s(%s factory) {super(factory);}", type.getSimpleSourceName(),
+          AutoBeanFactory.class.getCanonicalName());
+    }
+
+    // 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.println("}");
+
+    // public FooIntf as() {return shim;}
+    sw.println("public %s as() {return shim;}", type.getPeerType().getQualifiedSourceName());
+
+    // public Class<Intf> getType() {return Intf.class;}
+    sw.println("public Class<%1$s> getType() {return %1$s.class;}", ModelUtils.ensureBaseType(
+        type.getPeerType()).getQualifiedSourceName());
+
+    if (type.isSimpleBean()) {
+      writeCreateSimpleBean(sw, type);
+    }
+    writeTraversal(sw, type);
+    sw.commit(logger);
+  }
+
+  /**
+   * For interfaces that consist of nothing more than getters and setters,
+   * create a map-based implementation that will allow the AutoBean's internal
+   * state to be easily consumed.
+   */
+  private void writeCreateSimpleBean(SourceWriter sw, AutoBeanType type) {
+    sw.println("@Override protected %s createSimplePeer() {", type.getPeerType()
+        .getQualifiedSourceName());
+    sw.indent();
+    // 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());
+          } else {
+            // return (ReturnType) values.getOrReify(\"foo\");
+            castType = ModelUtils.getQualifiedBaseSourceName(returnType);
+            sw.println("return (%s) getOrReify(\"%s\");", castType, 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());
+          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)) {
+            sw.print("return ");
+          }
+          sw.print("%s.%s(%s.this", staticImpl.getEnclosingType().getQualifiedSourceName(),
+              staticImpl.getName(), type.getSimpleSourceName());
+          for (JParameter param : jmethod.getParameters()) {
+            sw.print(", %s", param.getName());
+          }
+          sw.println(");");
+          break;
+        default:
+          throw new RuntimeException();
+      }
+      sw.outdent();
+      sw.println("}");
+    }
+    sw.outdent();
+    sw.println("};");
+    sw.outdent();
+    sw.println("}");
+  }
+
+  /**
+   * Write an instance initializer block to populate the creators map.
+   */
+  private void writeDynamicMethods(SourceWriter sw) {
+    List<JClassType> privatePeers = new ArrayList<JClassType>();
+    sw.println("@Override protected void initializeCreatorMap(%s map) {", JsniCreatorMap.class
+        .getCanonicalName());
+    sw.indent();
+    for (AutoBeanType type : model.getAllTypes()) {
+      if (type.isNoWrap()) {
+        continue;
+      }
+      String classLiteralAccessor;
+      JClassType peer = type.getPeerType();
+      String peerName = ModelUtils.ensureBaseType(peer).getQualifiedSourceName();
+      if (peer.isPublic()) {
+        classLiteralAccessor = peerName + ".class";
+      } else {
+        privatePeers.add(peer);
+        classLiteralAccessor = "classLit_" + peerName.replace('.', '_') + "()";
+      }
+      // map.add(Foo.class, getConstructors_com_foo_Bar());
+      sw.println("map.add(%s, getConstructors_%s());", classLiteralAccessor, peerName.replace('.',
+          '_'));
+    }
+    sw.outdent();
+    sw.println("}");
+
+    /*
+     * Create a native method for each peer type that isn't public since Java
+     * class literal references are scoped.
+     */
+    for (JClassType peer : privatePeers) {
+      String peerName = ModelUtils.ensureBaseType(peer).getQualifiedSourceName();
+      sw.println("private native Class<?> classLit_%s() /*-{return @%s::class;}-*/;", peerName
+          .replace('.', '_'), peerName);
+    }
+
+    /*
+     * Create a method that returns an array containing references to the
+     * constructors.
+     */
+    String factoryJNIName =
+        context.getTypeOracle().findType(AutoBeanFactory.class.getCanonicalName())
+            .getJNISignature();
+    for (AutoBeanType type : model.getAllTypes()) {
+      String peerName = ModelUtils.ensureBaseType(type.getPeerType()).getQualifiedSourceName();
+      String peerJNIName = ModelUtils.ensureBaseType(type.getPeerType()).getJNISignature();
+      /*-
+       * private native JsArray<JSO> getConstructors_com_foo_Bar() {
+       *   return [
+       *     BarProxyImpl::new(ABFactory),
+       *     BarProxyImpl::new(ABFactory, DelegateType)
+       *   ];
+       * }
+       */
+      sw.println("private native %s<%s> getConstructors_%s() /*-{", JsArray.class
+          .getCanonicalName(), JavaScriptObject.class.getCanonicalName(), peerName
+          .replace('.', '_'));
+      sw.indent();
+      sw.println("return [");
+      if (type.isSimpleBean()) {
+        sw.indentln("@%s::new(%s),", type.getQualifiedSourceName(), factoryJNIName);
+      } else {
+        sw.indentln(",");
+      }
+      sw.indentln("@%s::new(%s%s)", type.getQualifiedSourceName(), factoryJNIName, peerJNIName);
+      sw.println("];");
+      sw.outdent();
+      sw.println("}-*/;");
+    }
+  }
+
+  private void writeEnumSetup(SourceWriter sw) {
+    // Make the deobfuscation model
+    Map<String, List<JEnumConstant>> map = new HashMap<String, List<JEnumConstant>>();
+    for (Map.Entry<JEnumConstant, String> entry : model.getEnumTokenMap().entrySet()) {
+      List<JEnumConstant> list = map.get(entry.getValue());
+      if (list == null) {
+        list = new ArrayList<JEnumConstant>();
+        map.put(entry.getValue(), list);
+      }
+      list.add(entry.getKey());
+    }
+
+    sw.println("@Override protected void initializeEnumMap() {");
+    sw.indent();
+    for (Map.Entry<JEnumConstant, String> entry : model.getEnumTokenMap().entrySet()) {
+      // enumToStringMap.put(Enum.FOO, "FOO");
+      sw.println("enumToStringMap.put(%s.%s, \"%s\");", entry.getKey().getEnclosingType()
+          .getQualifiedSourceName(), entry.getKey().getName(), entry.getValue());
+    }
+    for (Map.Entry<String, List<JEnumConstant>> entry : map.entrySet()) {
+      String listExpr;
+      if (entry.getValue().size() == 1) {
+        JEnumConstant e = entry.getValue().get(0);
+        // Collections.singletonList(Enum.FOO)
+        listExpr =
+            String.format("%s.<%s<?>> singletonList(%s.%s)", Collections.class.getCanonicalName(),
+                Enum.class.getCanonicalName(), e.getEnclosingType().getQualifiedSourceName(), e
+                    .getName());
+      } else {
+        // Arrays.asList(Enum.FOO, OtherEnum.FOO, ThirdEnum,FOO)
+        StringBuilder sb = new StringBuilder();
+        boolean needsComma = false;
+        sb.append(String.format("%s.<%s<?>> asList(", Arrays.class.getCanonicalName(), Enum.class
+            .getCanonicalName()));
+        for (JEnumConstant e : entry.getValue()) {
+          if (needsComma) {
+            sb.append(",");
+          }
+          needsComma = true;
+          sb.append(e.getEnclosingType().getQualifiedSourceName()).append(".").append(e.getName());
+        }
+        sb.append(")");
+        listExpr = sb.toString();
+      }
+      sw.println("stringsToEnumsMap.put(\"%s\", %s);", entry.getKey(), listExpr);
+    }
+    sw.outdent();
+    sw.println("}");
+  }
+
+  private void writeMethods(SourceWriter sw) throws UnableToCompleteException {
+    for (AutoBeanFactoryMethod method : model.getMethods()) {
+      AutoBeanType autoBeanType = method.getAutoBeanType();
+      // public AutoBean<Foo> foo(FooSubtype wrapped) {
+      sw.println("public %s %s(%s) {", method.getReturnType().getQualifiedSourceName(), method
+          .getName(), method.isWrapper()
+          ? (method.getWrappedType().getQualifiedSourceName() + " wrapped") : "");
+      if (method.isWrapper()) {
+        sw.indent();
+        // AutoBean<Foo> toReturn = AutoBeanUtils.getAutoBean(wrapped);
+        sw.println("%s toReturn = %s.getAutoBean(wrapped);", method.getReturnType()
+            .getParameterizedQualifiedSourceName(), AutoBeanUtils.class.getCanonicalName());
+        sw.println("if (toReturn != null) {return toReturn;}");
+        // return new FooAutoBean(Factory.this, wrapped);
+        sw.println("return new %s(%s.this, wrapped);", autoBeanType.getQualifiedSourceName(),
+            simpleSourceName);
+        sw.outdent();
+      } else {
+        // return new FooAutoBean(Factory.this);
+        sw.indentln("return new %s(%s.this);", autoBeanType.getQualifiedSourceName(),
+            simpleSourceName);
+      }
+      sw.println("}");
+    }
+  }
+
+  private void writeReturnWrapper(SourceWriter sw, AutoBeanType type, AutoBeanMethod method)
+      throws UnableToCompleteException {
+    if (!method.isValueType() && !method.isNoWrap()) {
+      JMethod jmethod = method.getMethod();
+      JClassType returnClass = jmethod.getReturnType().isClassOrInterface();
+      AutoBeanType peer = model.getPeer(returnClass);
+
+      sw.println("if (toReturn != null) {");
+      sw.indent();
+      sw.println("if (%s.this.isWrapped(toReturn)) {", type.getSimpleSourceName());
+      sw.indentln("toReturn = %s.this.getFromWrapper(toReturn);", type.getSimpleSourceName());
+      sw.println("} else {");
+      sw.indent();
+      if (peer != null) {
+        // toReturn = new FooAutoBean(getFactory(), toReturn).as();
+        sw.println("toReturn = new %s(getFactory(), toReturn).as();", peer.getQualifiedSourceName());
+      }
+      sw.outdent();
+      sw.println("}");
+
+      sw.outdent();
+      sw.println("}");
+    }
+    // Allow return values to be intercepted
+    JMethod interceptor = type.getInterceptor();
+    if (interceptor != null) {
+      // toReturn = FooCategory.__intercept(FooAutoBean.this, toReturn);
+      sw.println("toReturn = %s.%s(%s.this, toReturn);", interceptor.getEnclosingType()
+          .getQualifiedSourceName(), interceptor.getName(), type.getSimpleSourceName());
+    }
+  }
+
+  /**
+   * Create the shim instance of the AutoBean's peer type that lets us hijack
+   * the method calls. Using a shim type, as opposed to making the AutoBean
+   * implement the peer type directly, means that there can't be any conflicts
+   * between methods in the peer type and methods declared in the AutoBean
+   * implementation.
+   */
+  private void writeShim(SourceWriter sw, AutoBeanType type) throws UnableToCompleteException {
+    // private final FooImpl shim = new FooImpl() {
+    sw.println("private final %1$s shim = new %1$s() {", type.getPeerType()
+        .getQualifiedSourceName());
+    sw.indent();
+    for (AutoBeanMethod method : type.getMethods()) {
+      JMethod jmethod = method.getMethod();
+      String methodName = jmethod.getName();
+      JParameter[] parameters = jmethod.getParameters();
+      if (isObjectMethodImplementedByShim(jmethod)) {
+        // Skip any methods declared on Object, since we have special handling
+        continue;
+      }
+
+      // foo, bar, baz
+      StringBuilder arguments = new StringBuilder();
+      {
+        for (JParameter param : parameters) {
+          arguments.append(",").append(param.getName());
+        }
+        if (arguments.length() > 0) {
+          arguments = arguments.deleteCharAt(0);
+        }
+      }
+
+      sw.println("public %s {", getBaseMethodDeclaration(jmethod));
+      sw.indent();
+
+      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);
+
+          // Non-value types might need to be wrapped
+          writeReturnWrapper(sw, type, method);
+          sw.println("return toReturn;");
+          break;
+        case SET:
+        case SET_BUILDER:
+          // getWrapped().setFoo(foo);
+          sw.println("%s.this.getWrapped().%s(%s);", type.getSimpleSourceName(), methodName,
+              parameters[0].getName());
+          // FooAutoBean.this.set("setFoo", foo);
+          sw.println("%s.this.set(\"%s\", %s);", type.getSimpleSourceName(), methodName,
+              parameters[0].getName());
+          if (JBeanMethod.SET_BUILDER.equals(method.getAction())) {
+            sw.println("return this;");
+          }
+          break;
+        case CALL:
+          // XXX How should freezing and calls work together?
+          // sw.println("checkFrozen();");
+          if (JPrimitiveType.VOID.equals(jmethod.getReturnType())) {
+            // getWrapped().doFoo(params);
+            sw.println("%s.this.getWrapped().%s(%s);", type.getSimpleSourceName(), methodName,
+                arguments);
+            // call("doFoo", null, params);
+            sw.println("%s.this.call(\"%s\", null%s %s);", type.getSimpleSourceName(), methodName,
+                arguments.length() > 0 ? "," : "", arguments);
+          } else {
+            // Type toReturn = getWrapped().doFoo(params);
+            sw.println("%s toReturn = %s.this.getWrapped().%s(%s);", ModelUtils.ensureBaseType(
+                jmethod.getReturnType()).getQualifiedSourceName(), type.getSimpleSourceName(),
+                methodName, arguments);
+            // Non-value types might need to be wrapped
+            writeReturnWrapper(sw, type, method);
+            // call("doFoo", toReturn, params);
+            sw.println("%s.this.call(\"%s\", toReturn%s %s);", type.getSimpleSourceName(),
+                methodName, arguments.length() > 0 ? "," : "", arguments);
+            sw.println("return toReturn;");
+          }
+          break;
+        default:
+          throw new RuntimeException();
+      }
+      sw.outdent();
+      sw.println("}");
+    }
+
+    // Delegate equals(), hashCode(), and toString() to wrapped object
+    sw.println("@Override public boolean equals(Object o) {");
+    sw.indentln("return this == o || getWrapped().equals(o);");
+    sw.println("}");
+    sw.println("@Override public int hashCode() {");
+    sw.indentln("return getWrapped().hashCode();");
+    sw.println("}");
+    sw.println("@Override public String toString() {");
+    sw.indentln("return getWrapped().toString();");
+    sw.println("}");
+
+    // End of shim field declaration and assignment
+    sw.outdent();
+    sw.println("};");
+  }
+
+  /**
+   * Generate traversal logic.
+   */
+  private void writeTraversal(SourceWriter sw, AutoBeanType type) {
+    List<AutoBeanMethod> referencedSetters = new ArrayList<AutoBeanMethod>();
+    sw.println("@Override protected void traverseProperties(%s visitor, %s ctx) {",
+        AutoBeanVisitor.class.getCanonicalName(), OneShotContext.class.getCanonicalName());
+    sw.indent();
+    sw.println("%s bean;", AbstractAutoBean.class.getCanonicalName());
+    sw.println("Object value;");
+    sw.println("%s propertyContext;", ClientPropertyContext.class.getCanonicalName());
+    // Local variable ref cleans up emitted js
+    sw.println("%1$s as = as();", type.getPeerType().getQualifiedSourceName());
+
+    for (AutoBeanMethod method : type.getMethods()) {
+      if (!method.getAction().equals(JBeanMethod.GET)) {
+        continue;
+      }
+
+      AutoBeanMethod setter = null;
+      // If it's not a simple bean type, try to find a real setter method
+      if (!type.isSimpleBean()) {
+        for (AutoBeanMethod maybeSetter : type.getMethods()) {
+          boolean isASetter =
+              maybeSetter.getAction().equals(JBeanMethod.SET)
+                  || maybeSetter.getAction().equals(JBeanMethod.SET_BUILDER);
+          if (isASetter && maybeSetter.getPropertyName().equals(method.getPropertyName())) {
+            setter = maybeSetter;
+            break;
+          }
+        }
+      }
+
+      // The type of property influences the visitation
+      String valueExpression =
+          String.format("bean = (%1$s) %2$s.getAutoBean(as.%3$s());", AbstractAutoBean.class
+              .getCanonicalName(), AutoBeanUtils.class.getCanonicalName(), method.getMethod()
+              .getName());
+      String visitMethod;
+      String visitVariable = "bean";
+      if (method.isCollection()) {
+        visitMethod = "Collection";
+      } else if (method.isMap()) {
+        visitMethod = "Map";
+      } else if (method.isValueType()) {
+        valueExpression = String.format("value = as.%s();", method.getMethod().getName());
+        visitMethod = "Value";
+        visitVariable = "value";
+      } else {
+        visitMethod = "Reference";
+      }
+      sw.println(valueExpression);
+
+      // Map<List<Foo>, Bar> --> Map, List, Foo, Bar
+      List<JType> typeList = new ArrayList<JType>();
+      createTypeList(typeList, method.getMethod().getReturnType());
+      assert typeList.size() > 0;
+
+      /*
+       * Make the PropertyContext that lets us call the setter. We allow
+       * multiple methods to be bound to the same property (e.g. to allow JSON
+       * payloads to be interpreted as different types). The leading underscore
+       * allows purely numeric property names, which are valid JSON map keys.
+       */
+      // propertyContext = new CPContext(.....);
+      sw.println("propertyContext = new %s(", ClientPropertyContext.class.getCanonicalName());
+      sw.indent();
+      // The instance on which the context is nominally operating
+      sw.println("as,");
+      // Produce a JSNI reference to a setter function to call
+      {
+        if (setter != null) {
+          // Call a method that returns a JSNI reference to the method to call
+          // setFooMethodReference(),
+          sw.println("%sMethodReference(as),", setter.getMethod().getName());
+          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());
+        }
+      }
+      if (typeList.size() == 1) {
+        sw.println("%s.class", ModelUtils.ensureBaseType(typeList.get(0)).getQualifiedSourceName());
+      } else {
+        // Produce the array of parameter types
+        sw.print("new Class<?>[] {");
+        boolean first = true;
+        for (JType lit : typeList) {
+          if (first) {
+            first = false;
+          } else {
+            sw.print(", ");
+          }
+          sw.print("%s.class", ModelUtils.ensureBaseType(lit).getQualifiedSourceName());
+        }
+        sw.println("},");
+
+        // Produce the array of parameter counts
+        sw.print("new int[] {");
+        first = true;
+        for (JType lit : typeList) {
+          if (first) {
+            first = false;
+          } else {
+            sw.print(", ");
+          }
+          JParameterizedType hasParam = lit.isParameterized();
+          if (hasParam == null) {
+            sw.print("0");
+          } else {
+            sw.print(String.valueOf(hasParam.getTypeArgs().length));
+          }
+        }
+        sw.println("}");
+      }
+      sw.outdent();
+      sw.println(");");
+
+      // if (visitor.visitReferenceProperty("foo", value, ctx))
+      sw.println("if (visitor.visit%sProperty(\"%s\", %s, propertyContext)) {", visitMethod, method
+          .getPropertyName(), visitVariable);
+      if (!method.isValueType()) {
+        // Cycle-detection in AbstractAutoBean.traverse
+        sw.indentln("if (bean != null) { bean.traverse(visitor, ctx); }");
+      }
+      sw.println("}");
+      // visitor.endVisitorReferenceProperty("foo", value, ctx);
+      sw.println("visitor.endVisit%sProperty(\"%s\", %s, propertyContext);", visitMethod, method
+          .getPropertyName(), visitVariable);
+    }
+    sw.outdent();
+    sw.println("}");
+
+    for (AutoBeanMethod method : referencedSetters) {
+      JMethod jmethod = method.getMethod();
+      assert jmethod.getParameters().length == 1;
+
+      /*-
+       * Setter setFooMethodReference(Object instance) {
+       *   return instance.@com.example.Blah::setFoo(Lcom/example/Foo;);
+       * }
+       */
+      sw.println("public static native %s %sMethodReference(Object instance) /*-{",
+          ClientPropertyContext.Setter.class.getCanonicalName(), jmethod.getName());
+      sw.indentln("return instance.@%s::%s(%s);", jmethod.getEnclosingType()
+          .getQualifiedSourceName(), jmethod.getName(), jmethod.getParameters()[0].getType()
+          .getJNISignature());
+      sw.println("}-*/;");
+    }
+  }
+}
diff --git a/user/src/com/google/web/bindery/autobean/gwt/rebind/model/AutoBeanFactoryMethod.java b/user/src/com/google/web/bindery/autobean/gwt/rebind/model/AutoBeanFactoryMethod.java
new file mode 100644
index 0000000..4069a57
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/gwt/rebind/model/AutoBeanFactoryMethod.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.gwt.rebind.model;
+
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+
+/**
+ * Represents a single method in an AutoBeanFactory interface.
+ */
+public class AutoBeanFactoryMethod {
+  /**
+   * Builds AutoBeanFactoryMethods.
+   */
+  public static class Builder {
+    private AutoBeanFactoryMethod toReturn = new AutoBeanFactoryMethod();
+
+    public AutoBeanFactoryMethod build() {
+      try {
+        return toReturn;
+      } finally {
+        toReturn = null;
+      }
+    }
+
+    public void setAutoBeanType(AutoBeanType type) {
+      toReturn.autoBeanType = type;
+    }
+
+    public void setMethod(JMethod method) {
+      setName(method.getName());
+      setReturnType(method.getReturnType().isClassOrInterface());
+      if (method.getParameters().length == 1) {
+        setWrappedType(method.getParameters()[0].getType().isClassOrInterface());
+      }
+    }
+
+    public void setName(String name) {
+      toReturn.name = name;
+    }
+
+    public void setReturnType(JClassType returnType) {
+      toReturn.returnType = returnType;
+    }
+
+    public void setWrappedType(JClassType wrapped) {
+      toReturn.wrappedType = wrapped;
+    }
+  }
+
+  private AutoBeanType autoBeanType;
+  private JClassType wrappedType;
+  private String name;
+  private JClassType returnType;
+
+  private AutoBeanFactoryMethod() {
+  }
+
+  public AutoBeanType getAutoBeanType() {
+    return autoBeanType;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public JClassType getReturnType() {
+    return returnType;
+  }
+
+  public JClassType getWrappedType() {
+    return wrappedType;
+  }
+
+  public boolean isWrapper() {
+    return wrappedType != null;
+  }
+
+  /**
+   * For debugging use only.
+   */
+  @Override
+  public String toString() {
+    return name;
+  }
+}
diff --git a/user/src/com/google/web/bindery/autobean/gwt/rebind/model/AutoBeanFactoryModel.java b/user/src/com/google/web/bindery/autobean/gwt/rebind/model/AutoBeanFactoryModel.java
new file mode 100644
index 0000000..6122dfc
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/gwt/rebind/model/AutoBeanFactoryModel.java
@@ -0,0 +1,464 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.gwt.rebind.model;
+
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory.Category;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory.NoWrap;
+import com.google.web.bindery.autobean.shared.impl.EnumMap.ExtraEnums;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JEnumConstant;
+import com.google.gwt.core.ext.typeinfo.JEnumType;
+import com.google.gwt.core.ext.typeinfo.JGenericType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JParameter;
+import com.google.gwt.core.ext.typeinfo.JParameterizedType;
+import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.editor.rebind.model.ModelUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 
+ */
+public class AutoBeanFactoryModel {
+  private static final JType[] EMPTY_JTYPE = new JType[0];
+
+  private final JGenericType autoBeanInterface;
+  private final JClassType autoBeanFactoryInterface;
+  private final Map<JEnumConstant, String> allEnumConstants = new LinkedHashMap<JEnumConstant, String>();
+  private final List<JClassType> categoryTypes;
+  private final List<JClassType> noWrapTypes;
+  private final TreeLogger logger;
+  private final List<AutoBeanFactoryMethod> methods = new ArrayList<AutoBeanFactoryMethod>();
+  private final List<JMethod> objectMethods;
+  private final TypeOracle oracle;
+  private final Map<JClassType, AutoBeanType> peers = new LinkedHashMap<JClassType, AutoBeanType>();
+  private boolean poisoned;
+
+  /**
+   * Accumulates bean types that are reachable through the type graph.
+   */
+  private Set<JClassType> toCalculate = new LinkedHashSet<JClassType>();
+
+  public AutoBeanFactoryModel(TreeLogger logger, JClassType factoryType)
+      throws UnableToCompleteException {
+    this.logger = logger;
+    oracle = factoryType.getOracle();
+    autoBeanInterface = oracle.findType(AutoBean.class.getCanonicalName()).isGenericType();
+    autoBeanFactoryInterface = oracle.findType(
+        AutoBeanFactory.class.getCanonicalName()).isInterface();
+
+    /*
+     * We want to allow the user to override some of the useful Object methods,
+     * so we'll extract them here.
+     */
+    JClassType objectType = oracle.getJavaLangObject();
+    objectMethods = Arrays.asList(
+        objectType.findMethod("equals", new JType[] {objectType}),
+        objectType.findMethod("hashCode", EMPTY_JTYPE),
+        objectType.findMethod("toString", EMPTY_JTYPE));
+
+    // Process annotations
+    {
+      Category categoryAnnotation = factoryType.getAnnotation(Category.class);
+      if (categoryAnnotation != null) {
+        categoryTypes = new ArrayList<JClassType>(
+            categoryAnnotation.value().length);
+        processClassArrayAnnotation(categoryAnnotation.value(), categoryTypes);
+      } else {
+        categoryTypes = null;
+      }
+
+      noWrapTypes = new ArrayList<JClassType>();
+      noWrapTypes.add(oracle.findType(AutoBean.class.getCanonicalName()));
+      NoWrap noWrapAnnotation = factoryType.getAnnotation(NoWrap.class);
+      if (noWrapAnnotation != null) {
+        processClassArrayAnnotation(noWrapAnnotation.value(), noWrapTypes);
+      }
+
+      ExtraEnums extraEnumsAnnotation = factoryType.getAnnotation(ExtraEnums.class);
+      if (extraEnumsAnnotation != null) {
+        for (Class<?> clazz : extraEnumsAnnotation.value()) {
+          JEnumType asEnum = oracle.findType(clazz.getCanonicalName()).isEnum();
+          assert asEnum != null;
+          for (JEnumConstant value : asEnum.getEnumConstants()) {
+            allEnumConstants.put(value, AutoBeanMethod.getEnumName(value));
+          }
+        }
+      }
+    }
+
+    for (JMethod method : factoryType.getOverridableMethods()) {
+      if (method.getEnclosingType().equals(autoBeanFactoryInterface)) {
+        // Ignore methods in AutoBeanFactory
+        continue;
+      }
+
+      JClassType returnType = method.getReturnType().isInterface();
+      if (returnType == null) {
+        poison("The return type of method %s is a primitive type",
+            method.getName());
+        continue;
+      }
+
+      // AutoBean<FooIntf> blah() --> beanType = FooIntf
+      JClassType beanType = ModelUtils.findParameterizationOf(
+          autoBeanInterface, returnType)[0];
+      if (beanType.isInterface() == null) {
+        poison("The %s parameterization is not an interface",
+            beanType.getQualifiedSourceName());
+        continue;
+      }
+
+      // AutoBean<FooIntf> blah(FooIntfSub foo) --> toWrap = FooIntfSub
+      JClassType toWrap;
+      if (method.getParameters().length == 0) {
+        toWrap = null;
+      } else if (method.getParameters().length == 1) {
+        toWrap = method.getParameters()[0].getType().isClassOrInterface();
+        if (!beanType.isAssignableFrom(toWrap)) {
+          poison(
+              "The %s parameterization %s is not assignable from the delegate"
+                  + " type %s", autoBeanInterface.getSimpleSourceName(),
+              toWrap.getQualifiedSourceName());
+          continue;
+        }
+      } else {
+        poison("Unexpecetd parameters in method %s", method.getName());
+        continue;
+      }
+
+      AutoBeanType autoBeanType = getAutoBeanType(beanType);
+
+      // Must wrap things that aren't simple interfaces
+      if (!autoBeanType.isSimpleBean() && toWrap == null) {
+        if (categoryTypes != null) {
+          poison("The %s parameterization is not simple and the following"
+              + " methods did not have static implementations:",
+              beanType.getQualifiedSourceName());
+          for (AutoBeanMethod missing : autoBeanType.getMethods()) {
+            if (missing.getAction().equals(JBeanMethod.CALL)
+                && missing.getStaticImpl() == null) {
+              poison(missing.getMethod().getReadableDeclaration());
+            }
+          }
+        } else {
+          poison("The %s parameterization is not simple, but the %s method"
+              + " does not provide a delegate",
+              beanType.getQualifiedSourceName(), method.getName());
+        }
+        continue;
+      }
+
+      AutoBeanFactoryMethod.Builder builder = new AutoBeanFactoryMethod.Builder();
+      builder.setAutoBeanType(autoBeanType);
+      builder.setMethod(method);
+      methods.add(builder.build());
+    }
+
+    while (!toCalculate.isEmpty()) {
+      Set<JClassType> examine = toCalculate;
+      toCalculate = new LinkedHashSet<JClassType>();
+      for (JClassType beanType : examine) {
+        getAutoBeanType(beanType);
+      }
+    }
+
+    if (poisoned) {
+      die("Unable to complete due to previous errors");
+    }
+  }
+
+  public Collection<AutoBeanType> getAllTypes() {
+    return Collections.unmodifiableCollection(peers.values());
+  }
+
+  public List<JClassType> getCategoryTypes() {
+    return categoryTypes;
+  }
+
+  public Map<JEnumConstant, String> getEnumTokenMap() {
+    return Collections.unmodifiableMap(allEnumConstants);
+  }
+
+  public List<AutoBeanFactoryMethod> getMethods() {
+    return Collections.unmodifiableList(methods);
+  }
+
+  public AutoBeanType getPeer(JClassType beanType) {
+    beanType = ModelUtils.ensureBaseType(beanType);
+    return peers.get(beanType);
+  }
+
+  private List<AutoBeanMethod> computeMethods(JClassType beanType) {
+    List<JMethod> toExamine = new ArrayList<JMethod>();
+    toExamine.addAll(Arrays.asList(beanType.getInheritableMethods()));
+    toExamine.addAll(objectMethods);
+    List<AutoBeanMethod> toReturn = new ArrayList<AutoBeanMethod>(
+        toExamine.size());
+    for (JMethod method : toExamine) {
+      if (method.isPrivate()) {
+        // Ignore private methods
+        continue;
+      }
+      AutoBeanMethod.Builder builder = new AutoBeanMethod.Builder();
+      builder.setMethod(method);
+
+      // See if this method shouldn't have its return type wrapped
+      // TODO: Allow class return types?
+      JClassType classReturn = method.getReturnType().isInterface();
+      if (classReturn != null) {
+        maybeCalculate(classReturn);
+        if (noWrapTypes != null) {
+          for (JClassType noWrap : noWrapTypes) {
+            if (noWrap.isAssignableFrom(classReturn)) {
+              builder.setNoWrap(true);
+              break;
+            }
+          }
+        }
+      }
+
+      // GET, SET, or CALL
+      JBeanMethod action = JBeanMethod.which(method);
+      builder.setAction(action);
+      if (JBeanMethod.CALL.equals(action)) {
+        JMethod staticImpl = findStaticImpl(beanType, method);
+        if (staticImpl == null && objectMethods.contains(method)) {
+          // Don't complain about lack of implementation for Object methods
+          continue;
+        }
+        builder.setStaticImp(staticImpl);
+      }
+
+      AutoBeanMethod toAdd = builder.build();
+
+      // Collect referenced enums
+      if (toAdd.hasEnumMap()) {
+        allEnumConstants.putAll(toAdd.getEnumMap());
+      }
+
+      // See if parameterizations will pull in more types
+      if (toAdd.isCollection()) {
+        maybeCalculate(toAdd.getElementType());
+      } else if (toAdd.isMap()) {
+        maybeCalculate(toAdd.getKeyType());
+        maybeCalculate(toAdd.getValueType());
+      }
+
+      toReturn.add(toAdd);
+    }
+    return toReturn;
+  }
+
+  private void die(String message) throws UnableToCompleteException {
+    poison(message);
+    throw new UnableToCompleteException();
+  }
+
+  /**
+   * Find <code>Object __intercept(AutoBean&lt;?> bean, Object value);</code> in
+   * the category types.
+   */
+  private JMethod findInterceptor(JClassType beanType) {
+    if (categoryTypes == null) {
+      return null;
+    }
+    for (JClassType category : categoryTypes) {
+      for (JMethod method : category.getOverloads("__intercept")) {
+        // Ignore non-static, non-public methods
+        // TODO: Implement visibleFrom() to allow package-protected categories
+        if (!method.isStatic() || !method.isPublic()) {
+          continue;
+        }
+
+        JParameter[] params = method.getParameters();
+        if (params.length != 2) {
+          continue;
+        }
+        if (!methodAcceptsAutoBeanAsFirstParam(beanType, method)) {
+          continue;
+        }
+        JClassType value = params[1].getType().isClassOrInterface();
+        if (value == null) {
+          continue;
+        }
+        if (!oracle.getJavaLangObject().isAssignableTo(value)) {
+          continue;
+        }
+        return method;
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Search the category types for a static implementation of an interface
+   * method. Given the interface method declaration:
+   * 
+   * <pre>
+   * Foo bar(Baz baz);
+   * </pre>
+   * 
+   * this will search the types in {@link #categoryTypes} for the following
+   * method:
+   * 
+   * <pre>
+   * public static Foo bar(AutoBean&lt;Intf> bean, Baz baz) {}
+   * </pre>
+   */
+  private JMethod findStaticImpl(JClassType beanType, JMethod method) {
+    if (categoryTypes == null) {
+      return null;
+    }
+
+    for (JClassType category : categoryTypes) {
+      // One extra argument for the AutoBean
+      JParameter[] methodParams = method.getParameters();
+      int requiredArgs = methodParams.length + 1;
+      overload : for (JMethod overload : category.getOverloads(method.getName())) {
+        if (!overload.isStatic() || !overload.isPublic()) {
+          // Ignore non-static, non-public methods
+          continue;
+        }
+
+        JParameter[] overloadParams = overload.getParameters();
+        if (overloadParams.length != requiredArgs) {
+          continue;
+        }
+
+        if (!methodAcceptsAutoBeanAsFirstParam(beanType, overload)) {
+          // Ignore if the first parameter is a primitive or not assignable
+          continue;
+        }
+
+        // Match the rest of the parameters
+        for (int i = 1; i < requiredArgs; i++) {
+          JType methodType = methodParams[i - 1].getType();
+          JType overloadType = overloadParams[i].getType();
+          if (methodType.equals(overloadType)) {
+            // Match; exact, the usual case
+          } else if (methodType.isClassOrInterface() != null
+              && overloadType.isClassOrInterface() != null
+              && methodType.isClassOrInterface().isAssignableTo(
+                  overloadType.isClassOrInterface())) {
+            // Match; assignment-compatible
+          } else {
+            // No match, keep looking
+            continue overload;
+          }
+        }
+        return overload;
+      }
+    }
+    return null;
+  }
+
+  private AutoBeanType getAutoBeanType(JClassType beanType) {
+    beanType = ModelUtils.ensureBaseType(beanType);
+    AutoBeanType toReturn = peers.get(beanType);
+    if (toReturn == null) {
+      AutoBeanType.Builder builder = new AutoBeanType.Builder();
+      builder.setOwnerFactory(this);
+      builder.setPeerType(beanType);
+      builder.setMethods(computeMethods(beanType));
+      builder.setInterceptor(findInterceptor(beanType));
+      if (noWrapTypes != null) {
+        for (JClassType noWrap : noWrapTypes) {
+          if (noWrap.isAssignableFrom(beanType)) {
+            builder.setNoWrap(true);
+            break;
+          }
+        }
+      }
+      toReturn = builder.build();
+      peers.put(beanType, toReturn);
+    }
+    return toReturn;
+  }
+
+  /**
+   * Enqueue a type in {@link #toCalculate} if {@link #peers} does not already
+   * contain an entry.
+   */
+  private void maybeCalculate(JClassType type) {
+    if (type.isInterface() == null || ModelUtils.isValueType(oracle, type)) {
+      return;
+    }
+    if (!peers.containsKey(type)) {
+      toCalculate.add(type);
+    }
+  }
+
+  private boolean methodAcceptsAutoBeanAsFirstParam(JClassType beanType,
+      JMethod method) {
+    JParameter[] params = method.getParameters();
+    if (params.length == 0) {
+      return false;
+    }
+    JClassType paramAsClass = params[0].getType().isClassOrInterface();
+
+    // First parameter is a primitive
+    if (paramAsClass == null) {
+      return false;
+    }
+
+    // Check using base types to account for erasure semantics
+    JParameterizedType expectedFirst = oracle.getParameterizedType(
+        autoBeanInterface,
+        new JClassType[] {ModelUtils.ensureBaseType(beanType)});
+    return expectedFirst.isAssignableTo(paramAsClass);
+  }
+
+  private void poison(String message, Object... args) {
+    logger.log(TreeLogger.ERROR, String.format(message, args));
+    poisoned = true;
+  }
+
+  private void processClassArrayAnnotation(Class<?>[] classes,
+      Collection<JClassType> accumulator) {
+    for (Class<?> clazz : classes) {
+      JClassType category = oracle.findType(clazz.getCanonicalName());
+      if (category == null) {
+        poison("Could not find @%s type %s in the TypeOracle",
+            Category.class.getSimpleName(), clazz.getCanonicalName());
+        continue;
+      } else if (!category.isPublic()) {
+        poison("Category type %s is not public",
+            category.getQualifiedSourceName());
+        continue;
+      } else if (!category.isStatic() && category.isMemberType()) {
+        poison("Category type %s must be static",
+            category.getQualifiedSourceName());
+        continue;
+      }
+      accumulator.add(category);
+    }
+  }
+}
diff --git a/user/src/com/google/web/bindery/autobean/gwt/rebind/model/AutoBeanMethod.java b/user/src/com/google/web/bindery/autobean/gwt/rebind/model/AutoBeanMethod.java
new file mode 100644
index 0000000..43763df
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/gwt/rebind/model/AutoBeanMethod.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.gwt.rebind.model;
+
+import com.google.web.bindery.autobean.shared.AutoBean.PropertyName;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JEnumConstant;
+import com.google.gwt.core.ext.typeinfo.JEnumType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.editor.rebind.model.ModelUtils;
+
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Describes a method implemented by an AutoBean.
+ */
+public class AutoBeanMethod {
+  /**
+   * Creates AutoBeanMethods.
+   */
+  public static class Builder {
+    private AutoBeanMethod toReturn = new AutoBeanMethod();
+
+    public AutoBeanMethod build() {
+      if (toReturn.action.equals(JBeanMethod.GET)
+          || toReturn.action.equals(JBeanMethod.SET)
+          || toReturn.action.equals(JBeanMethod.SET_BUILDER)) {
+        PropertyName annotation = toReturn.method.getAnnotation(PropertyName.class);
+        if (annotation != null) {
+          toReturn.propertyName = annotation.value();
+        } else {
+          toReturn.propertyName = toReturn.action.inferName(toReturn.method);
+        }
+      }
+
+      try {
+        return toReturn;
+      } finally {
+        toReturn = null;
+      }
+    }
+
+    public void setAction(JBeanMethod action) {
+      toReturn.action = action;
+    }
+
+    public void setMethod(JMethod method) {
+      toReturn.method = method;
+      TypeOracle oracle = method.getEnclosingType().getOracle();
+
+      JType returnType = method.getReturnType();
+      toReturn.isValueType = ModelUtils.isValueType(oracle, returnType);
+
+      if (!toReturn.isValueType) {
+        // See if it's a collection or a map
+        JClassType returnClass = returnType.isClassOrInterface();
+        JClassType collectionInterface = oracle.findType(Collection.class.getCanonicalName());
+        JClassType mapInterface = oracle.findType(Map.class.getCanonicalName());
+        if (collectionInterface.isAssignableFrom(returnClass)) {
+          JClassType[] parameterizations = ModelUtils.findParameterizationOf(
+              collectionInterface, returnClass);
+          toReturn.elementType = parameterizations[0];
+          maybeProcessEnumType(toReturn.elementType);
+        } else if (mapInterface.isAssignableFrom(returnClass)) {
+          JClassType[] parameterizations = ModelUtils.findParameterizationOf(
+              mapInterface, returnClass);
+          toReturn.keyType = parameterizations[0];
+          toReturn.valueType = parameterizations[1];
+          maybeProcessEnumType(toReturn.keyType);
+          maybeProcessEnumType(toReturn.valueType);
+        }
+      } else {
+        maybeProcessEnumType(returnType);
+      }
+    }
+
+    public void setNoWrap(boolean noWrap) {
+      toReturn.isNoWrap = noWrap;
+    }
+
+    public void setStaticImp(JMethod staticImpl) {
+      toReturn.staticImpl = staticImpl;
+    }
+
+    /**
+     * Call {@link #processEnumType(JEnumType)} if {@code type} is a
+     * {@link JEnumType}.
+     */
+    private void maybeProcessEnumType(JType type) {
+      assert type != null : "type == null";
+      JEnumType enumType = type.isEnum();
+      if (enumType != null) {
+        processEnumType(enumType);
+      }
+    }
+
+    /**
+     * Adds a JEnumType to the AutoBeanMethod's enumMap so that the
+     * AutoBeanFactoryGenerator can embed extra metadata about the enum values.
+     */
+    private void processEnumType(JEnumType enumType) {
+      Map<JEnumConstant, String> map = toReturn.enumMap;
+      if (map == null) {
+        map = toReturn.enumMap = new LinkedHashMap<JEnumConstant, String>();
+      }
+      for (JEnumConstant e : enumType.getEnumConstants()) {
+        String name = getEnumName(e);
+        map.put(e, name);
+      }
+    }
+  }
+
+  static String getEnumName(JEnumConstant e) {
+    String name;
+    PropertyName annotation = e.getAnnotation(PropertyName.class);
+    if (annotation == null) {
+      name = e.getName();
+    } else {
+      name = annotation.value();
+    }
+    return name;
+  }
+
+  private JBeanMethod action;
+  private JClassType elementType;
+  private Map<JEnumConstant, String> enumMap;
+  private JClassType keyType;
+  private JMethod method;
+  private boolean isNoWrap;
+  private boolean isValueType;
+  private String propertyName;
+  private JMethod staticImpl;
+  private JClassType valueType;
+
+  private AutoBeanMethod() {
+  }
+
+  public JBeanMethod getAction() {
+    return action;
+  }
+
+  public JClassType getElementType() {
+    return elementType;
+  }
+
+  public Map<JEnumConstant, String> getEnumMap() {
+    return enumMap;
+  }
+
+  public JClassType getKeyType() {
+    return keyType;
+  }
+
+  public JMethod getMethod() {
+    return method;
+  }
+
+  public String getPropertyName() {
+    return propertyName;
+  }
+
+  /**
+   * If the AutoBean method was declared in a type containing a
+   * {@link com.google.gwt.editor.client.AutoBean.Category Category} annotation,
+   * this method will return the static implementation.
+   */
+  public JMethod getStaticImpl() {
+    return staticImpl;
+  }
+
+  public JClassType getValueType() {
+    return valueType;
+  }
+
+  public boolean hasEnumMap() {
+    return enumMap != null;
+  }
+
+  public boolean isCollection() {
+    return elementType != null;
+  }
+
+  public boolean isMap() {
+    return keyType != null;
+  }
+
+  public boolean isNoWrap() {
+    return isNoWrap;
+  }
+
+  public boolean isValueType() {
+    return isValueType;
+  }
+
+  /**
+   * For debugging use only.
+   */
+  @Override
+  public String toString() {
+    return method.toString();
+  }
+}
diff --git a/user/src/com/google/web/bindery/autobean/gwt/rebind/model/AutoBeanType.java b/user/src/com/google/web/bindery/autobean/gwt/rebind/model/AutoBeanType.java
new file mode 100644
index 0000000..da40afe
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/gwt/rebind/model/AutoBeanType.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.gwt.rebind.model;
+
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * Describes an AutoBean.
+ */
+public class AutoBeanType {
+
+  /**
+   * Builder.
+   */
+  public static class Builder {
+    private boolean affectedByCategories;
+    private String beanSimpleSourceName;
+    private String categorySuffix;
+    private AutoBeanType toReturn = new AutoBeanType();
+
+    public AutoBeanType build() {
+      // Different implementations necessary for category-affected impls
+      toReturn.simpleSourceName = beanSimpleSourceName
+          + (affectedByCategories ? categorySuffix : "");
+      try {
+        return toReturn;
+      } finally {
+        toReturn = null;
+      }
+    }
+
+    public void setInterceptor(JMethod interceptor) {
+      affectedByCategories = interceptor != null;
+      toReturn.interceptor = interceptor;
+    }
+
+    public void setMethods(List<AutoBeanMethod> methods) {
+      toReturn.methods = new ArrayList<AutoBeanMethod>(methods);
+      Collections.sort(toReturn.methods, new Comparator<AutoBeanMethod>() {
+        public int compare(AutoBeanMethod o1, AutoBeanMethod o2) {
+          int c = o1.getAction().compareTo(o2.getAction());
+          if (c != 0) {
+            return c;
+          }
+          // Name alone would cause overload conflicts
+          return o1.getMethod().getReadableDeclaration().compareTo(
+              o2.getMethod().getReadableDeclaration());
+        }
+      });
+      toReturn.methods = Collections.unmodifiableList(toReturn.methods);
+
+      toReturn.simpleBean = true;
+      for (AutoBeanMethod method : methods) {
+        if (method.getAction().equals(JBeanMethod.CALL)) {
+          if (method.getStaticImpl() == null) {
+            toReturn.simpleBean = false;
+          } else {
+            affectedByCategories = true;
+          }
+        }
+      }
+    }
+
+    public void setNoWrap(boolean noWrap) {
+      toReturn.noWrap = noWrap;
+    }
+
+    public void setOwnerFactory(AutoBeanFactoryModel autoBeanFactoryModel) {
+      if (autoBeanFactoryModel.getCategoryTypes() == null) {
+        return;
+      }
+      StringBuilder sb = new StringBuilder();
+      for (JClassType category : autoBeanFactoryModel.getCategoryTypes()) {
+        sb.append("_").append(
+            category.getQualifiedSourceName().replace('.', '_'));
+      }
+      categorySuffix = sb.toString();
+    }
+
+    public void setPeerType(JClassType type) {
+      assert type.isParameterized() == null && type.isRawType() == null;
+      toReturn.peerType = type;
+      String packageName = type.getPackage().getName();
+      if (packageName.startsWith("java")) {
+        packageName = "emul." + packageName;
+      }
+      toReturn.packageName = packageName;
+      beanSimpleSourceName = type.getName().replace('.', '_') + "AutoBean";
+    }
+  }
+
+  private JMethod interceptor;
+  private List<AutoBeanMethod> methods;
+  private boolean noWrap;
+  private String packageName;
+  private JClassType peerType;
+  private boolean simpleBean;
+  private String simpleSourceName;
+
+  private AutoBeanType() {
+  }
+
+  /**
+   * A method that is allowed to intercept and modify return values from
+   * getters.
+   */
+  public JMethod getInterceptor() {
+    return interceptor;
+  }
+
+  public List<AutoBeanMethod> getMethods() {
+    return methods;
+  }
+
+  public String getPackageNome() {
+    return packageName;
+  }
+
+  public JClassType getPeerType() {
+    return peerType;
+  }
+
+  public String getQualifiedSourceName() {
+    return getPackageNome() + "." + getSimpleSourceName();
+  }
+
+  public String getSimpleSourceName() {
+    return simpleSourceName;
+  }
+
+  public boolean isNoWrap() {
+    return noWrap;
+  }
+
+  /**
+   * A simple bean has only getters and setters.
+   */
+  public boolean isSimpleBean() {
+    return simpleBean;
+  }
+
+  /**
+   * For debugging use only.
+   */
+  @Override
+  public String toString() {
+    return peerType.toString();
+  }
+}
diff --git a/user/src/com/google/web/bindery/autobean/gwt/rebind/model/JBeanMethod.java b/user/src/com/google/web/bindery/autobean/gwt/rebind/model/JBeanMethod.java
new file mode 100644
index 0000000..d600c19
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/gwt/rebind/model/JBeanMethod.java
@@ -0,0 +1,154 @@
+/*
+ * 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.web.bindery.autobean.gwt.rebind.model;
+
+import static com.google.web.bindery.autobean.vm.impl.BeanMethod.GET_PREFIX;
+import static com.google.web.bindery.autobean.vm.impl.BeanMethod.HAS_PREFIX;
+import static com.google.web.bindery.autobean.vm.impl.BeanMethod.IS_PREFIX;
+import static com.google.web.bindery.autobean.vm.impl.BeanMethod.SET_PREFIX;
+
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
+import com.google.gwt.core.ext.typeinfo.JType;
+
+import java.beans.Introspector;
+
+/**
+ * Common utility code for matching {@link JMethod} and against bean-style
+ * accessor semantics.
+ * 
+ * @see com.google.web.bindery.autobean.vm.impl.BeanMethod
+ */
+public enum JBeanMethod {
+  GET {
+    @Override
+    public String inferName(JMethod method) {
+      if (isBooleanProperty(method) && method.getName().startsWith(IS_PREFIX)) {
+        return Introspector.decapitalize(method.getName().substring(2));
+      }
+      return super.inferName(method);
+    }
+
+    @Override
+    public boolean matches(JMethod method) {
+      if (method.getParameters().length > 0) {
+        return false;
+      }
+
+      if (isBooleanProperty(method)) {
+        return true;
+      }
+
+      String name = method.getName();
+      if (name.startsWith(GET_PREFIX) && name.length() > 3) {
+        return true;
+      }
+      return false;
+    }
+
+    /**
+     * Returns {@code true} if the method matches {@code boolean isFoo()} or
+     * {@code boolean hasFoo()} property accessors.
+     */
+    private boolean isBooleanProperty(JMethod method) {
+      JType returnType = method.getReturnType();
+      if (JPrimitiveType.BOOLEAN.equals(returnType)
+          || method.getEnclosingType().getOracle().findType(
+              Boolean.class.getCanonicalName()).equals(returnType)) {
+        String name = method.getName();
+        if (name.startsWith(IS_PREFIX) && name.length() > 2) {
+          return true;
+        }
+        if (name.startsWith(HAS_PREFIX) && name.length() > 3) {
+          return true;
+        }
+      }
+      return false;
+    }
+  },
+  SET {
+    @Override
+    public boolean matches(JMethod method) {
+      if (!JPrimitiveType.VOID.equals(method.getReturnType())) {
+        return false;
+      }
+      if (method.getParameters().length != 1) {
+        return false;
+      }
+      String name = method.getName();
+      if (name.startsWith(SET_PREFIX) && name.length() > 3) {
+        return true;
+      }
+      return false;
+    }
+  },
+  SET_BUILDER {
+    @Override
+    public boolean matches(JMethod method) {
+      JClassType returnClass = method.getReturnType().isClassOrInterface();
+      if (returnClass == null
+          || !returnClass.isAssignableFrom(method.getEnclosingType())) {
+        return false;
+      }
+      if (method.getParameters().length != 1) {
+        return false;
+      }
+      String name = method.getName();
+      if (name.startsWith(SET_PREFIX) && name.length() > 3) {
+        return true;
+      }
+      return false;
+    }
+  },
+  CALL {
+    /**
+     * Matches all leftover methods.
+     */
+    @Override
+    public boolean matches(JMethod method) {
+      return true;
+    }
+  };
+
+  /**
+   * Determine which Action a method maps to.
+   */
+  public static JBeanMethod which(JMethod method) {
+    for (JBeanMethod action : JBeanMethod.values()) {
+      if (action.matches(method)) {
+        return action;
+      }
+    }
+    throw new RuntimeException("CALL should have matched");
+  }
+
+  /**
+   * Infer the name of a property from the method.
+   */
+  public String inferName(JMethod method) {
+    if (this == CALL) {
+      throw new UnsupportedOperationException(
+          "Cannot infer a property name for a CALL-type method");
+    }
+    return Introspector.decapitalize(method.getName().substring(3));
+  }
+
+  /**
+   * Returns {@code true} if the BeanLikeMethod matches the method.
+   */
+  public abstract boolean matches(JMethod method);
+}
diff --git a/user/src/com/google/web/bindery/autobean/shared/AutoBean.java b/user/src/com/google/web/bindery/autobean/shared/AutoBean.java
new file mode 100644
index 0000000..9aa94fc
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/shared/AutoBean.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.shared;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * A controller for an implementation of a bean interface. Instances of
+ * AutoBeans are obtained from an {@link AutoBeanFactory}.
+ * 
+ * @param <T> the type of interface that will be wrapped.
+ */
+public interface AutoBean<T> {
+  /**
+   * An annotation that allows inferred property names to be overridden.
+   * <p>
+   * This annotation is asymmetric, applying it to a getter will not affect the
+   * setter. The asymmetry allows existing users of an interface to read old
+   * {@link AutoBeanCodex} messages, but write new ones.
+   */
+  @Documented
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(value = {ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
+  public @interface PropertyName {
+    String value();
+  }
+
+  /**
+   * Accept an AutoBeanVisitor.
+   * 
+   * @param visitor an {@link AutoBeanVisitor}
+   */
+  void accept(AutoBeanVisitor visitor);
+
+  /**
+   * Returns a proxy implementation of the <code>T</code> interface which will
+   * delegate to the underlying wrapped object, if any.
+   * 
+   * @return a proxy that delegates to the wrapped object
+   */
+  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.
+   * <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}.
+   * 
+   * @throws UnsupportedOperationException
+   * @deprecated with no replacement
+   */
+  @Deprecated
+  AutoBean<T> clone(boolean deep);
+
+  /**
+   * Returns the AutoBeanFactory that created the AutoBean.
+   * 
+   * @return an AutoBeanFactory
+   */
+  AutoBeanFactory getFactory();
+
+  /**
+   * Retrieve a tag value that was previously provided to
+   * {@link #setTag(String, Object)}.
+   * 
+   * @param tagName the tag name
+   * @return the tag value
+   * @see #setTag(String, Object)
+   */
+  <Q> Q getTag(String tagName);
+
+  /**
+   * Returns the wrapped interface type.
+   */
+  Class<T> getType();
+
+  /**
+   * Returns the value most recently passed to {@link #setFrozen}, or
+   * {@code false} if it has never been called.
+   * 
+   * @return {@code true} if this instance is frozen
+   */
+  boolean isFrozen();
+
+  /**
+   * Returns {@code true} if the AutoBean was provided with an external object.
+   * 
+   * @return {@code true} if this instance is a wrapper
+   */
+  boolean isWrapper();
+
+  /**
+   * Disallows any method calls other than getters. All setter and call
+   * operations will throw an {@link IllegalStateException}.
+   * 
+   * @param frozen if {@code true}, freeze this instance
+   */
+  void setFrozen(boolean frozen);
+
+  /**
+   * A tag is an arbitrary piece of external metadata to be associated with the
+   * wrapped value.
+   * 
+   * @param tagName the tag name
+   * @param value the wrapped value
+   * @see #getTag(String)
+   */
+  void setTag(String tagName, Object value);
+
+  /**
+   * If the AutoBean wraps an object, return the underlying object. The AutoBean
+   * will no longer function once unwrapped.
+   * 
+   * @return the previously-wrapped object
+   * @throws IllegalStateException if the AutoBean is not a wrapper
+   */
+  T unwrap();
+}
diff --git a/user/src/com/google/web/bindery/autobean/shared/AutoBeanCodex.java b/user/src/com/google/web/bindery/autobean/shared/AutoBeanCodex.java
new file mode 100644
index 0000000..2f07b1a
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/shared/AutoBeanCodex.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.shared;
+
+import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl;
+import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.EncodeState;
+import com.google.web.bindery.autobean.shared.impl.StringQuoter;
+
+/**
+ * Utility methods for encoding an AutoBean graph into a JSON-compatible string.
+ * This codex intentionally does not preserve object identity, nor does it
+ * encode cycles, but it will detect them.
+ */
+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
+   */
+  public static <T> AutoBean<T> decode(AutoBeanFactory factory, Class<T> clazz, Splittable data) {
+    return AutoBeanCodexImpl.doDecode(EncodeState.forDecode(factory), clazz, data);
+  }
+
+  /**
+   * 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 payload a payload string previously generated by
+   *          {@link #encode(AutoBean)}{@link Splittable#getPayload()
+   *          .getPayload()}.
+   * @return an AutoBean containing the payload contents
+   */
+  public static <T> AutoBean<T> decode(AutoBeanFactory factory, Class<T> clazz, String payload) {
+    Splittable data = StringQuoter.split(payload);
+    return decode(factory, clazz, data);
+  }
+
+  /**
+   * Copy data from a {@link Splittable} into an AutoBean. Unset values in the
+   * Splittable will not nullify data that already exists in the AutoBean.
+   * 
+   * @param data the source data to copy
+   * @param bean the target AutoBean
+   */
+  public static void decodeInto(Splittable data, AutoBean<?> bean) {
+    AutoBeanCodexImpl.doDecodeInto(EncodeState.forDecode(bean.getFactory()), data, bean);
+  }
+
+  /**
+   * Encodes an AutoBean. The actual payload contents can be retrieved through
+   * {@link Splittable#getPayload()}.
+   * 
+   * @param bean the bean to encode
+   * @return a Splittable that encodes the state of the AutoBean
+   */
+  public static Splittable encode(AutoBean<?> bean) {
+    if (bean == null) {
+      return Splittable.NULL;
+    }
+
+    StringBuilder sb = new StringBuilder();
+    EncodeState state = EncodeState.forEncode(bean.getFactory(), sb);
+    AutoBeanCodexImpl.doEncode(state, bean);
+    return StringQuoter.split(sb.toString());
+  }
+}
diff --git a/user/src/com/google/web/bindery/autobean/shared/AutoBeanFactory.java b/user/src/com/google/web/bindery/autobean/shared/AutoBeanFactory.java
new file mode 100644
index 0000000..44496a5
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/shared/AutoBeanFactory.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.shared;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * A tag interface for the AutoBean generator. Instances of AutoBeans are
+ * created by declaring factory methods on a subtype of this interface.
+ * <p>
+ * Simple interfaces, consisting of only getters and setters, can be constructed
+ * with a no-arg method. Non-simple interfaces must provide a delegate object to
+ * implement a non-simple interface or use a {@link Category}.
+ * 
+ * <pre>
+ * interface MyFactory extends AutoBeanFactory {
+ *   // A factory method for a simple bean
+ *   AutoBean&lt;BeanInterface> beanInterface();
+ *   // A factory method for a wrapper bean
+ *   AutoBean&lt;ArbitraryInterface> wrapper(ArbitraryInterface delegate);
+ * }
+ * </pre>
+ * 
+ * @see <a
+ *      href="http://code.google.com/p/google-web-toolkit/wiki/AutoBean">AutoBean
+ *      wiki page</a>
+ */
+public interface AutoBeanFactory {
+  /**
+   * Allows non-property methods on simple bean implementations when applied.
+   * For any given method, the specified classes will be searched for a public,
+   * static method whose method signature is exactly equal to the declared
+   * method's signature, save for the addition of a new initial paramater that
+   * must accept <code>AutoBean&lt;T></code>.
+   * 
+   * <pre>
+   * interface HasMethod {
+   *   void doSomething(int a, double b);
+   * }
+   * </pre>
+   * 
+   * would be paired with a category implemenation such as
+   * 
+   * <pre>
+   * class HasMethodCategory {
+   *   public static void doSomething(AutoBean&lt;HasMethod> bean, int a, double b) {
+   *   }
+   * }
+   * </pre>
+   * 
+   * and registered with
+   * 
+   * <pre>
+   * {@literal @}Category(HasMethodCategory.class)
+   * interface MyBeanFactory extends AutoBeanFactory {
+   *   AutoBean&lt;HasMethod> hasMethod();
+   * }
+   * </pre>
+   */
+  @Documented
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.TYPE)
+  public @interface Category {
+    Class<?>[] value();
+  }
+
+  /**
+   * The types specified by this annotation will not be wrapped by an AutoBean
+   * when returned from an AutoBean-controlled method.
+   */
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.TYPE)
+  public @interface NoWrap {
+    /**
+     * The interface types that should not be wrapped.
+     */
+    Class<?>[] value();
+  }
+
+  /**
+   * Allows dynamic creation of AutoBean instances based on declared
+   * parameterizations.
+   * 
+   * @param <T> the parameterization of the created {@link AutoBean}
+   * @param clazz the Class of type T of the new instance
+   * @return an {@link AutoBean} of type T or {@code null} if the interface type
+   *         is unknown to the factory
+   */
+  <T> AutoBean<T> create(Class<T> clazz);
+
+  /**
+   * Allows dynamic creation of wrapped AutoBean instances based on declared
+   * parameterizations.
+   * 
+   * @param <T> the parameterization of the created {@link AutoBean}
+   * @param <U> the delegate's type, a subtype of T
+   * @param clazz the Class of type T of the new instance
+   * @param delegate a delegate that extends type T
+   * @return an {@link AutoBean} of type T or {@code null} if the interface type
+   *         is unknown to the factory
+   */
+  <T, U extends T> AutoBean<T> create(Class<T> clazz, U delegate);
+}
diff --git a/user/src/com/google/web/bindery/autobean/shared/AutoBeanUtils.java b/user/src/com/google/web/bindery/autobean/shared/AutoBeanUtils.java
new file mode 100644
index 0000000..e97c438
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/shared/AutoBeanUtils.java
@@ -0,0 +1,460 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.shared;
+
+import com.google.gwt.core.client.impl.WeakMapping;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Utility methods for working with AutoBeans.
+ */
+public final class AutoBeanUtils {
+  /*
+   * TODO(bobv): Make Comparison a real type that holds a map contain the diff
+   * between the two objects. Then export a Map of PendingComparison to
+   * Comparisons as a public API to make it easy for developers to perform deep
+   * diffs across a graph structure.
+   * 
+   * Three-way merge...
+   */
+
+  private enum Comparison {
+    TRUE, FALSE, PENDING;
+  }
+
+  /**
+   * A Pair where order does not matter and the objects are compared by
+   * identity.
+   */
+  private static class PendingComparison {
+    private final AutoBean<?> a;
+    private final AutoBean<?> b;
+    private final int hashCode;
+
+    public PendingComparison(AutoBean<?> a, AutoBean<?> b) {
+      this.a = a;
+      this.b = b;
+      // Don't make relatively prime since order does not matter
+      hashCode = System.identityHashCode(a) + System.identityHashCode(b);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (!(o instanceof PendingComparison)) {
+        return false;
+      }
+      PendingComparison other = (PendingComparison) o;
+      return a == other.a && b == other.b || // Direct match
+          a == other.b && b == other.a; // Swapped
+    }
+
+    @Override
+    public int hashCode() {
+      return hashCode;
+    }
+  }
+
+  /**
+   * Compare two graphs of AutoBeans based on values.
+   * <p>
+   * <ul>
+   * <li>AutoBeans are compared based on type and property values</li>
+   * <li>Lists are compared with element-order equality</li>
+   * <li>Sets and all other Collection types are compare with bag equality</li>
+   * <li>Maps are compared as a lists of keys-value pairs</li>
+   * <li>{@link Splittable Splittables} are compared by value</li>
+   * </ul>
+   * <p>
+   * This will work for both simple and wrapper AutoBeans.
+   * <p>
+   * This method may crawl the entire object graph reachable from the input
+   * parameters and may be arbitrarily expensive to compute.
+   * 
+   * @param a an {@link AutoBean}
+   * @param b an {@link AutoBean}
+   * @return {@code false} if any values in the graph reachable through
+   *         <code>a</code> are different from those reachable from
+   *         <code>b</code>
+   */
+  public static boolean deepEquals(AutoBean<?> a, AutoBean<?> b) {
+    return sameOrEquals(a, b, new HashMap<PendingComparison, Comparison>());
+  }
+
+  /**
+   * Returns a map of properties that differ (via {@link Object#equals(Object)})
+   * between two AutoBeans. The keys are property names and the values are the
+   * value of the property in <code>b</code>. Properties present in
+   * <code>a</code> but missing in <code>b</code> will be represented by
+   * <code>null</code> values. This implementation will compare AutoBeans of
+   * different parameterizations, although the diff produced is likely
+   * meaningless.
+   * <p>
+   * This will work for both simple and wrapper AutoBeans.
+   * 
+   * @param a an {@link AutoBean}
+   * @param b an {@link AutoBean}
+   * @return a {@link Map} of differing properties
+   */
+  public static Map<String, Object> diff(AutoBean<?> a, AutoBean<?> b) {
+    // Fast check for comparing an object to itself
+    if (a.equals(b)) {
+      return Collections.emptyMap();
+    }
+    final Map<String, Object> toReturn = getAllProperties(b);
+
+    // Remove the entries that are equal, adding nulls for missing properties
+    a.accept(new AutoBeanVisitor() {
+      @Override
+      public boolean visitReferenceProperty(String propertyName, AutoBean<?> previousValue,
+          PropertyContext ctx) {
+        if (toReturn.containsKey(propertyName)) {
+          if (equal(propertyName, previousValue)) {
+            // No change
+            toReturn.remove(propertyName);
+          }
+        } else {
+          // The predecessor has a value that this object doesn't.
+          toReturn.put(propertyName, null);
+        }
+        return false;
+      }
+
+      @Override
+      public boolean visitValueProperty(String propertyName, Object previousValue,
+          PropertyContext ctx) {
+        if (toReturn.containsKey(propertyName)) {
+          if (equal(propertyName, previousValue)) {
+            // No change
+            toReturn.remove(propertyName);
+          }
+        } else {
+          // The predecessor has a value that this object doesn't.
+          toReturn.put(propertyName, null);
+        }
+        return false;
+      }
+
+      private boolean equal(String propertyName, AutoBean<?> previousValue) {
+        return previousValue == null && toReturn.get(propertyName) == null || previousValue != null
+            && equal(propertyName, previousValue.as());
+      }
+
+      private boolean equal(String propertyName, Object previousValue) {
+        Object currentValue = toReturn.get(propertyName);
+        return previousValue == null && currentValue == null || previousValue != null
+            && previousValue.equals(currentValue);
+      }
+    });
+    return toReturn;
+  }
+
+  /**
+   * Returns a map that is a copy of the properties contained in an AutoBean.
+   * The returned map is mutable, but editing it will not have any effect on the
+   * bean that produced it.
+   * 
+   * @param bean an {@link AutoBean}
+   * @return a {@link Map} of the bean's properties
+   */
+  public static Map<String, Object> getAllProperties(AutoBean<?> bean) {
+    final Map<String, Object> toReturn = new LinkedHashMap<String, Object>();
+
+    // Look at the previous value of all properties
+    bean.accept(new AutoBeanVisitor() {
+      @Override
+      public boolean visitReferenceProperty(String propertyName, AutoBean<?> value,
+          PropertyContext ctx) {
+        toReturn.put(propertyName, value == null ? null : value.as());
+        return false;
+      }
+
+      @Override
+      public boolean visitValueProperty(String propertyName, Object value, PropertyContext ctx) {
+        toReturn.put(propertyName, value);
+        return false;
+      }
+    });
+    return toReturn;
+  }
+
+  /**
+   * Return the single AutoBean wrapper that is observing the delegate object or
+   * {@code null} if the parameter is {@code null}or not wrapped by an AutoBean.
+   * 
+   * @param delegate a delegate object, or {@code null}
+   * @return the {@link AutoBean} wrapper for the delegate, or {@code null}
+   */
+  @SuppressWarnings("unchecked")
+  public static <T, U extends T> AutoBean<T> getAutoBean(U delegate) {
+    return delegate == null ? null : (AutoBean<T>) WeakMapping.get(delegate, AutoBean.class
+        .getName());
+  }
+
+  /**
+   * Compare two AutoBeans, this method has the type fan-out.
+   */
+  static boolean sameOrEquals(Object value, Object otherValue,
+      Map<PendingComparison, Comparison> pending) {
+    if (value == otherValue) {
+      // Fast exit
+      return true;
+    }
+
+    if (value instanceof Collection<?> && otherValue instanceof Collection<?>) {
+      // Check collections
+      return sameOrEquals((Collection<?>) value, (Collection<?>) otherValue, pending, null);
+    }
+
+    if (value instanceof Map<?, ?> && otherValue instanceof Map<?, ?>) {
+      // Check maps
+      return sameOrEquals((Map<?, ?>) value, (Map<?, ?>) otherValue, pending);
+    }
+
+    if (value instanceof Splittable && otherValue instanceof Splittable) {
+      return sameOrEquals((Splittable) value, (Splittable) otherValue, pending);
+    }
+
+    // Possibly substitute the AutoBean for its shim
+    {
+      AutoBean<?> maybeValue = AutoBeanUtils.getAutoBean(value);
+      AutoBean<?> maybeOther = AutoBeanUtils.getAutoBean(otherValue);
+      if (maybeValue != null && maybeOther != null) {
+        value = maybeValue;
+        otherValue = maybeOther;
+      }
+    }
+
+    if (value instanceof AutoBean<?> && otherValue instanceof AutoBean<?>) {
+      // Check ValueProxies
+      return sameOrEquals((AutoBean<?>) value, (AutoBean<?>) otherValue, pending);
+    }
+
+    if (value == null ^ otherValue == null) {
+      // One is null, the other isn't
+      return false;
+    }
+
+    if (value != null && !value.equals(otherValue)) {
+      // Regular object equality
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * If a comparison between two AutoBeans is currently pending, this method
+   * will skip their comparison.
+   */
+  private static boolean sameOrEquals(AutoBean<?> value, AutoBean<?> otherValue,
+      Map<PendingComparison, Comparison> pending) {
+    if (value == otherValue) {
+      // Simple case
+      return true;
+    } else if (!value.getType().equals(otherValue.getType())) {
+      // Beans of different types
+      return false;
+    }
+
+    /*
+     * The PendingComparison key allows us to break reference cycles when
+     * crawling the graph. Since the entire operation is essentially a
+     * concatenated && operation, it's ok to speculatively return true for
+     * repeated a.equals(b) tests.
+     */
+    PendingComparison key = new PendingComparison(value, otherValue);
+    Comparison previous = pending.get(key);
+    if (previous == null) {
+      // Prevent the same comparison from being made
+      pending.put(key, Comparison.PENDING);
+
+      // Compare each property
+      Map<String, Object> beanProperties = AutoBeanUtils.getAllProperties(value);
+      Map<String, Object> otherProperties = AutoBeanUtils.getAllProperties(otherValue);
+      for (Map.Entry<String, Object> entry : beanProperties.entrySet()) {
+        Object property = entry.getValue();
+        Object otherProperty = otherProperties.get(entry.getKey());
+        if (!sameOrEquals(property, otherProperty, pending)) {
+          pending.put(key, Comparison.FALSE);
+          return false;
+        }
+      }
+      pending.put(key, Comparison.TRUE);
+      return true;
+    } else {
+      // Return true for TRUE or PENDING
+      return !Comparison.FALSE.equals(previous);
+    }
+  }
+
+  /**
+   * Compare two collections by size, then by contents. List comparisons will
+   * preserve order. All other collections will be treated with bag semantics.
+   */
+  private static boolean sameOrEquals(Collection<?> collection, Collection<?> otherCollection,
+      Map<PendingComparison, Comparison> pending, Map<Object, Object> pairs) {
+    if (collection.size() != otherCollection.size()) {
+      return false;
+    }
+
+    if (collection instanceof List<?>) {
+      // Lists we can simply iterate over
+      Iterator<?> it = collection.iterator();
+      Iterator<?> otherIt = otherCollection.iterator();
+      while (it.hasNext()) {
+        assert otherIt.hasNext();
+        Object element = it.next();
+        Object otherElement = otherIt.next();
+        if (!sameOrEquals(element, otherElement, pending)) {
+          return false;
+        }
+        if (pairs != null) {
+          pairs.put(element, otherElement);
+        }
+      }
+    } else {
+      // Do an n*m comparison on any other collection type
+      List<Object> values = new ArrayList<Object>(collection);
+      List<Object> otherValues = new ArrayList<Object>(otherCollection);
+      it : for (Iterator<Object> it = values.iterator(); it.hasNext();) {
+        Object value = it.next();
+        for (Iterator<Object> otherIt = otherValues.iterator(); otherIt.hasNext();) {
+          Object otherValue = otherIt.next();
+          if (sameOrEquals(value, otherValue, pending)) {
+            if (pairs != null) {
+              pairs.put(value, otherValue);
+            }
+            // If a match is found, remove both values from their lists
+            it.remove();
+            otherIt.remove();
+            continue it;
+          }
+        }
+        // A match for the value wasn't found
+        return false;
+      }
+      assert values.isEmpty() && otherValues.isEmpty();
+    }
+    return true;
+  }
+
+  /**
+   * Compare two Maps by size, and key-value pairs.
+   */
+  private static boolean sameOrEquals(Map<?, ?> map, Map<?, ?> otherMap,
+      Map<PendingComparison, Comparison> pending) {
+    if (map.size() != otherMap.size()) {
+      return false;
+    }
+    Map<Object, Object> pairs = new IdentityHashMap<Object, Object>();
+    if (!sameOrEquals(map.keySet(), otherMap.keySet(), pending, pairs)) {
+      return false;
+    }
+    for (Map.Entry<?, ?> entry : map.entrySet()) {
+      Object otherValue = otherMap.get(pairs.get(entry.getKey()));
+      if (!sameOrEquals(entry.getValue(), otherValue, pending)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Compare Splittables by kind and values.
+   */
+  private static boolean sameOrEquals(Splittable value, Splittable otherValue,
+      Map<PendingComparison, Comparison> pending) {
+    if (value == otherValue) {
+      return true;
+    }
+
+    // Strings
+    if (value.isString()) {
+      if (!otherValue.isString()) {
+        return false;
+      }
+      return value.asString().equals(otherValue.asString());
+    }
+
+    // Arrays
+    if (value.isIndexed()) {
+      if (!otherValue.isIndexed()) {
+        return false;
+      }
+
+      if (value.size() != otherValue.size()) {
+        return false;
+      }
+
+      for (int i = 0, j = value.size(); i < j; i++) {
+        if (!sameOrEquals(value.get(i), otherValue.get(i), pending)) {
+          return false;
+        }
+      }
+      return true;
+    }
+
+    // Objects
+    if (value.isKeyed()) {
+      if (!otherValue.isKeyed()) {
+        return false;
+      }
+      /*
+       * We want to treat a missing property key as a null value, so we can't
+       * just compare the key lists.
+       */
+      List<String> keys = value.getPropertyKeys();
+      for (String key : keys) {
+        if (value.isNull(key)) {
+          // If value['foo'] is null, other['foo'] must also be null
+          if (!otherValue.isNull(key)) {
+            return false;
+          }
+        } else if (otherValue.isNull(key)
+            || !sameOrEquals(value.get(key), otherValue.get(key), pending)) {
+          return false;
+        }
+      }
+
+      // Look at keys only in otherValue, and ensure nullness
+      List<String> otherKeys = new ArrayList<String>(otherValue.getPropertyKeys());
+      otherKeys.removeAll(keys);
+      for (String key : otherKeys) {
+        if (!value.isNull(key)) {
+          return false;
+        }
+      }
+      return true;
+    }
+
+    // Unexpected
+    throw new UnsupportedOperationException("Splittable of unknown type");
+  }
+
+  /**
+   * Utility class.
+   */
+  private AutoBeanUtils() {
+  }
+}
diff --git a/user/src/com/google/web/bindery/autobean/shared/AutoBeanVisitor.java b/user/src/com/google/web/bindery/autobean/shared/AutoBeanVisitor.java
new file mode 100644
index 0000000..bff9d4a
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/shared/AutoBeanVisitor.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.shared;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Allows traversal of an AutoBean object graph.
+ */
+public class AutoBeanVisitor {
+  /**
+   * A PropertyContext that describes the parameterization of the Collection
+   * being visited.
+   */
+  public interface CollectionPropertyContext extends PropertyContext {
+    /**
+     * Returns the collection's element type.
+     * 
+     * @return a Class object representing the element type
+     */
+    Class<?> getElementType();
+  }
+
+  /**
+   * Reserved for future expansion to avoid API breaks.
+   */
+  public interface Context {
+  }
+
+  /**
+   * A PropertyContext that describes the parameterization of the Map being
+   * visited.
+   */
+  public interface MapPropertyContext extends PropertyContext {
+    /**
+     * Returns the map's key type.
+     * 
+     * @return a Class object representing the key type
+     */
+    Class<?> getKeyType();
+
+    /**
+     * Returns the map's value type.
+     * 
+     * @return a Class object representing the value type
+     */
+    Class<?> getValueType();
+  }
+
+  /**
+   * The ParameterizationVisitor provides access to more complete type
+   * information than a simple class literal can provide.
+   * <p>
+   * The order of traversal reflects the declared parameterization of the
+   * property. For example, a {@code Map<String, List<Foo>>} would be traversed
+   * via the following sequence:
+   * 
+   * <pre>
+   * visitType(Map.class);
+   *   visitParameter();
+   *     visitType(String.class);
+   *     endVisitType(String.class);
+   *   endVisitParameter();
+   *   visitParameter();
+   *     visitType(List.class);
+   *       visitParameter();
+   *         visitType(Foo.class);
+   *         endVisitType(Foo.class);
+   *       endParameter();
+   *     endVisitType(List.class);
+   *   endVisitParameter();
+   * endVisitType(Map.class);
+   * </pre>
+   */
+  public static class ParameterizationVisitor {
+    /**
+     * Called when finished with a type parameter.
+     */
+    public void endVisitParameter() {
+    }
+
+    /**
+     * Called when finished with a type.
+     * 
+     * @param type a Class object
+     */
+    public void endVisitType(Class<?> type) {
+    }
+
+    /**
+     * Called when visiting a type parameter.
+     * 
+     * @return {@code true} if the type parameter should be visited
+     */
+    public boolean visitParameter() {
+      return true;
+    }
+
+    /**
+     * Called when visiting a possibly parameterized type.
+     * 
+     * @param type a Class object
+     * @return {@code true} if the type should be visited
+     */
+    public boolean visitType(Class<?> type) {
+      return true;
+    }
+  }
+
+  /**
+   * Allows properties to be reset.
+   */
+  public interface PropertyContext {
+    /**
+     * Allows deeper inspection of the declared parameterization of the
+     * property.
+     */
+    void accept(ParameterizationVisitor visitor);
+
+    /**
+     * Indicates if the {@link #set} method will succeed.
+     * 
+     * @return {@code true} if the property can be set
+     */
+    boolean canSet();
+
+    /**
+     * Returns the expected type of the property.
+     * 
+     * @return a Class object representing the property type
+     */
+    Class<?> getType();
+
+    /**
+     * Sets a property value.
+     * 
+     * @param value the new value
+     */
+    void set(Object value);
+  }
+
+  /**
+   * Called after visiting an {@link AutoBean}.
+   * 
+   * @param bean an {@link AutoBean}
+   * @param ctx a Context
+   */
+  public void endVisit(AutoBean<?> bean, Context ctx) {
+  }
+
+  /**
+   * Called after visiting a reference property.
+   * 
+   * @param propertyName the property name, as a String
+   * @param value the property value
+   * @param ctx a PropertyContext
+   */
+  public void endVisitCollectionProperty(String propertyName, AutoBean<Collection<?>> value,
+      CollectionPropertyContext ctx) {
+    endVisitReferenceProperty(propertyName, value, ctx);
+  }
+
+  /**
+   * Called after visiting a reference property.
+   * 
+   * @param propertyName the property name, as a String
+   * @param value the property value
+   * @param ctx a PropertyContext
+   */
+  public void endVisitMapProperty(String propertyName, AutoBean<Map<?, ?>> value,
+      MapPropertyContext ctx) {
+    endVisitReferenceProperty(propertyName, value, ctx);
+  }
+
+  /**
+   * Called after visiting a reference property.
+   * 
+   * @param propertyName the property name, as a String
+   * @param value the property value
+   * @param ctx a PropertyContext
+   */
+  public void endVisitReferenceProperty(String propertyName, AutoBean<?> value, PropertyContext ctx) {
+  }
+
+  /**
+   * Called after visiting a value property.
+   * 
+   * @param propertyName the property name, as a String
+   * @param value the property value
+   * @param ctx a PropertyContext
+   */
+  public void endVisitValueProperty(String propertyName, Object value, PropertyContext ctx) {
+  }
+
+  /**
+   * Called when visiting an {@link AutoBean}.
+   * 
+   * @param bean an {@link AutoBean}
+   * @param ctx a Context
+   */
+  public boolean visit(AutoBean<?> bean, Context ctx) {
+    return true;
+  }
+
+  /**
+   * Called every time, but {@link #visit(AutoBean, Context)} will be called for
+   * the value only the first time it is encountered.
+   * 
+   * @param propertyName the property name, as a String
+   * @param value the property value
+   * @param ctx a PropertyContext
+   */
+  public boolean visitCollectionProperty(String propertyName, AutoBean<Collection<?>> value,
+      CollectionPropertyContext ctx) {
+    return visitReferenceProperty(propertyName, value, ctx);
+  }
+
+  /**
+   * Called every time, but {@link #visit(AutoBean, Context)} will be called for
+   * the value only the first time it is encountered.
+   * 
+   * @param propertyName the property name, as a String
+   * @param value the property value
+   * @param ctx a PropertyContext
+   */
+  public boolean visitMapProperty(String propertyName, AutoBean<Map<?, ?>> value,
+      MapPropertyContext ctx) {
+    return visitReferenceProperty(propertyName, value, ctx);
+  }
+
+  /**
+   * Called every time, but {@link #visit(AutoBean, Context)} will be called for
+   * the value only the first time it is encountered.
+   * 
+   * @param propertyName the property name, as a String
+   * @param value the property value
+   * @param ctx a PropertyContext
+   */
+  public boolean visitReferenceProperty(String propertyName, AutoBean<?> value, PropertyContext ctx) {
+    return true;
+  }
+
+  /**
+   * TODO: document.
+   * 
+   * @param propertyName the property name, as a String
+   * @param value the property value
+   * @param ctx a PropertyContext
+   */
+  public boolean visitValueProperty(String propertyName, Object value, PropertyContext ctx) {
+    return true;
+  }
+}
diff --git a/user/src/com/google/web/bindery/autobean/shared/Splittable.java b/user/src/com/google/web/bindery/autobean/shared/Splittable.java
new file mode 100644
index 0000000..2698345
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/shared/Splittable.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.shared;
+
+import com.google.web.bindery.autobean.shared.impl.StringQuoter;
+
+import java.util.List;
+
+/**
+ * This interface provides an abstraction around the underlying data model
+ * (JavaScriptObject, {@code org.json}, or XML) used to encode an AutoBeanCodex
+ * payload.
+ */
+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);
+
+  /**
+   * Returns the named property.
+   */
+  Splittable get(String key);
+
+  /**
+   * Returns a wire-format representation of the data.
+   */
+  String getPayload();
+
+  /**
+   * Returns all keys available in the Splittable. This method may be expensive
+   * to compute.
+   */
+  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.
+   */
+  boolean isIndexed();
+
+  /**
+   * Returns {@code} true if {@link #getPropertyKeys()} and {@link #get(String)}
+   * can be expected to return meaningful values.
+   */
+  boolean isKeyed();
+
+  /**
+   * Indicates if the nth element of a list is null or undefined.
+   */
+  boolean isNull(int index);
+
+  /**
+   * Indicates if the named property is null or undefined.
+   */
+  boolean isNull(String key);
+
+  /**
+   * Returns {@code true} if the value of the Splittable is numeric.
+   */
+  boolean isNumber();
+
+  /**
+   * Returns {@code true} if {@link #setReified(String, Object)} has been called
+   * with the given key.
+   */
+  boolean isReified(String key);
+
+  /**
+   * Returns {@code} true if {@link #asString()} can be expected to return a
+   * meaningful value.
+   */
+  boolean isString();
+
+  /**
+   * Returns {@code true} if the value of the key is undefined.
+   */
+  boolean isUndefined(String key);
+
+  /**
+   * Associates a tag value with the Splittable.
+   */
+  void setReified(String key, Object object);
+
+  /**
+   * Resets the length of an indexed Splittable.
+   */
+  void setSize(int i);
+
+  /**
+   * Returns the size of an indexed Splittable.
+   */
+  int size();
+}
diff --git a/user/src/com/google/web/bindery/autobean/shared/ValueCodex.java b/user/src/com/google/web/bindery/autobean/shared/ValueCodex.java
new file mode 100644
index 0000000..4bcb304
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/shared/ValueCodex.java
@@ -0,0 +1,368 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.shared;
+
+import com.google.web.bindery.autobean.shared.impl.StringQuoter;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Provides unified encoding and decoding of value objects.
+ */
+public class ValueCodex {
+  enum Type {
+    BIG_DECIMAL(BigDecimal.class) {
+      @Override
+      public boolean canUpcast(Object value) {
+        return value instanceof BigDecimal;
+      }
+
+      @Override
+      public BigDecimal decode(Class<?> clazz, Splittable value) {
+        return new BigDecimal(value.asString());
+      }
+
+      @Override
+      public Splittable encode(Object value) {
+        return StringQuoter.create(((BigDecimal) value).toString());
+      }
+    },
+    BIG_INTEGER(BigInteger.class) {
+      @Override
+      public boolean canUpcast(Object value) {
+        return value instanceof BigInteger;
+      }
+
+      @Override
+      public BigInteger decode(Class<?> clazz, Splittable value) {
+        return new BigInteger(value.asString());
+      }
+
+      @Override
+      public Splittable encode(Object value) {
+        return StringQuoter.create(((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);
+      }
+    },
+    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);
+      }
+    },
+    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));
+      }
+    },
+    DATE(Date.class) {
+      @Override
+      public boolean canUpcast(Object value) {
+        return value instanceof Date;
+      }
+
+      @Override
+      public Date decode(Class<?> clazz, Splittable value) {
+        return StringQuoter.tryParseDate(value.asString());
+      }
+
+      @Override
+      public Splittable encode(Object value) {
+        return StringQuoter.create(String.valueOf(((Date) value).getTime()));
+      }
+    },
+    DOUBLE(Double.class, double.class, 0d) {
+      @Override
+      public Double decode(Class<?> clazz, Splittable value) {
+        return value.asNumber();
+      }
+
+      @Override
+      public Splittable encode(Object value) {
+        return StringQuoter.create((Double) value);
+      }
+    },
+    ENUM(Enum.class) {
+      @Override
+      public Enum<?> decode(Class<?> clazz, Splittable value) {
+        return (Enum<?>) clazz.getEnumConstants()[(int) value.asNumber()];
+      }
+
+      @Override
+      public Splittable encode(Object value) {
+        return StringQuoter.create(((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);
+      }
+    },
+    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);
+      }
+    },
+    LONG(Long.class, long.class, 0L) {
+      @Override
+      public Long decode(Class<?> clazz, Splittable value) {
+        return Long.parseLong(value.asString());
+      }
+
+      @Override
+      public Splittable encode(Object value) {
+        return StringQuoter.create(String.valueOf((Long) value));
+      }
+    },
+    SHORT(Short.class, short.class, (short) 0) {
+      @Override
+      public Short decode(Class<?> clazz, Splittable value) {
+        return (short) value.asNumber();
+      }
+
+      @Override
+      public Splittable encode(Object value) {
+        return StringQuoter.create((Short) value);
+      }
+    },
+    STRING(String.class) {
+      @Override
+      public String decode(Class<?> clazz, Splittable value) {
+        return value.asString();
+      }
+
+      @Override
+      public Splittable encode(Object value) {
+        return StringQuoter.create((String) value);
+      }
+    },
+    SPLITTABLE(Splittable.class) {
+      @Override
+      public Splittable decode(Class<?> clazz, Splittable value) {
+        return value;
+      }
+
+      @Override
+      public Splittable encode(Object value) {
+        return (Splittable) value;
+      }
+    },
+    VOID(Void.class, void.class, null) {
+      @Override
+      public Void decode(Class<?> clazz, Splittable value) {
+        return null;
+      }
+
+      @Override
+      public Splittable encode(Object value) {
+        return null;
+      }
+    };
+    private final Object defaultValue;
+    private final Class<?> type;
+    private final Class<?> primitiveType;
+
+    Type(Class<?> objectType) {
+      this(objectType, null, null);
+    }
+
+    Type(Class<?> objectType, Class<?> primitiveType, Object defaultValue) {
+      this.type = objectType;
+      this.primitiveType = primitiveType;
+      this.defaultValue = defaultValue;
+    }
+
+    /**
+     * Determines whether or not the Type can handle the given value via
+     * upcasting semantics.
+     * 
+     * @param value a value Object
+     */
+    public boolean canUpcast(Object value) {
+      // Most value types are final, so this method is meaningless
+      return false;
+    }
+
+    public abstract Object decode(Class<?> clazz, Splittable value);
+
+    public abstract Splittable encode(Object value);
+
+    public Object getDefaultValue() {
+      return defaultValue;
+    }
+
+    public Class<?> getPrimitiveType() {
+      return primitiveType;
+    }
+
+    public Class<?> getType() {
+      return type;
+    }
+  }
+
+  private static final Set<Class<?>> ALL_VALUE_TYPES;
+  private static final Map<Class<?>, Type> TYPES_BY_CLASS;
+  static {
+    Map<Class<?>, Type> temp = new HashMap<Class<?>, Type>();
+    for (Type t : Type.values()) {
+      temp.put(t.getType(), t);
+      if (t.getPrimitiveType() != null) {
+        temp.put(t.getPrimitiveType(), t);
+      }
+    }
+    ALL_VALUE_TYPES = Collections.unmodifiableSet(temp.keySet());
+    TYPES_BY_CLASS = Collections.unmodifiableMap(temp);
+  }
+
+  /**
+   * Returns true if ValueCodex can operate on values of the given type.
+   * 
+   * @param clazz a Class object
+   * @return {@code true} if the given object type can be decoded
+   */
+  public static boolean canDecode(Class<?> clazz) {
+    if (findType(clazz) != null) {
+      return true;
+    }
+    // Use other platform-specific tests
+    return ValueCodexHelper.canDecode(clazz);
+  }
+
+  @SuppressWarnings("unchecked")
+  public static <T> T decode(Class<T> clazz, Splittable split) {
+    if (split == null || split == Splittable.NULL) {
+      return null;
+    }
+    return (T) getTypeOrDie(clazz).decode(clazz, split);
+  }
+
+  /**
+   * No callers in GWT codebase.
+   * 
+   * @deprecated use {@link #decode(Class, Splittable)} instead.
+   * @throws UnsupportedOperationException
+   */
+  @Deprecated
+  public static <T> T decode(Class<T> clazz, String string) {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Encode a value object when the wire format type is known. This method
+   * should be preferred over {@link #encode(Object)} when possible.
+   */
+  public static Splittable encode(Class<?> clazz, Object obj) {
+    if (obj == null) {
+      return Splittable.NULL;
+    }
+    return getTypeOrDie(clazz).encode(obj);
+  }
+
+  public static Splittable encode(Object obj) {
+    if (obj == null) {
+      return Splittable.NULL;
+    }
+    Type t = findType(obj.getClass());
+    // Try upcasting
+    if (t == null) {
+      for (Type maybe : Type.values()) {
+        if (maybe.canUpcast(obj)) {
+          t = maybe;
+          break;
+        }
+      }
+    }
+    if (t == null) {
+      throw new UnsupportedOperationException(obj.getClass().getName());
+    }
+    return t.encode(obj);
+  }
+
+  /**
+   * Return all Value types that can be processed by the ValueCodex.
+   */
+  public static Set<Class<?>> getAllValueTypes() {
+    return ALL_VALUE_TYPES;
+  }
+
+  /**
+   * Returns the uninitialized field value for the given primitive type.
+   */
+  public static Object getUninitializedFieldValue(Class<?> clazz) {
+    Type type = getTypeOrDie(clazz);
+    if (clazz.equals(type.getPrimitiveType())) {
+      return type.getDefaultValue();
+    }
+    return null;
+  }
+
+  /**
+   * May return <code>null</code>.
+   */
+  private static <T> Type findType(Class<T> clazz) {
+    if (clazz.isEnum()) {
+      return Type.ENUM;
+    }
+    return TYPES_BY_CLASS.get(clazz);
+  }
+
+  private static <T> Type getTypeOrDie(Class<T> clazz) {
+    Type toReturn = findType(clazz);
+    if (toReturn == null) {
+      throw new UnsupportedOperationException(clazz.getName());
+    }
+    return toReturn;
+  }
+}
diff --git a/user/src/com/google/web/bindery/autobean/shared/ValueCodexHelper.java b/user/src/com/google/web/bindery/autobean/shared/ValueCodexHelper.java
new file mode 100644
index 0000000..fb9fcc9
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/shared/ValueCodexHelper.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.shared;
+
+import com.google.gwt.core.client.GWT;
+
+/**
+ * Provides reflection-based operation for server (JVM) implementation. There is
+ * a no-op super-source version for client (dev- and web-mode) code.
+ */
+class ValueCodexHelper {
+  /**
+   * Returns {@code true} if {@code clazz} is assignable to any of the value
+   * types.
+   */
+  static boolean canDecode(Class<?> clazz) {
+    assert !GWT.isClient();
+    for (Class<?> valueType : ValueCodex.getAllValueTypes()) {
+      if (valueType.isAssignableFrom(clazz)) {
+        return true;
+      }
+    }
+    return false;
+  }
+}
diff --git a/user/src/com/google/web/bindery/autobean/shared/impl/AbstractAutoBean.java b/user/src/com/google/web/bindery/autobean/shared/impl/AbstractAutoBean.java
new file mode 100644
index 0000000..4e801d9
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/shared/impl/AbstractAutoBean.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.shared.impl;
+
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory;
+import com.google.web.bindery.autobean.shared.AutoBeanUtils;
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor;
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor.Context;
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.Coder;
+import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.EncodeState;
+import com.google.gwt.core.client.impl.WeakMapping;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Basic implementation.
+ * 
+ * @param <T> the wrapper type
+ */
+public abstract class AbstractAutoBean<T> implements AutoBean<T>, HasSplittable {
+  /**
+   * Used to avoid cycles when visiting.
+   */
+  public static class OneShotContext implements Context {
+    private final Set<AbstractAutoBean<?>> seen = new HashSet<AbstractAutoBean<?>>();
+
+    public boolean hasSeen(AbstractAutoBean<?> bean) {
+      return !seen.add(bean);
+    }
+  }
+
+  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;
+  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;
+
+  /**
+   * 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();
+  }
+
+  /**
+   * Constructor that wraps an existing object. The parameters on this method
+   * are reversed to avoid conflicting with the other two-arg constructor for
+   * {@code AutoBean<Splittable>} instances.
+   */
+  protected AbstractAutoBean(T wrapped, AutoBeanFactory factory) {
+    this.factory = factory;
+    usingSimplePeer = false;
+    data = null;
+    this.wrapped = wrapped;
+
+    // Used by AutoBeanUtils
+    WeakMapping.set(wrapped, AutoBean.class.getName(), this);
+  }
+
+  public void accept(AutoBeanVisitor visitor) {
+    traverse(visitor, new OneShotContext());
+  }
+
+  public abstract T as();
+
+  public AutoBean<T> clone(boolean deep) {
+    throw new UnsupportedOperationException();
+  }
+
+  public AutoBeanFactory getFactory() {
+    return factory;
+  }
+
+  public Splittable getSplittable() {
+    return data;
+  }
+
+  @SuppressWarnings("unchecked")
+  public <Q> Q getTag(String tagName) {
+    return tags == null ? null : (Q) tags.get(tagName);
+  }
+
+  /**
+   * Indicates that the value returned from {@link #getSplittable()} may not
+   * contain all of the data encapsulated by the AutoBean.
+   */
+  public boolean hasUnsplittableValues() {
+    return data.isReified(UNSPLITTABLE_VALUES_KEY);
+  }
+
+  public boolean isFrozen() {
+    return frozen;
+  }
+
+  public boolean isWrapper() {
+    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;
+  }
+
+  public void setTag(String tagName, Object value) {
+    if (tags == null) {
+      tags = new HashMap<String, Object>();
+    }
+    tags.put(tagName, value);
+  }
+
+  public void traverse(AutoBeanVisitor visitor, OneShotContext ctx) {
+    // Avoid cycles
+    if (ctx.hasSeen(this)) {
+      return;
+    }
+    if (visitor.visit(this, ctx)) {
+      traverseProperties(visitor, ctx);
+    }
+    visitor.endVisit(this, ctx);
+  }
+
+  public T unwrap() {
+    if (usingSimplePeer) {
+      throw new IllegalStateException();
+    }
+    try {
+      WeakMapping.set(wrapped, AutoBean.class.getName(), null);
+      return wrapped;
+    } finally {
+      wrapped = null;
+    }
+  }
+
+  /**
+   * No-op. Used as a debugger hook point for generated code.
+   * 
+   * @param method the method name
+   * @param returned the returned object
+   * @param parameters the parameter list
+   */
+  protected void call(String method, Object returned, Object... parameters) {
+  }
+
+  protected void checkFrozen() {
+    if (frozen) {
+      throw new IllegalStateException("The AutoBean has been frozen");
+    }
+  }
+
+  protected void checkWrapped() {
+    if (wrapped == null && !usingSimplePeer) {
+      throw new IllegalStateException("The AutoBean has been unwrapped");
+    }
+  }
+
+  protected T createSimplePeer() {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * No-op. Used as a debugger hook point for generated code.
+   * 
+   * @param method the method name
+   * @param toReturn the value to return
+   */
+  protected <V> V get(String method, V toReturn) {
+    return toReturn;
+  }
+
+  protected <W> W getFromWrapper(W obj) {
+    // Some versions of javac have problem inferring the generics here
+    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();
+    return wrapped;
+  }
+
+  protected boolean isUsingSimplePeer() {
+    return usingSimplePeer;
+  }
+
+  protected boolean isWrapped(Object obj) {
+    return AutoBeanUtils.getAutoBean(obj) != null;
+  }
+
+  /**
+   * No-op. Used as a debugger hook point for generated code.
+   * 
+   * @param method the method name
+   * @param value the Object value to be set
+   */
+  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);
+}
diff --git a/user/src/com/google/web/bindery/autobean/shared/impl/AutoBeanCodexImpl.java b/user/src/com/google/web/bindery/autobean/shared/impl/AutoBeanCodexImpl.java
new file mode 100644
index 0000000..78b4f2e
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/shared/impl/AutoBeanCodexImpl.java
@@ -0,0 +1,613 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.shared.impl;
+
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory;
+import com.google.web.bindery.autobean.shared.AutoBeanUtils;
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor;
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor.ParameterizationVisitor;
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.shared.ValueCodex;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+
+/**
+ * Contains the implementation details of AutoBeanCodex. This type was factored
+ * out of AutoBeanCodex so that various implementation details can be accessed
+ * without polluting a public API.
+ */
+public class AutoBeanCodexImpl {
+
+  /**
+   * Describes a means of encoding or decoding a particular type of data to or
+   * from a wire format representation. Any given instance of a Coder should be
+   * stateless; any state required for operation must be maintained in an
+   * {@link EncodeState}.
+   */
+  public interface Coder {
+    Object decode(EncodeState state, Splittable data);
+
+    void encode(EncodeState state, Object value);
+
+    Splittable extractSplittable(EncodeState state, Object value);
+  }
+
+  /**
+   * Contains transient state for Coder operation.
+   */
+  public static class EncodeState {
+    /**
+     * Constructs a state object used for decoding payloads.
+     */
+    public static EncodeState forDecode(AutoBeanFactory factory) {
+      return new EncodeState(factory, null);
+    }
+
+    /**
+     * Constructs a state object used for encoding payloads.
+     */
+    public static EncodeState forEncode(AutoBeanFactory factory, StringBuilder sb) {
+      return new EncodeState(factory, sb);
+    }
+
+    /**
+     * Constructs a "stateless" state for testing Coders that do not require
+     * AutoBean implementation details.
+     */
+    public static EncodeState forTesting() {
+      return new EncodeState(null, null);
+    }
+
+    final EnumMap enumMap;
+    final AutoBeanFactory factory;
+    final StringBuilder sb;
+    final Stack<AutoBean<?>> seen;
+
+    private EncodeState(AutoBeanFactory factory, StringBuilder sb) {
+      this.factory = factory;
+      enumMap = factory instanceof EnumMap ? (EnumMap) factory : null;
+      this.sb = sb;
+      this.seen = sb == null ? null : new Stack<AutoBean<?>>();
+    }
+  }
+
+  /**
+   * Dynamically creates a Coder that is capable of operating on a particular
+   * parameterization of a datastructure (e.g. {@code Map<String, List<String>>}
+   * ).
+   */
+  static class CoderCreator extends ParameterizationVisitor {
+    private Stack<Coder> stack = new Stack<Coder>();
+
+    @Override
+    public void endVisitType(Class<?> type) {
+      if (List.class.equals(type) || Set.class.equals(type)) {
+        stack.push(collectionCoder(type, stack.pop()));
+      } else if (Map.class.equals(type)) {
+        // Note that the parameters are passed in reverse order
+        stack.push(mapCoder(stack.pop(), stack.pop()));
+      } else if (Splittable.class.equals(type)) {
+        stack.push(splittableCoder());
+      } else if (type.getEnumConstants() != null) {
+        @SuppressWarnings(value = {"unchecked"})
+        Class<Enum<?>> enumType = (Class<Enum<?>>) type;
+        stack.push(enumCoder(enumType));
+      } else if (ValueCodex.canDecode(type)) {
+        stack.push(valueCoder(type));
+      } else {
+        stack.push(objectCoder(type));
+      }
+    }
+
+    public Coder getCoder() {
+      assert stack.size() == 1 : "Incorrect size: " + stack.size();
+      return stack.pop();
+    }
+  }
+
+  /**
+   * Constructs one of the lightweight collection types.
+   */
+  static class CollectionCoder implements Coder {
+    private final Coder elementDecoder;
+    private final Class<?> type;
+
+    public CollectionCoder(Class<?> type, Coder elementDecoder) {
+      this.elementDecoder = elementDecoder;
+      this.type = type;
+    }
+
+    public Object decode(EncodeState state, Splittable data) {
+      Collection<Object> collection;
+      if (List.class.equals(type)) {
+        collection = new SplittableList<Object>(data, elementDecoder, state);
+      } else if (Set.class.equals(type)) {
+        collection = new SplittableSet<Object>(data, elementDecoder, state);
+      } else {
+        // Should not reach here
+        throw new RuntimeException(type.getName());
+      }
+      return collection;
+    }
+
+    public void encode(EncodeState state, Object value) {
+      if (value == null) {
+        state.sb.append("null");
+        return;
+      }
+
+      Iterator<?> it = ((Collection<?>) value).iterator();
+      state.sb.append("[");
+      if (it.hasNext()) {
+        elementDecoder.encode(state, it.next());
+        while (it.hasNext()) {
+          state.sb.append(",");
+          elementDecoder.encode(state, it.next());
+        }
+      }
+      state.sb.append("]");
+    }
+
+    public Splittable extractSplittable(EncodeState state, Object value) {
+      return tryExtractSplittable(value);
+    }
+  }
+
+  /**
+   * Produces enums.
+   * 
+   * @param <E>
+   */
+  static class EnumCoder<E extends Enum<?>> implements Coder {
+    private final Class<E> type;
+
+    public EnumCoder(Class<E> type) {
+      this.type = type;
+    }
+
+    public Object decode(EncodeState state, Splittable data) {
+      return state.enumMap.getEnum(type, data.asString());
+    }
+
+    public void encode(EncodeState state, Object value) {
+      if (value == null) {
+        state.sb.append("null");
+        return;
+      }
+      state.sb.append(StringQuoter.quote(state.enumMap.getToken((Enum<?>) value)));
+    }
+
+    public Splittable extractSplittable(EncodeState state, Object value) {
+      return StringQuoter.split(StringQuoter.quote(state.enumMap.getToken((Enum<?>) value)));
+    }
+  }
+
+  /**
+   * Used to stop processing.
+   */
+  static class HaltException extends RuntimeException {
+    public HaltException(RuntimeException cause) {
+      super(cause);
+    }
+
+    @Override
+    public RuntimeException getCause() {
+      return (RuntimeException) super.getCause();
+    }
+  }
+
+  /**
+   * Constructs one of the lightweight Map types, depending on the key type.
+   */
+  static class MapCoder implements Coder {
+    private final Coder keyDecoder;
+    private final Coder valueDecoder;
+
+    /**
+     * Parameters in reversed order to accommodate stack-based setup.
+     */
+    public MapCoder(Coder valueDecoder, Coder keyDecoder) {
+      this.keyDecoder = keyDecoder;
+      this.valueDecoder = valueDecoder;
+    }
+
+    public Object decode(EncodeState state, Splittable data) {
+      Map<Object, Object> toReturn;
+      if (data.isIndexed()) {
+        assert data.size() == 2 : "Wrong data size: " + data.size();
+        toReturn = new SplittableComplexMap<Object, Object>(data, keyDecoder, valueDecoder, state);
+      } else {
+        toReturn = new SplittableSimpleMap<Object, Object>(data, keyDecoder, valueDecoder, state);
+      }
+      return toReturn;
+    }
+
+    public void encode(EncodeState state, Object value) {
+      if (value == null) {
+        state.sb.append("null");
+        return;
+      }
+
+      Map<?, ?> map = (Map<?, ?>) value;
+      boolean isSimpleMap = keyDecoder instanceof ValueCoder;
+      if (isSimpleMap) {
+        boolean first = true;
+        state.sb.append("{");
+        for (Map.Entry<?, ?> entry : map.entrySet()) {
+          Object mapKey = entry.getKey();
+          if (mapKey == null) {
+            // A null key in a simple map is meaningless
+            continue;
+          }
+          Object mapValue = entry.getValue();
+
+          if (first) {
+            first = false;
+          } else {
+            state.sb.append(",");
+          }
+
+          keyDecoder.encode(state, mapKey);
+          state.sb.append(":");
+          if (mapValue == null) {
+            // Null values must be preserved
+            state.sb.append("null");
+          } else {
+            valueDecoder.encode(state, mapValue);
+          }
+        }
+        state.sb.append("}");
+      } else {
+        List<Object> keys = new ArrayList<Object>(map.size());
+        List<Object> values = new ArrayList<Object>(map.size());
+        for (Map.Entry<?, ?> entry : map.entrySet()) {
+          keys.add(entry.getKey());
+          values.add(entry.getValue());
+        }
+        state.sb.append("[");
+        collectionCoder(List.class, keyDecoder).encode(state, keys);
+        state.sb.append(",");
+        collectionCoder(List.class, valueDecoder).encode(state, values);
+        state.sb.append("]");
+      }
+    }
+
+    public Splittable extractSplittable(EncodeState state, Object value) {
+      return tryExtractSplittable(value);
+    }
+  }
+
+  /**
+   * Recurses into {@link AutoBeanCodexImpl}.
+   */
+  static class ObjectCoder implements Coder {
+    private final Class<?> type;
+
+    public ObjectCoder(Class<?> type) {
+      this.type = type;
+    }
+
+    public Object decode(EncodeState state, Splittable data) {
+      AutoBean<?> bean = doDecode(state, type, data);
+      return bean == null ? null : bean.as();
+    }
+
+    public void encode(EncodeState state, Object value) {
+      if (value == null) {
+        state.sb.append("null");
+        return;
+      }
+      doEncode(state, AutoBeanUtils.getAutoBean(value));
+    }
+
+    public Splittable extractSplittable(EncodeState state, Object value) {
+      return tryExtractSplittable(value);
+    }
+  }
+
+  static class PropertyCoderCreator extends AutoBeanVisitor {
+    private AutoBean<?> bean;
+
+    @Override
+    public boolean visit(AutoBean<?> bean, Context ctx) {
+      this.bean = bean;
+      return true;
+    }
+
+    @Override
+    public boolean visitReferenceProperty(String propertyName, AutoBean<?> value,
+        PropertyContext ctx) {
+      maybeCreateCoder(propertyName, ctx);
+      return false;
+    }
+
+    @Override
+    public boolean visitValueProperty(String propertyName, Object value, PropertyContext ctx) {
+      maybeCreateCoder(propertyName, ctx);
+      return false;
+    }
+
+    private void maybeCreateCoder(String propertyName, PropertyContext ctx) {
+      CoderCreator creator = new CoderCreator();
+      ctx.accept(creator);
+      coderFor.put(key(bean, propertyName), creator.getCoder());
+    }
+  }
+
+  /**
+   * Extracts properties from a bean and turns them into JSON text.
+   */
+  static class PropertyGetter extends AutoBeanVisitor {
+    private boolean first = true;
+    private final EncodeState state;
+
+    public PropertyGetter(EncodeState state) {
+      this.state = state;
+    }
+
+    @Override
+    public void endVisit(AutoBean<?> bean, Context ctx) {
+      state.sb.append("}");
+      state.seen.pop();
+    }
+
+    @Override
+    public boolean visit(AutoBean<?> bean, Context ctx) {
+      if (state.seen.contains(bean)) {
+        throw new HaltException(new UnsupportedOperationException("Cycles not supported"));
+      }
+      state.seen.push(bean);
+      state.sb.append("{");
+      return true;
+    }
+
+    @Override
+    public boolean visitReferenceProperty(String propertyName, AutoBean<?> value,
+        PropertyContext ctx) {
+      if (value != null) {
+        encodeProperty(propertyName, value.as(), ctx);
+      }
+      return false;
+    }
+
+    @Override
+    public boolean visitValueProperty(String propertyName, Object value, PropertyContext ctx) {
+      if (value != null && !value.equals(ValueCodex.getUninitializedFieldValue(ctx.getType()))) {
+        encodeProperty(propertyName, value, ctx);
+      }
+      return false;
+    }
+
+    private void encodeProperty(String propertyName, Object value, PropertyContext ctx) {
+      CoderCreator pd = new CoderCreator();
+      ctx.accept(pd);
+      Coder decoder = pd.getCoder();
+      if (first) {
+        first = false;
+      } else {
+        state.sb.append(",");
+      }
+      state.sb.append(StringQuoter.quote(propertyName));
+      state.sb.append(":");
+      decoder.encode(state, value);
+    }
+  }
+
+  /**
+   * Populates beans with data extracted from an evaluated JSON payload.
+   */
+  static class PropertySetter extends AutoBeanVisitor {
+    private Splittable data;
+    private EncodeState state;
+
+    public void decodeInto(EncodeState state, Splittable data, AutoBean<?> bean) {
+      this.data = data;
+      this.state = state;
+      bean.accept(this);
+    }
+
+    @Override
+    public boolean visitReferenceProperty(String propertyName, AutoBean<?> value,
+        PropertyContext ctx) {
+      decodeProperty(propertyName, ctx);
+      return false;
+    }
+
+    @Override
+    public boolean visitValueProperty(String propertyName, Object value, PropertyContext ctx) {
+      decodeProperty(propertyName, ctx);
+      return false;
+    }
+
+    protected void decodeProperty(String propertyName, PropertyContext ctx) {
+      if (!data.isNull(propertyName)) {
+        CoderCreator pd = new CoderCreator();
+        ctx.accept(pd);
+        Coder decoder = pd.getCoder();
+        Object propertyValue = decoder.decode(state, data.get(propertyName));
+        ctx.set(propertyValue);
+      }
+    }
+  }
+
+  /**
+   * A passthrough Coder.
+   */
+  static class SplittableCoder implements Coder {
+    static final Coder INSTANCE = new SplittableCoder();
+
+    public Object decode(EncodeState state, Splittable data) {
+      return data;
+    }
+
+    public void encode(EncodeState state, Object value) {
+      if (value == null) {
+        state.sb.append("null");
+        return;
+      }
+      state.sb.append(((Splittable) value).getPayload());
+    }
+
+    public Splittable extractSplittable(EncodeState state, Object value) {
+      return (Splittable) value;
+    }
+  }
+
+  /**
+   * Delegates to ValueCodex.
+   */
+  static class ValueCoder implements Coder {
+    private final Class<?> type;
+
+    public ValueCoder(Class<?> type) {
+      assert type.getEnumConstants() == null : "Should use EnumTypeCodex";
+      this.type = type;
+    }
+
+    public Object decode(EncodeState state, Splittable propertyValue) {
+      if (propertyValue == null || propertyValue == Splittable.NULL) {
+        return ValueCodex.getUninitializedFieldValue(type);
+      }
+      return ValueCodex.decode(type, propertyValue);
+    }
+
+    public void encode(EncodeState state, Object value) {
+      state.sb.append(ValueCodex.encode(type, value).getPayload());
+    }
+
+    public Splittable extractSplittable(EncodeState state, Object value) {
+      return ValueCodex.encode(type, value);
+    }
+  }
+
+  /**
+   * A map of AutoBean interface+property names to the Coder for that property.
+   */
+  private static final Map<String, Coder> coderFor = new HashMap<String, Coder>();
+  /**
+   * A map of types to a Coder that handles the type.
+   */
+  private static final Map<Class<?>, Coder> coders = new HashMap<Class<?>, Coder>();
+
+  public static Coder collectionCoder(Class<?> type, Coder elementCoder) {
+    return new CollectionCoder(type, elementCoder);
+  }
+
+  public static Coder doCoderFor(AutoBean<?> bean, String propertyName) {
+    String key = key(bean, propertyName);
+    Coder toReturn = coderFor.get(key);
+    if (toReturn == null) {
+      bean.accept(new PropertyCoderCreator());
+      toReturn = coderFor.get(key);
+      if (toReturn == null) {
+        throw new IllegalArgumentException(propertyName);
+      }
+    }
+    return toReturn;
+  }
+
+  public static <T> AutoBean<T> doDecode(EncodeState state, Class<T> clazz, Splittable data) {
+    /*
+     * If we decode the same Splittable twice, re-use the ProxyAutoBean to
+     * maintain referential integrity. If we didn't do this, either facade would
+     * update the same backing data, yet not be the same object via ==
+     * comparison.
+     */
+    @SuppressWarnings("unchecked")
+    AutoBean<T> toReturn = (AutoBean<T>) data.getReified(AutoBeanCodexImpl.class.getName());
+    if (toReturn != null) {
+      return toReturn;
+    }
+    toReturn = state.factory.create(clazz);
+    data.setReified(AutoBeanCodexImpl.class.getName(), toReturn);
+    if (toReturn == null) {
+      throw new IllegalArgumentException(clazz.getName());
+    }
+    ((AbstractAutoBean<T>) toReturn).setData(data);
+    return toReturn;
+  }
+
+  public static void doDecodeInto(EncodeState state, Splittable data, AutoBean<?> bean) {
+    new PropertySetter().decodeInto(state, data, bean);
+  }
+
+  public static void doEncode(EncodeState state, AutoBean<?> bean) {
+    PropertyGetter e = new PropertyGetter(state);
+    try {
+      bean.accept(e);
+    } catch (HaltException ex) {
+      throw ex.getCause();
+    }
+  }
+
+  public static <E extends Enum<?>> Coder enumCoder(Class<E> type) {
+    Coder toReturn = coders.get(type);
+    if (toReturn == null) {
+      toReturn = new EnumCoder<E>(type);
+      coders.put(type, toReturn);
+    }
+    return toReturn;
+  }
+
+  public static Coder mapCoder(Coder valueCoder, Coder keyCoder) {
+    return new MapCoder(valueCoder, keyCoder);
+  }
+
+  public static Coder objectCoder(Class<?> type) {
+    Coder toReturn = coders.get(type);
+    if (toReturn == null) {
+      toReturn = new ObjectCoder(type);
+      coders.put(type, toReturn);
+    }
+    return toReturn;
+  }
+
+  public static Coder splittableCoder() {
+    return SplittableCoder.INSTANCE;
+  }
+
+  public static Coder valueCoder(Class<?> type) {
+    Coder toReturn = coders.get(type);
+    if (toReturn == null) {
+      toReturn = new ValueCoder(type);
+      coders.put(type, toReturn);
+    }
+    return toReturn;
+  }
+
+  static Splittable tryExtractSplittable(Object value) {
+    AutoBean<?> bean = AutoBeanUtils.getAutoBean(value);
+    if (bean != null) {
+      value = bean;
+    }
+    if (bean instanceof HasSplittable) {
+      return ((HasSplittable) bean).getSplittable();
+    }
+    return null;
+  }
+
+  private static String key(AutoBean<?> bean, String propertyName) {
+    return bean.getType().getName() + ":" + propertyName;
+  }
+}
diff --git a/user/src/com/google/web/bindery/autobean/shared/impl/EnumMap.java b/user/src/com/google/web/bindery/autobean/shared/impl/EnumMap.java
new file mode 100644
index 0000000..8356831
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/shared/impl/EnumMap.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.shared.impl;
+
+/**
+ * This interface is implemented by our generated AutoBeanFactory types to
+ * convert enum values to strings.
+ */
+public interface EnumMap {
+  /**
+   * Extra enums that should be included in the AutoBeanFactory.
+   */
+  public @interface ExtraEnums {
+    Class<? extends Enum<?>>[] value();
+  }
+
+  <E extends Enum<?>> E getEnum(Class<E> clazz, String token);
+
+  String getToken(Enum<?> e);
+}
diff --git a/user/src/com/google/web/bindery/autobean/shared/impl/HasSplittable.java b/user/src/com/google/web/bindery/autobean/shared/impl/HasSplittable.java
new file mode 100644
index 0000000..41b39d2
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/shared/impl/HasSplittable.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.shared.impl;
+
+import com.google.web.bindery.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/web/bindery/autobean/shared/impl/SplittableComplexMap.java b/user/src/com/google/web/bindery/autobean/shared/impl/SplittableComplexMap.java
new file mode 100644
index 0000000..c118d29
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/shared/impl/SplittableComplexMap.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.shared.impl;
+
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.Coder;
+import com.google.web.bindery.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/web/bindery/autobean/shared/impl/SplittableList.java b/user/src/com/google/web/bindery/autobean/shared/impl/SplittableList.java
new file mode 100644
index 0000000..48fe0f6
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/shared/impl/SplittableList.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.shared.impl;
+
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.Coder;
+import com.google.web.bindery.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/web/bindery/autobean/shared/impl/SplittableSet.java b/user/src/com/google/web/bindery/autobean/shared/impl/SplittableSet.java
new file mode 100644
index 0000000..9b6d8ab
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/shared/impl/SplittableSet.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.shared.impl;
+
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.Coder;
+import com.google.web.bindery.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/web/bindery/autobean/shared/impl/SplittableSimpleMap.java b/user/src/com/google/web/bindery/autobean/shared/impl/SplittableSimpleMap.java
new file mode 100644
index 0000000..fc4dc0f
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/shared/impl/SplittableSimpleMap.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.shared.impl;
+
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.Coder;
+import com.google.web.bindery.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/web/bindery/autobean/shared/impl/StringQuoter.java b/user/src/com/google/web/bindery/autobean/shared/impl/StringQuoter.java
new file mode 100644
index 0000000..d30fb3e
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/shared/impl/StringQuoter.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.shared.impl;
+
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.vm.impl.JsonSplittable;
+
+import org.json.JSONObject;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * This class has a super-source version with a client-only implementation.
+ */
+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 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();
+  }
+
+  /**
+   * Create a quoted JSON string.
+   */
+  public static String quote(String raw) {
+    return JSONObject.quote(raw);
+  }
+
+  public static Splittable split(String payload) {
+    return JsonSplittable.create(payload);
+  }
+
+  /**
+   * Attempt to parse an ISO-8601 date format. May return {@code null} if the
+   * input cannot be parsed.
+   */
+  public static Date tryParseDate(String date) {
+    try {
+      return new Date(Long.parseLong(date));
+    } catch (NumberFormatException ignored) {
+    }
+    if (date.endsWith("Z")) {
+      date = date.substring(0, date.length() - 1) + "+0000";
+    }
+    try {
+      return ISO8601.parse(date);
+    } catch (ParseException ignored) {
+    }
+    try {
+      return RFC2822.parse(date);
+    } catch (ParseException ignored) {
+    }
+    return null;
+  }
+}
diff --git a/user/src/com/google/web/bindery/autobean/shared/package-info.java b/user/src/com/google/web/bindery/autobean/shared/package-info.java
new file mode 100644
index 0000000..4dec6fa
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/shared/package-info.java
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+/**
+ * The AutoBean framework provides automatically-generated implementations of
+ * bean-like interfaces and a low-level serialization mechanism for those
+ * interfaces. AutoBeans can be used in both client and server code to improve
+ * code re-use.
+ * 
+ * @see <a
+ *      href="http://code.google.com/p/google-web-toolkit/wiki/AutoBean">AutoBean
+ *      wiki page</a>
+ * @see com.google.web.bindery.autobean.shared.AutoBeanFactory
+ * @see com.google.web.bindery.autobean.vm.AutoBeanFactorySource
+ */
+@com.google.gwt.util.PreventSpuriousRebuilds
+package com.google.web.bindery.autobean.shared;
+
diff --git a/user/src/com/google/web/bindery/autobean/vm/AutoBeanFactorySource.java b/user/src/com/google/web/bindery/autobean/vm/AutoBeanFactorySource.java
new file mode 100644
index 0000000..eaba1d0
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/vm/AutoBeanFactorySource.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.vm;
+
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory.Category;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory.NoWrap;
+import com.google.web.bindery.autobean.shared.impl.EnumMap;
+import com.google.web.bindery.autobean.vm.impl.FactoryHandler;
+import com.google.web.bindery.autobean.vm.impl.ProxyAutoBean;
+
+/**
+ * Generates JVM-compatible implementations of AutoBeanFactory and AutoBean
+ * types.
+ * <p>
+ * This implementation is written assuming that the AutoBeanFactory and
+ * associated declarations will validate if compiled and used with the
+ * AutoBeanFactoyModel.
+ * <p>
+ * <span style='color: red'>This is experimental, unsupported code.</span>
+ */
+public class AutoBeanFactorySource {
+  /*
+   * NB: This implementation is excessively dynamic, however the inability to
+   * create a TypeOracle fram a ClassLoader prevents re-using the existing model
+   * code. If the model code could be reused, it would be straightforward to
+   * simply generate implementations of the various interfaces.
+   */
+  private static final AutoBeanFactory EMPTY = create(AutoBeanFactory.class);
+
+  /**
+   * Create an instance of an AutoBeanFactory.
+   * 
+   * @param <F> the factory type
+   * @param clazz the Class representing the factory interface
+   * @return an instance of the AutoBeanFactory
+   */
+  public static <F extends AutoBeanFactory> F create(Class<F> clazz) {
+    Configuration.Builder builder = new Configuration.Builder();
+    Category cat = clazz.getAnnotation(Category.class);
+    if (cat != null) {
+      builder.setCategories(cat.value());
+    }
+    NoWrap noWrap = clazz.getAnnotation(NoWrap.class);
+    if (noWrap != null) {
+      builder.setNoWrap(noWrap.value());
+    }
+
+    return ProxyAutoBean.makeProxy(clazz, new FactoryHandler(builder.build()), EnumMap.class);
+  }
+
+  /**
+   * Create an instance of an AutoBean directly.
+   * 
+   * @param <T> the interface type implemented by the AutoBean
+   * @param clazz the interface type implemented by the AutoBean
+   * @return an instance of an AutoBean
+   */
+  public static <T> AutoBean<T> createBean(Class<T> clazz, Configuration configuration) {
+    return new ProxyAutoBean<T>(EMPTY, clazz, configuration);
+  }
+}
diff --git a/user/src/com/google/web/bindery/autobean/vm/Configuration.java b/user/src/com/google/web/bindery/autobean/vm/Configuration.java
new file mode 100644
index 0000000..593ad52
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/vm/Configuration.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.vm;
+
+import com.google.web.bindery.autobean.shared.AutoBean;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Used by {@link AutoBeanFactorySource#createBean(Class, Configuration)}. This
+ * type replicates the annotations that may be applied to an AutoBeanFactory
+ * declaration.
+ * <p>
+ * <span style='color: red'>This is experimental, unsupported code.</span>
+ */
+public class Configuration {
+  /**
+   * Builds {@link Configuration} objects.
+   */
+  public static class Builder {
+    private Configuration toReturn = new Configuration();
+
+    public Configuration build() {
+      toReturn.noWrap.add(AutoBean.class);
+      toReturn.noWrap = Collections.unmodifiableSet(toReturn.noWrap);
+      try {
+        return toReturn;
+      } finally {
+        toReturn = null;
+      }
+    }
+
+    /**
+     * Equivalent to applying a
+     * {@link com.google.web.bindery.autobean.shared.AutoBeanFactory.Category
+     * Category} annotation to an AutoBeanFactory declaration.
+     * 
+     * @param categories the category types that should be searched for static
+     *          implementations of non-property methods
+     * @return the Builder
+     */
+    public Builder setCategories(Class<?>... categories) {
+      toReturn.categories =
+          Collections.unmodifiableList(new ArrayList<Class<?>>(Arrays.asList(categories)));
+      return this;
+    }
+
+    /**
+     * Equivalent to applying a
+     * {@link com.google.web.bindery.autobean.shared.AutoBeanFactory.NoWrap
+     * NoWrap} annotation to an AutoBeanFactory declaration.
+     * 
+     * @param noWrap the types that should be excluded from wrapping
+     * @return the Builder
+     */
+    public Builder setNoWrap(Class<?>... noWrap) {
+      toReturn.noWrap.addAll(Arrays.asList(noWrap));
+      return this;
+    }
+  }
+
+  private List<Class<?>> categories = Collections.emptyList();
+
+  private Set<Class<?>> noWrap = new HashSet<Class<?>>();
+
+  private Configuration() {
+  }
+
+  public List<Class<?>> getCategories() {
+    return categories;
+  }
+
+  public Set<Class<?>> getNoWrap() {
+    return noWrap;
+  }
+}
diff --git a/user/src/com/google/web/bindery/autobean/vm/impl/BeanMethod.java b/user/src/com/google/web/bindery/autobean/vm/impl/BeanMethod.java
new file mode 100644
index 0000000..f165239
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/vm/impl/BeanMethod.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.vm.impl;
+
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBean.PropertyName;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+/**
+ * Breakout of method types that an AutoBean shim interface can implement. The
+ * order of the values of the enum is important.
+ * 
+ * @see com.google.web.bindery.autobean.gwt.rebind.model.JBeanMethod
+ */
+public enum BeanMethod {
+  /**
+   * Methods defined in Object.
+   */
+  OBJECT {
+
+    @Override
+    public String inferName(Method method) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args) throws Throwable {
+      if (CALL.matches(handler, method)) {
+        return CALL.invoke(handler, method, args);
+      }
+      return method.invoke(handler, args);
+    }
+
+    @Override
+    boolean matches(SimpleBeanHandler<?> handler, Method method) {
+      return method.getDeclaringClass().equals(Object.class);
+    }
+  },
+  /**
+   * Getters.
+   */
+  GET {
+    @Override
+    public String inferName(Method method) {
+      String name = method.getName();
+      if (name.startsWith(IS_PREFIX) && !method.isAnnotationPresent(PropertyName.class)) {
+        Class<?> returnType = method.getReturnType();
+        if (Boolean.TYPE.equals(returnType) || Boolean.class.equals(returnType)) {
+          return decapitalize(name.substring(2));
+        }
+      }
+      return super.inferName(method);
+    }
+
+    @Override
+    Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args) {
+      String propertyName = inferName(method);
+      Object toReturn = handler.getBean().getOrReify(propertyName);
+      if (toReturn == null && method.getReturnType().isPrimitive()) {
+        toReturn = TypeUtils.getDefaultPrimitiveValue(method.getReturnType());
+      }
+      return toReturn;
+    }
+
+    @Override
+    boolean matches(SimpleBeanHandler<?> handler, Method method) {
+      Class<?> returnType = method.getReturnType();
+      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) {
+          return true;
+        }
+      }
+      return name.startsWith(GET_PREFIX) && name.length() > 3;
+    }
+  },
+  /**
+   * Setters.
+   */
+  SET {
+    @Override
+    Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args) {
+      handler.getBean().setProperty(inferName(method), args[0]);
+      return null;
+    }
+
+    @Override
+    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);
+    }
+  },
+  /**
+   * A setter that returns a type assignable from the interface in which the
+   * method is declared to support chained, builder-pattern setters. For
+   * example, {@code foo.setBar(1).setBaz(42)}.
+   */
+  SET_BUILDER {
+    @Override
+    Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args) {
+      ProxyAutoBean<?> bean = handler.getBean();
+      bean.setProperty(inferName(method), args[0]);
+      return bean.as();
+    }
+
+    @Override
+    boolean matches(SimpleBeanHandler<?> handler, Method method) {
+      String name = method.getName();
+      return name.startsWith(SET_PREFIX) && name.length() > 3
+          && method.getParameterTypes().length == 1
+          && method.getReturnType().isAssignableFrom(method.getDeclaringClass());
+    }
+  },
+  /**
+   * Domain methods.
+   */
+  CALL {
+    @Override
+    public String inferName(Method method) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args) throws Throwable {
+      if (args == null) {
+        args = EMPTY_OBJECT;
+      }
+
+      Method found = findMethod(handler, method);
+      if (found != null) {
+        Object[] realArgs = new Object[args.length + 1];
+        realArgs[0] = handler.getBean();
+        System.arraycopy(args, 0, realArgs, 1, args.length);
+        return found.invoke(null, realArgs);
+      }
+      throw new RuntimeException("Could not find category implementation of "
+          + method.toGenericString());
+    }
+
+    @Override
+    boolean matches(SimpleBeanHandler<?> handler, Method method) {
+      return handler.getBean().isWrapper()
+          || !handler.getBean().getConfiguration().getCategories().isEmpty()
+          && findMethod(handler, method) != null;
+    }
+  };
+
+  public static final String GET_PREFIX = "get";
+  public static final String HAS_PREFIX = "has";
+  public static final String IS_PREFIX = "is";
+  public static final String SET_PREFIX = "set";
+
+  private static final Object[] EMPTY_OBJECT = new Object[0];
+
+  static Method findMethod(SimpleBeanHandler<?> handler, Method method) {
+    Class<?>[] declaredParams = method.getParameterTypes();
+    Class<?>[] searchParams = new Class<?>[declaredParams.length + 1];
+    searchParams[0] = AutoBean.class;
+    System.arraycopy(declaredParams, 0, searchParams, 1, declaredParams.length);
+    Class<?> autoBeanType = handler.getBean().getType();
+
+    for (Class<?> clazz : handler.getBean().getConfiguration().getCategories()) {
+      try {
+        Method found = clazz.getMethod(method.getName(), searchParams);
+        if (!Modifier.isStatic(found.getModifiers())) {
+          continue;
+        }
+        // Check the AutoBean parameterization of the 0th argument
+        Class<?> foundAutoBean =
+            TypeUtils.ensureBaseType(TypeUtils.getSingleParameterization(AutoBean.class, found
+                .getGenericParameterTypes()[0]));
+        if (!foundAutoBean.isAssignableFrom(autoBeanType)) {
+          continue;
+        }
+        return found;
+      } catch (NoSuchMethodException expected) {
+      } catch (IllegalArgumentException e) {
+        throw new RuntimeException(e);
+      }
+    }
+    return null;
+  }
+
+  /**
+   * 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) {
+      return null;
+    }
+    int length = name.length();
+    if (length == 0 || (length > 1 && Character.isUpperCase(name.charAt(1)))) {
+      return name;
+    }
+    StringBuilder sb = new StringBuilder(length);
+    sb.append(Character.toLowerCase(name.charAt(0)));
+    sb.append(name.substring(1));
+    return sb.toString();
+  }
+
+  public String inferName(Method method) {
+    PropertyName prop = method.getAnnotation(PropertyName.class);
+    if (prop != null) {
+      return prop.value();
+    }
+    return decapitalize(method.getName().substring(3));
+  }
+
+  /**
+   * Convenience method, not valid for {@link BeanMethod#CALL}.
+   */
+  public boolean matches(Method method) {
+    return matches(null, method);
+  }
+
+  /**
+   * Invoke the method.
+   */
+  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);
+}
diff --git a/user/src/com/google/web/bindery/autobean/vm/impl/BeanPropertyContext.java b/user/src/com/google/web/bindery/autobean/vm/impl/BeanPropertyContext.java
new file mode 100644
index 0000000..a4fe31f
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/vm/impl/BeanPropertyContext.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.vm.impl;
+
+import java.lang.reflect.Method;
+
+/**
+ * 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;
+
+  public BeanPropertyContext(ProxyAutoBean<?> bean, Method getter) {
+    super(getter);
+    this.bean = bean;
+    propertyName = BeanMethod.GET.inferName(getter);
+  }
+
+  @Override
+  public boolean canSet() {
+    return true;
+  }
+
+  @Override
+  public void set(Object value) {
+    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));
+  }
+}
diff --git a/user/src/com/google/web/bindery/autobean/vm/impl/FactoryHandler.java b/user/src/com/google/web/bindery/autobean/vm/impl/FactoryHandler.java
new file mode 100644
index 0000000..8532f41
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/vm/impl/FactoryHandler.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.vm.impl;
+
+import com.google.web.bindery.autobean.shared.AutoBean.PropertyName;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory;
+import com.google.web.bindery.autobean.shared.AutoBeanUtils;
+import com.google.web.bindery.autobean.vm.Configuration;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+
+/**
+ * Handles dispatches on AutoBeanFactory interfaces.
+ */
+public class FactoryHandler implements InvocationHandler {
+  private final Configuration configuration;
+
+  /**
+   * Constructor.
+   * 
+   * @param categories the classes specified by a Category annotation
+   */
+  public FactoryHandler(Configuration configuration) {
+    this.configuration = configuration;
+  }
+
+  /**
+   * Handles both declared factory methods as well as the dynamic create
+   * methods.
+   */
+  public Object invoke(Object proxy, Method method, Object[] args)
+      throws Throwable {
+
+    Class<?> beanType;
+    Object toWrap = null;
+    String name = method.getName();
+    if (name.equals("create")) {
+      // Dynamic create. Guaranteed to have at least one argument
+      // create(clazz); or create(clazz, toWrap);
+      beanType = (Class<?>) args[0];
+      if (args.length == 2) {
+        toWrap = args[1];
+      }
+    } else if (name.equals("getEnum")) {
+      Class<?> clazz = (Class<?>) args[0];
+      String token = (String) args[1];
+      return getEnum(clazz, token);
+    } else if (name.equals("getToken")) {
+      Enum<?> e = (Enum<?>) args[0];
+      return getToken(e);
+    } else {
+      // Declared factory method, use the parameterization
+      // AutoBean<Foo> foo(); or Autobean<foo> foo(Foo toWrap);
+      ParameterizedType returnType = (ParameterizedType) method.getGenericReturnType();
+      beanType = (Class<?>) returnType.getActualTypeArguments()[0];
+
+      if (args != null && args.length == 1) {
+        toWrap = args[0];
+      }
+    }
+
+    // Return any existing wrapper
+    ProxyAutoBean<Object> toReturn = (ProxyAutoBean<Object>) AutoBeanUtils.getAutoBean(toWrap);
+    if (toReturn == null) {
+      // Create the implementation bean
+      if (toWrap == null) {
+        toReturn = new ProxyAutoBean<Object>((AutoBeanFactory) proxy, beanType,
+            configuration);
+      } else {
+        toReturn = new ProxyAutoBean<Object>((AutoBeanFactory) proxy, beanType,
+            configuration, toWrap);
+      }
+    }
+
+    return toReturn;
+  }
+
+  /**
+   * EnumMap support.
+   */
+  private Object getEnum(Class<?> clazz, String token)
+      throws IllegalAccessException {
+    for (Field f : clazz.getFields()) {
+      String fieldName;
+      PropertyName annotation = f.getAnnotation(PropertyName.class);
+      if (annotation != null) {
+        fieldName = annotation.value();
+      } else {
+        fieldName = f.getName();
+      }
+      if (token.equals(fieldName)) {
+        f.setAccessible(true);
+        return f.get(null);
+      }
+    }
+    throw new IllegalArgumentException("Cannot find enum " + token
+        + " in type " + clazz.getCanonicalName());
+  }
+
+  /**
+   * EnumMap support.
+   */
+  private Object getToken(Enum<?> e) throws NoSuchFieldException {
+    // Remember enum constants are fields
+    PropertyName annotation = e.getDeclaringClass().getField(e.name()).getAnnotation(
+        PropertyName.class);
+    if (annotation != null) {
+      return annotation.value();
+    } else {
+      return e.name();
+    }
+  }
+}
diff --git a/user/src/com/google/web/bindery/autobean/vm/impl/GetterPropertyContext.java b/user/src/com/google/web/bindery/autobean/vm/impl/GetterPropertyContext.java
new file mode 100644
index 0000000..d00b8ed
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/vm/impl/GetterPropertyContext.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.vm.impl;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Used by {@link ProxyAutoBean#traverseProperties()}.
+ */
+class GetterPropertyContext extends MethodPropertyContext {
+  private final Method setter;
+  private final Object shim;
+
+  GetterPropertyContext(ProxyAutoBean<?> bean, Method getter) {
+    super(getter);
+    this.shim = bean.as();
+
+    // Look for the setter method.
+    Method found = null;
+    String name = BeanMethod.GET.inferName(getter);
+    for (Method m : getter.getDeclaringClass().getMethods()) {
+      if (BeanMethod.SET.matches(m) || BeanMethod.SET_BUILDER.matches(m)) {
+        if (BeanMethod.SET.inferName(m).equals(name)
+            && getter.getReturnType().isAssignableFrom(m.getParameterTypes()[0])) {
+          found = m;
+          break;
+        }
+      }
+    }
+    setter = found;
+  }
+
+  @Override
+  public boolean canSet() {
+    return setter != null;
+  }
+
+  @Override
+  public void set(Object value) {
+    if (!canSet()) {
+      throw new UnsupportedOperationException("No setter");
+    }
+    try {
+      setter.setAccessible(true);
+      setter.invoke(shim, value);
+    } catch (IllegalArgumentException e) {
+      throw new RuntimeException(e);
+    } catch (IllegalAccessException e) {
+      throw new RuntimeException(e);
+    } catch (InvocationTargetException e) {
+      throw new RuntimeException(e.getCause());
+    }
+  }
+}
diff --git a/user/src/com/google/web/bindery/autobean/vm/impl/JsonSplittable.java b/user/src/com/google/web/bindery/autobean/vm/impl/JsonSplittable.java
new file mode 100644
index 0000000..8fdad79
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/vm/impl/JsonSplittable.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.vm.impl;
+
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.shared.impl.HasSplittable;
+import com.google.web.bindery.autobean.shared.impl.StringQuoter;
+
+import 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 static Splittable create(String payload) {
+    try {
+      switch (payload.charAt(0)) {
+        case '{':
+          return new JsonSplittable(new JSONObject(payload));
+        case '[':
+          return new JsonSplittable(new JSONArray(payload));
+        case '"':
+          return new JsonSplittable(new JSONArray("[" + payload + "]").getString(0));
+        case '-':
+        case '0':
+        case '1':
+        case '2':
+        case '3':
+        case '4':
+        case '5':
+        case '6':
+        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;
+        default:
+          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 static String[] getNames(JSONObject json) {
+    int length = json.length();
+    if (length == 0) {
+      return null;
+    }
+    String[] names = new String[length];
+    Iterator<?> i = json.keys();
+    int j = 0;
+    while (i.hasNext()) {
+      names[j++] = (String) i.next();
+    }
+    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 JsonSplittable(JSONArray array) {
+    this.array = array;
+  }
+
+  private JsonSplittable(JSONObject obj) {
+    this.obj = obj;
+  }
+
+  private JsonSplittable(String string) {
+    this.array = null;
+    this.obj = null;
+    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));
+    } catch (JSONException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public Splittable get(String key) {
+    try {
+      return makeSplittable(obj.get(key));
+    } catch (JSONException e) {
+      throw new RuntimeException(key, e);
+    }
+  }
+
+  public String getPayload() {
+    if (isNull) {
+      return "null";
+    }
+    if (obj != null) {
+      return obj.toString();
+    }
+    if (array != null) {
+      return array.toString();
+    }
+    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");
+  }
+
+  public List<String> getPropertyKeys() {
+    String[] names = getNames(obj);
+    if (names == null) {
+      return Collections.emptyList();
+    } else {
+      return Collections.unmodifiableList(Arrays.asList(names));
+    }
+  }
+
+  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;
+  }
+
+  public boolean isKeyed() {
+    return obj != null;
+  }
+
+  public boolean isNull(int index) {
+    return array.isNull(index);
+  }
+
+  public boolean isNull(String key) {
+    // Treat undefined and null as the same
+    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();
+  }
+
+  /**
+   * For debugging use only.
+   */
+  @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 string;
+    }
+    if (number != null) {
+      return number;
+    }
+    if (bool != null) {
+      return bool;
+    }
+    throw new RuntimeException("No data");
+  }
+}
diff --git a/user/src/com/google/web/bindery/autobean/vm/impl/MethodPropertyContext.java b/user/src/com/google/web/bindery/autobean/vm/impl/MethodPropertyContext.java
new file mode 100644
index 0000000..10ce579
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/vm/impl/MethodPropertyContext.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.vm.impl;
+
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor.CollectionPropertyContext;
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor.MapPropertyContext;
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor.ParameterizationVisitor;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+/**
+ * A base type to handle analyzing the return value of a getter method. The
+ * accessor methods are implemented in subtypes.
+ */
+abstract class MethodPropertyContext implements CollectionPropertyContext,
+    MapPropertyContext {
+  private static class Data {
+    Class<?> elementType;
+    Type genericType;
+    Class<?> keyType;
+    Class<?> valueType;
+    Class<?> type;
+  }
+
+  /**
+   * Save prior instances in order to decrease the amount of data computed.
+   */
+  private static final Map<Method, Data> cache = new WeakHashMap<Method, Data>();
+  private final Data data;
+
+  public MethodPropertyContext(Method getter) {
+    synchronized (cache) {
+      Data previous = cache.get(getter);
+      if (previous != null) {
+        this.data = previous;
+        return;
+      }
+
+      this.data = new Data();
+      data.genericType = getter.getGenericReturnType();
+      data.type = getter.getReturnType();
+      // Compute collection element type
+      if (Collection.class.isAssignableFrom(getType())) {
+        data.elementType = TypeUtils.ensureBaseType(TypeUtils.getSingleParameterization(
+            Collection.class, getter.getGenericReturnType(),
+            getter.getReturnType()));
+      } else if (Map.class.isAssignableFrom(getType())) {
+        Type[] types = TypeUtils.getParameterization(Map.class,
+            getter.getGenericReturnType());
+        data.keyType = TypeUtils.ensureBaseType(types[0]);
+        data.valueType = TypeUtils.ensureBaseType(types[1]);
+      }
+      cache.put(getter, data);
+    }
+  }
+
+  public void accept(ParameterizationVisitor visitor) {
+    traverse(visitor, data.genericType);
+  }
+
+  public abstract boolean canSet();
+
+  public Class<?> getElementType() {
+    return data.elementType;
+  }
+
+  public Class<?> getKeyType() {
+    return data.keyType;
+  }
+
+  public Class<?> getType() {
+    return data.type;
+  }
+
+  public Class<?> getValueType() {
+    return data.valueType;
+  }
+
+  public abstract void set(Object value);
+
+  private void traverse(ParameterizationVisitor visitor, Type type) {
+    Class<?> base = TypeUtils.ensureBaseType(type);
+    if (visitor.visitType(base)) {
+      Type[] params = TypeUtils.getParameterization(base, type);
+      for (Type t : params) {
+        if (visitor.visitParameter()) {
+          traverse(visitor, t);
+        }
+        visitor.endVisitParameter();
+      }
+    }
+    visitor.endVisitType(base);
+  }
+}
diff --git a/user/src/com/google/web/bindery/autobean/vm/impl/ProxyAutoBean.java b/user/src/com/google/web/bindery/autobean/vm/impl/ProxyAutoBean.java
new file mode 100644
index 0000000..cdf61dc
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/vm/impl/ProxyAutoBean.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.vm.impl;
+
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory;
+import com.google.web.bindery.autobean.shared.AutoBeanUtils;
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor;
+import com.google.web.bindery.autobean.shared.impl.AbstractAutoBean;
+import com.google.web.bindery.autobean.vm.Configuration;
+import com.google.gwt.core.client.impl.WeakMapping;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+/**
+ * An implementation of an AutoBean that uses reflection.
+ * 
+ * @param <T> the type of interface being wrapped
+ */
+public class ProxyAutoBean<T> extends AbstractAutoBean<T> {
+  private static class Data {
+    final List<Method> getters = new ArrayList<Method>();
+    final List<String> getterNames = new ArrayList<String>();
+    final List<PropertyType> propertyType = new ArrayList<PropertyType>();
+  }
+
+  private enum PropertyType {
+    VALUE, REFERENCE, COLLECTION, MAP;
+  }
+
+  private static final Map<Class<?>, Data> cache = new WeakHashMap<Class<?>, Data>();
+
+  /**
+   * Utility method to crete a new {@link Proxy} instance.
+   * 
+   * @param <T> the interface type to be implemented by the Proxy
+   * @param intf the Class representing the interface type
+   * @param handler the implementation of the interface
+   * @param extraInterfaces additional interface types the Proxy should
+   *          implement
+   * @return a Proxy instance
+   */
+  public static <T> T makeProxy(Class<T> intf, InvocationHandler handler,
+      Class<?>... extraInterfaces) {
+    Class<?>[] intfs;
+    if (extraInterfaces == null) {
+      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));
+  }
+
+  private static Data calculateData(Class<?> beanType) {
+    Data toReturn;
+    synchronized (cache) {
+      toReturn = cache.get(beanType);
+      if (toReturn == null) {
+        toReturn = new Data();
+        for (Method method : beanType.getMethods()) {
+          if (BeanMethod.GET.matches(method)) {
+            toReturn.getters.add(method);
+
+            String name;
+            PropertyName annotation = method.getAnnotation(PropertyName.class);
+            if (annotation != null) {
+              name = annotation.value();
+            } else {
+              name = BeanMethod.GET.inferName(method);
+            }
+            toReturn.getterNames.add(name);
+
+            Class<?> returnType = method.getReturnType();
+            if (TypeUtils.isValueType(returnType)) {
+              toReturn.propertyType.add(PropertyType.VALUE);
+            } else if (Collection.class.isAssignableFrom(returnType)) {
+              toReturn.propertyType.add(PropertyType.COLLECTION);
+            } else if (Map.class.isAssignableFrom(returnType)) {
+              toReturn.propertyType.add(PropertyType.MAP);
+            } else {
+              toReturn.propertyType.add(PropertyType.REFERENCE);
+            }
+          }
+        }
+        cache.put(beanType, toReturn);
+      }
+    }
+    return toReturn;
+  }
+
+  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) {
+    super(factory);
+    this.beanType = (Class<T>) beanType;
+    this.configuration = configuration;
+    this.data = calculateData(beanType);
+    this.shim = createShim();
+  }
+
+  @SuppressWarnings("unchecked")
+  public ProxyAutoBean(AutoBeanFactory factory, Class<?> beanType, Configuration configuration,
+      T toWrap) {
+    super(toWrap, factory);
+    this.beanType = (Class<T>) beanType;
+    this.configuration = configuration;
+    this.data = calculateData(beanType);
+    this.shim = createShim();
+  }
+
+  @Override
+  public T as() {
+    return shim;
+  }
+
+  public Configuration getConfiguration() {
+    return configuration;
+  }
+
+  public Class<T> getType() {
+    return beanType;
+  }
+
+  /**
+   * Allow access by {@link ShimHandler}.
+   */
+  @Override
+  protected void call(String method, Object returned, Object... parameters) {
+    super.call(method, returned, parameters);
+  }
+
+  /**
+   * Allow access by {@link ShimHandler}.
+   */
+  @Override
+  protected void checkFrozen() {
+    super.checkFrozen();
+  }
+
+  /**
+   * Allow access by {@link ShimHandler}.
+   */
+  @Override
+  protected void checkWrapped() {
+    super.checkWrapped();
+  }
+
+  /**
+   * Not used in this implementation. Instead, the simple implementation is
+   * created lazily in {@link #getWrapped()}.
+   */
+  @Override
+  protected T createSimplePeer() {
+    return null;
+  }
+
+  /**
+   * Allow access by {@link ShimHandler}.
+   */
+  @Override
+  protected <V> V get(String method, V toReturn) {
+    return super.get(method, toReturn);
+  }
+
+  /**
+   * Allow access by BeanMethod.
+   */
+  @Override
+  protected <V> V getOrReify(String propertyName) {
+    return super.<V> getOrReify(propertyName);
+  }
+
+  /**
+   * Allow access by {@link ShimHandler}.
+   */
+  @Override
+  protected T getWrapped() {
+    if (wrapped == null && isUsingSimplePeer()) {
+      wrapped = (T) ProxyAutoBean.makeProxy(beanType, new SimpleBeanHandler<T>(this));
+    }
+    return super.getWrapped();
+  }
+
+  /**
+   * Allow access by {@link ShimHandler}.
+   */
+  @Override
+  protected void set(String method, Object value) {
+    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) {
+    assert data.getters.size() == data.getterNames.size()
+        && data.getters.size() == data.propertyType.size();
+    Iterator<Method> getterIt = data.getters.iterator();
+    Iterator<String> nameIt = data.getterNames.iterator();
+    Iterator<PropertyType> typeIt = data.propertyType.iterator();
+    while (getterIt.hasNext()) {
+      Method getter = getterIt.next();
+      String name = nameIt.next();
+      PropertyType propertyType = typeIt.next();
+
+      // Use the shim to handle automatic wrapping
+      Object value;
+      try {
+        getter.setAccessible(true);
+        value = getter.invoke(shim);
+      } catch (IllegalArgumentException e) {
+        throw new RuntimeException(e);
+      } catch (IllegalAccessException e) {
+        throw new RuntimeException(e);
+      } catch (InvocationTargetException e) {
+        throw new RuntimeException(e.getCause());
+      }
+
+      // Create the context used for the property visitation
+      MethodPropertyContext x =
+          isUsingSimplePeer() ? new BeanPropertyContext(this, getter) : new GetterPropertyContext(
+              this, getter);
+
+      switch (propertyType) {
+        case VALUE: {
+          if (visitor.visitValueProperty(name, value, x)) {
+          }
+          visitor.endVisitValueProperty(name, value, x);
+          break;
+        }
+        case COLLECTION: {
+          // Workaround for generics bug in mac javac 1.6.0_22
+          @SuppressWarnings("rawtypes")
+          AutoBean temp = AutoBeanUtils.getAutoBean((Collection) value);
+          @SuppressWarnings("unchecked")
+          AutoBean<Collection<?>> bean = (AutoBean<Collection<?>>) temp;
+          if (visitor.visitCollectionProperty(name, bean, x)) {
+            if (value != null) {
+              ((ProxyAutoBean<?>) bean).traverse(visitor, ctx);
+            }
+          }
+          visitor.endVisitCollectionProperty(name, bean, x);
+          break;
+        }
+        case MAP: {
+          // Workaround for generics bug in mac javac 1.6.0_22
+          @SuppressWarnings("rawtypes")
+          AutoBean temp = AutoBeanUtils.getAutoBean((Map) value);
+          @SuppressWarnings("unchecked")
+          AutoBean<Map<?, ?>> bean = (AutoBean<Map<?, ?>>) temp;
+          if (visitor.visitMapProperty(name, bean, x)) {
+            if (value != null) {
+              ((ProxyAutoBean<?>) bean).traverse(visitor, ctx);
+            }
+          }
+          visitor.endVisitMapProperty(name, bean, x);
+          break;
+        }
+        case REFERENCE: {
+          ProxyAutoBean<?> bean = (ProxyAutoBean<?>) AutoBeanUtils.getAutoBean(value);
+          if (visitor.visitReferenceProperty(name, bean, x)) {
+            if (value != null) {
+              bean.traverse(visitor, ctx);
+            }
+          }
+          visitor.endVisitReferenceProperty(name, bean, x);
+          break;
+        }
+      }
+    }
+  }
+
+  Class<?> getBeanType() {
+    return beanType;
+  }
+
+  private T createShim() {
+    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/web/bindery/autobean/vm/impl/ShimHandler.java b/user/src/com/google/web/bindery/autobean/vm/impl/ShimHandler.java
new file mode 100644
index 0000000..95dabe8
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/vm/impl/ShimHandler.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.vm.impl;
+
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanUtils;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+/**
+ * Implements an AutoBean's shim interface that intercepts calls to the backing
+ * object.
+ * 
+ * @param <T> the interface type of the AutoBean
+ */
+class ShimHandler<T> implements InvocationHandler {
+  private final ProxyAutoBean<T> bean;
+  private final Method interceptor;
+
+  public ShimHandler(ProxyAutoBean<T> bean, T toWrap) {
+    this.bean = bean;
+
+    Method maybe = null;
+    for (Class<?> clazz : bean.getConfiguration().getCategories()) {
+      try {
+        maybe = clazz.getMethod("__intercept", AutoBean.class, Object.class);
+        break;
+      } catch (SecurityException expected) {
+      } catch (NoSuchMethodException expected) {
+      }
+    }
+    interceptor = maybe;
+  }
+
+  @Override
+  public boolean equals(Object couldBeShim) {
+    if (couldBeShim == null) {
+      return false;
+    }
+    // Handles the foo.equals(foo) case
+    if (Proxy.isProxyClass(couldBeShim.getClass())
+        && this == Proxy.getInvocationHandler(couldBeShim)) {
+      return true;
+    }
+    return bean.getWrapped().equals(couldBeShim);
+  }
+
+  @Override
+  public int hashCode() {
+    return bean.getWrapped().hashCode();
+  }
+
+  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+    method.setAccessible(true);
+    Object toReturn;
+    String name = method.getName();
+    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();
+    }
+    return toReturn;
+  }
+
+  @Override
+  public String toString() {
+    return bean.getWrapped().toString();
+  }
+
+  private Object maybeWrap(Class<?> intf, Object toReturn) {
+    if (toReturn == null) {
+      return null;
+    }
+    AutoBean<?> returnBean = AutoBeanUtils.getAutoBean(toReturn);
+    if (returnBean != null) {
+      return returnBean.as();
+    }
+    if (TypeUtils.isValueType(intf) || TypeUtils.isValueType(toReturn.getClass())
+        || bean.getConfiguration().getNoWrap().contains(intf)) {
+      return toReturn;
+    }
+    if (toReturn.getClass().isArray()) {
+      /*
+       * We can't reliably wrap arrays, but the only time we typically see an
+       * array is with toArray() call on a collection, since arrays aren't
+       * supported property types.
+       */
+      return toReturn;
+    }
+    ProxyAutoBean<Object> newBean =
+        new ProxyAutoBean<Object>(bean.getFactory(), intf, bean.getConfiguration(), toReturn);
+    return newBean.as();
+  }
+}
diff --git a/user/src/com/google/web/bindery/autobean/vm/impl/SimpleBeanHandler.java b/user/src/com/google/web/bindery/autobean/vm/impl/SimpleBeanHandler.java
new file mode 100644
index 0000000..a6624a6
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/vm/impl/SimpleBeanHandler.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.vm.impl;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+
+/**
+ * Dynamic implementation of an AutoBean's simple peer object.
+ * 
+ * @param <T> the type of interface the shim allows access to
+ */
+class SimpleBeanHandler<T> implements InvocationHandler {
+  private final ProxyAutoBean<T> bean;
+
+  public SimpleBeanHandler(ProxyAutoBean<T> bean) {
+    this.bean = bean;
+  }
+
+  public ProxyAutoBean<T> getBean() {
+    return bean;
+  }
+
+  /**
+   * Delegates most work to {@link BeanMethod}.
+   */
+  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);
+        return toReturn;
+      }
+    }
+    throw new RuntimeException("Unhandled invocation " + method.getName());
+  }
+  
+  /**
+   * For debugging use only.
+   */
+  @Override
+  public String toString() {
+    return bean.getSplittable().getPayload();
+  }
+}
diff --git a/user/src/com/google/web/bindery/autobean/vm/impl/TypeUtils.java b/user/src/com/google/web/bindery/autobean/vm/impl/TypeUtils.java
new file mode 100644
index 0000000..73b977e
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/vm/impl/TypeUtils.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.vm.impl;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Shared code for answering question about Class objects. This is a
+ * server-compatible analog to ModelUtils.
+ */
+public class TypeUtils {
+  static final Map<Class<?>, Class<?>> AUTOBOX_MAP;
+  static final Map<Class<?>, Object> DEFAULT_PRIMITIVE_VALUES;
+  @SuppressWarnings("unchecked")
+  static final Set<Class<?>> VALUE_TYPES = Collections.unmodifiableSet(new HashSet<Class<?>>(
+      Arrays.asList(Boolean.class, Character.class, Class.class, Date.class,
+          Enum.class, Number.class, String.class, Void.class)));
+
+  static {
+    Map<Class<?>, Object> temp = new HashMap<Class<?>, Object>();
+    temp.put(boolean.class, false);
+    temp.put(byte.class, (byte) 0);
+    temp.put(char.class, (char) 0);
+    temp.put(double.class, (double) 0);
+    temp.put(float.class, (float) 0);
+    temp.put(int.class, 0);
+    temp.put(long.class, (long) 0);
+    temp.put(short.class, (short) 0);
+    temp.put(void.class, null);
+
+    DEFAULT_PRIMITIVE_VALUES = Collections.unmodifiableMap(temp);
+  }
+
+  static {
+    Map<Class<?>, Class<?>> autoBoxMap = new HashMap<Class<?>, Class<?>>();
+    autoBoxMap.put(boolean.class, Boolean.class);
+    autoBoxMap.put(byte.class, Byte.class);
+    autoBoxMap.put(char.class, Character.class);
+    autoBoxMap.put(double.class, Double.class);
+    autoBoxMap.put(float.class, Float.class);
+    autoBoxMap.put(int.class, Integer.class);
+    autoBoxMap.put(long.class, Long.class);
+    autoBoxMap.put(short.class, Short.class);
+    autoBoxMap.put(void.class, Void.class);
+    AUTOBOX_MAP = Collections.unmodifiableMap(autoBoxMap);
+  }
+
+  /**
+   * Similar to ModelUtils#ensureBaseType(JType) but for the reflection API.
+   */
+  public static Class<?> ensureBaseType(Type type) {
+    if (type instanceof Class<?>) {
+      return (Class<?>) type;
+    }
+    if (type instanceof GenericArrayType) {
+      return Array.newInstance(
+          ensureBaseType(((GenericArrayType) type).getGenericComponentType()),
+          0).getClass();
+    }
+    if (type instanceof ParameterizedType) {
+      return ensureBaseType(((ParameterizedType) type).getRawType());
+    }
+    if (type instanceof TypeVariable<?>) {
+      return ensureBaseType(((TypeVariable<?>) type).getBounds()[0]);
+    }
+    if (type instanceof WildcardType) {
+      WildcardType wild = (WildcardType) type;
+      return ensureBaseType(wild.getUpperBounds()[0]);
+    }
+    throw new RuntimeException("Cannot handle " + type.getClass().getName());
+  }
+
+  /**
+   * Given a primitive Class type, return a default value.
+   */
+  public static Object getDefaultPrimitiveValue(Class<?> clazz) {
+    assert clazz.isPrimitive() : "Expecting primitive type";
+    return DEFAULT_PRIMITIVE_VALUES.get(clazz);
+  }
+
+  public static Type[] getParameterization(Class<?> intf, Type... types) {
+    for (Type type : types) {
+      if (type == null) {
+        continue;
+      } else if (type instanceof ParameterizedType) {
+        ParameterizedType param = (ParameterizedType) type;
+        Type[] actualTypeArguments = param.getActualTypeArguments();
+        Class<?> base = ensureBaseType(param.getRawType());
+        Type[] typeParameters = base.getTypeParameters();
+
+        Map<Type, Type> map = new HashMap<Type, Type>();
+        for (int i = 0, j = typeParameters.length; i < j; i++) {
+          map.put(typeParameters[i], actualTypeArguments[i]);
+        }
+        Type[] lookFor = intf.equals(base) ? intf.getTypeParameters()
+            : getParameterization(intf, base.getGenericInterfaces());
+        List<Type> toReturn = new ArrayList<Type>();
+        for (int i = 0, j = lookFor.length; i < j; i++) {
+          Type found = map.get(lookFor[i]);
+          if (found != null) {
+            toReturn.add(found);
+          }
+        }
+        return toReturn.toArray(new Type[toReturn.size()]);
+      } else if (type instanceof Class<?>) {
+        Class<?> clazz = (Class<?>) type;
+        if (intf.equals(clazz)) {
+          return intf.getTypeParameters();
+        }
+        Type[] found = getParameterization(intf, clazz.getGenericSuperclass());
+        if (found != null) {
+          return found;
+        }
+        found = getParameterization(intf, clazz.getGenericInterfaces());
+        if (found != null) {
+          return found;
+        }
+      }
+    }
+    return null;
+  }
+
+  public static Type getSingleParameterization(Class<?> intf, Type... types) {
+    Type[] found = getParameterization(intf, types);
+    return found == null ? null : found[0];
+  }
+
+  public static boolean isValueType(Class<?> clazz) {
+    if (clazz.isPrimitive() || VALUE_TYPES.contains(clazz)) {
+      return true;
+    }
+    for (Class<?> c : VALUE_TYPES) {
+      if (c.isAssignableFrom(clazz)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  public static <V> Class<V> maybeAutobox(Class<V> domainType) {
+    @SuppressWarnings("unchecked")
+    Class<V> autoBoxType = (Class<V>) AUTOBOX_MAP.get(domainType);
+    return autoBoxType == null ? domainType : autoBoxType;
+  }
+
+  private TypeUtils() {
+  }
+}
diff --git a/user/src/com/google/web/bindery/autobean/vm/package-info.java b/user/src/com/google/web/bindery/autobean/vm/package-info.java
new file mode 100644
index 0000000..ef1a6ee
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/vm/package-info.java
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+/**
+ * Contains JVM-compatible implementations of the AutoBean framework.
+ * <p>
+ * <span style='color: red'>This is experimental, unsupported code.</span>
+ * 
+ * @see <a
+ *      href="http://code.google.com/p/google-web-toolkit/wiki/AutoBean">AutoBean
+ *      wiki page</a>
+ */
+@com.google.gwt.util.PreventSpuriousRebuilds
+package com.google.web.bindery.autobean.vm;
+
diff --git a/user/src/com/google/web/bindery/requestfactory/RequestFactory.gwt.xml b/user/src/com/google/web/bindery/requestfactory/RequestFactory.gwt.xml
index 279acc4..f55cc8d 100644
--- a/user/src/com/google/web/bindery/requestfactory/RequestFactory.gwt.xml
+++ b/user/src/com/google/web/bindery/requestfactory/RequestFactory.gwt.xml
@@ -17,7 +17,7 @@
 -->
 <module>
   <inherits name='com.google.gwt.core.Core'/>
-  <inherits name='com.google.gwt.autobean.AutoBean'/>
+  <inherits name='com.google.web.bindery.autobean.AutoBean'/>
   <inherits name='com.google.gwt.editor.Editor'/>
   <inherits name='com.google.gwt.http.HTTP'/>
   <inherits name='com.google.gwt.logging.LoggingDisabled'/>
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/client/impl/AbstractRequestFactoryEditorDriver.java b/user/src/com/google/web/bindery/requestfactory/gwt/client/impl/AbstractRequestFactoryEditorDriver.java
index a4c10dd..df21135 100644
--- a/user/src/com/google/web/bindery/requestfactory/gwt/client/impl/AbstractRequestFactoryEditorDriver.java
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/client/impl/AbstractRequestFactoryEditorDriver.java
@@ -15,8 +15,8 @@
  */
 package com.google.web.bindery.requestfactory.gwt.client.impl;
 
-import com.google.gwt.autobean.shared.AutoBean;
-import com.google.gwt.autobean.shared.AutoBeanUtils;
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanUtils;
 import com.google.gwt.editor.client.Editor;
 import com.google.gwt.editor.client.EditorContext;
 import com.google.gwt.editor.client.EditorVisitor;
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/client/impl/PathCollector.java b/user/src/com/google/web/bindery/requestfactory/gwt/client/impl/PathCollector.java
index e14d3b2..ab4f8aa 100644
--- a/user/src/com/google/web/bindery/requestfactory/gwt/client/impl/PathCollector.java
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/client/impl/PathCollector.java
@@ -15,7 +15,7 @@
  */
 package com.google.web.bindery.requestfactory.gwt.client.impl;
 
-import com.google.gwt.autobean.shared.ValueCodex;
+import com.google.web.bindery.autobean.shared.ValueCodex;
 import com.google.gwt.editor.client.EditorContext;
 import com.google.gwt.editor.client.EditorVisitor;
 
@@ -62,4 +62,4 @@
     }
     return true;
   }
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/rebind/RequestFactoryGenerator.java b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/RequestFactoryGenerator.java
index 834b5c0..e6d7f5c 100644
--- a/user/src/com/google/web/bindery/requestfactory/gwt/rebind/RequestFactoryGenerator.java
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/RequestFactoryGenerator.java
@@ -15,13 +15,13 @@
  */
 package com.google.web.bindery.requestfactory.gwt.rebind;
 
-import com.google.gwt.autobean.rebind.model.JBeanMethod;
-import com.google.gwt.autobean.shared.AutoBean;
-import com.google.gwt.autobean.shared.AutoBean.PropertyName;
-import com.google.gwt.autobean.shared.AutoBeanFactory;
-import com.google.gwt.autobean.shared.AutoBeanFactory.Category;
-import com.google.gwt.autobean.shared.AutoBeanFactory.NoWrap;
-import com.google.gwt.autobean.shared.impl.EnumMap.ExtraEnums;
+import com.google.web.bindery.autobean.gwt.rebind.model.JBeanMethod;
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBean.PropertyName;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory.Category;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory.NoWrap;
+import com.google.web.bindery.autobean.shared.impl.EnumMap.ExtraEnums;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.ext.Generator;
 import com.google.gwt.core.ext.GeneratorContext;
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/RequestFactoryModel.java b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/RequestFactoryModel.java
index ef1d7e8..6af1dbb 100644
--- a/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/RequestFactoryModel.java
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/RequestFactoryModel.java
@@ -15,8 +15,8 @@
  */
 package com.google.web.bindery.requestfactory.gwt.rebind.model;
 
-import com.google.gwt.autobean.rebind.model.JBeanMethod;
-import com.google.gwt.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.gwt.rebind.model.JBeanMethod;
+import com.google.web.bindery.autobean.shared.Splittable;
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.JClassType;
diff --git a/user/src/com/google/web/bindery/requestfactory/server/ReflectiveServiceLayer.java b/user/src/com/google/web/bindery/requestfactory/server/ReflectiveServiceLayer.java
index f25e0ae..3433518 100644
--- a/user/src/com/google/web/bindery/requestfactory/server/ReflectiveServiceLayer.java
+++ b/user/src/com/google/web/bindery/requestfactory/server/ReflectiveServiceLayer.java
@@ -15,9 +15,9 @@
  */
 package com.google.web.bindery.requestfactory.server;
 
-import com.google.gwt.autobean.server.impl.BeanMethod;
-import com.google.gwt.autobean.server.impl.TypeUtils;
-import com.google.gwt.autobean.shared.ValueCodex;
+import com.google.web.bindery.autobean.shared.ValueCodex;
+import com.google.web.bindery.autobean.vm.impl.BeanMethod;
+import com.google.web.bindery.autobean.vm.impl.TypeUtils;
 import com.google.web.bindery.requestfactory.shared.BaseProxy;
 import com.google.web.bindery.requestfactory.shared.InstanceRequest;
 import com.google.web.bindery.requestfactory.shared.Request;
diff --git a/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidator.java b/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidator.java
index dc69783..b6841ab 100644
--- a/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidator.java
+++ b/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidator.java
@@ -15,7 +15,7 @@
  */
 package com.google.web.bindery.requestfactory.server;
 
-import com.google.gwt.autobean.shared.ValueCodex;
+import com.google.web.bindery.autobean.shared.ValueCodex;
 import com.google.gwt.dev.asm.AnnotationVisitor;
 import com.google.gwt.dev.asm.ClassReader;
 import com.google.gwt.dev.asm.ClassVisitor;
diff --git a/user/src/com/google/web/bindery/requestfactory/server/RequestState.java b/user/src/com/google/web/bindery/requestfactory/server/RequestState.java
index 988250f..e167c41 100644
--- a/user/src/com/google/web/bindery/requestfactory/server/RequestState.java
+++ b/user/src/com/google/web/bindery/requestfactory/server/RequestState.java
@@ -15,12 +15,12 @@
  */
 package com.google.web.bindery.requestfactory.server;
 
-import com.google.gwt.autobean.server.AutoBeanFactoryMagic;
-import com.google.gwt.autobean.shared.AutoBean;
-import com.google.gwt.autobean.shared.AutoBeanCodex;
-import com.google.gwt.autobean.shared.Splittable;
-import com.google.gwt.autobean.shared.ValueCodex;
-import com.google.gwt.autobean.shared.impl.StringQuoter;
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanCodex;
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.shared.ValueCodex;
+import com.google.web.bindery.autobean.shared.impl.StringQuoter;
+import com.google.web.bindery.autobean.vm.AutoBeanFactorySource;
 import com.google.web.bindery.requestfactory.server.SimpleRequestProcessor.IdToEntityMap;
 import com.google.web.bindery.requestfactory.shared.BaseProxy;
 import com.google.web.bindery.requestfactory.shared.EntityProxy;
@@ -210,7 +210,7 @@
    */
   private <Q extends BaseProxy> AutoBean<Q> createProxyBean(
       SimpleProxyId<Q> id, Object domainObject) {
-    AutoBean<Q> toReturn = AutoBeanFactoryMagic.createBean(id.getProxyClass(),
+    AutoBean<Q> toReturn = AutoBeanFactorySource.createBean(id.getProxyClass(),
         SimpleRequestProcessor.CONFIGURATION);
     toReturn.setTag(Constants.STABLE_ID, id);
     toReturn.setTag(Constants.DOMAIN_OBJECT, domainObject);
diff --git a/user/src/com/google/web/bindery/requestfactory/server/Resolver.java b/user/src/com/google/web/bindery/requestfactory/server/Resolver.java
index 086adbf..9f641eb 100644
--- a/user/src/com/google/web/bindery/requestfactory/server/Resolver.java
+++ b/user/src/com/google/web/bindery/requestfactory/server/Resolver.java
@@ -15,12 +15,12 @@
  */
 package com.google.web.bindery.requestfactory.server;
 
-import com.google.gwt.autobean.server.impl.TypeUtils;
-import com.google.gwt.autobean.shared.AutoBean;
-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.ValueCodex;
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanUtils;
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor;
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.shared.ValueCodex;
+import com.google.web.bindery.autobean.vm.impl.TypeUtils;
 import com.google.web.bindery.requestfactory.shared.BaseProxy;
 import com.google.web.bindery.requestfactory.shared.EntityProxy;
 import com.google.web.bindery.requestfactory.shared.EntityProxyId;
@@ -447,4 +447,4 @@
     throw new ReportableException("Unsupported domain type "
         + returnClass.getCanonicalName());
   }
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/ResolverServiceLayer.java b/user/src/com/google/web/bindery/requestfactory/server/ResolverServiceLayer.java
index e9f6914..f95e896 100644
--- a/user/src/com/google/web/bindery/requestfactory/server/ResolverServiceLayer.java
+++ b/user/src/com/google/web/bindery/requestfactory/server/ResolverServiceLayer.java
@@ -15,7 +15,7 @@
  */
 package com.google.web.bindery.requestfactory.server;
 
-import com.google.gwt.autobean.server.impl.TypeUtils;
+import com.google.web.bindery.autobean.vm.impl.TypeUtils;
 import com.google.web.bindery.requestfactory.shared.BaseProxy;
 import com.google.web.bindery.requestfactory.shared.EntityProxy;
 import com.google.web.bindery.requestfactory.shared.EntityProxyId;
diff --git a/user/src/com/google/web/bindery/requestfactory/server/SimpleRequestProcessor.java b/user/src/com/google/web/bindery/requestfactory/server/SimpleRequestProcessor.java
index c09a46a..a763dbb 100644
--- a/user/src/com/google/web/bindery/requestfactory/server/SimpleRequestProcessor.java
+++ b/user/src/com/google/web/bindery/requestfactory/server/SimpleRequestProcessor.java
@@ -15,16 +15,16 @@
  */
 package com.google.web.bindery.requestfactory.server;
 
-import com.google.gwt.autobean.server.AutoBeanFactoryMagic;
-import com.google.gwt.autobean.server.Configuration;
-import com.google.gwt.autobean.server.impl.TypeUtils;
-import com.google.gwt.autobean.shared.AutoBean;
-import com.google.gwt.autobean.shared.AutoBeanCodex;
-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.ValueCodex;
 import com.google.gwt.user.server.Base64Utils;
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanCodex;
+import com.google.web.bindery.autobean.shared.AutoBeanUtils;
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor;
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.shared.ValueCodex;
+import com.google.web.bindery.autobean.vm.AutoBeanFactorySource;
+import com.google.web.bindery.autobean.vm.Configuration;
+import com.google.web.bindery.autobean.vm.impl.TypeUtils;
 import com.google.web.bindery.requestfactory.shared.BaseProxy;
 import com.google.web.bindery.requestfactory.shared.EntityProxyId;
 import com.google.web.bindery.requestfactory.shared.InstanceRequest;
@@ -37,6 +37,7 @@
 import com.google.web.bindery.requestfactory.shared.impl.EntityProxyCategory;
 import com.google.web.bindery.requestfactory.shared.impl.SimpleProxyId;
 import com.google.web.bindery.requestfactory.shared.impl.ValueProxyCategory;
+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.MessageFactory;
 import com.google.web.bindery.requestfactory.shared.messages.OperationMessage;
@@ -44,7 +45,6 @@
 import com.google.web.bindery.requestfactory.shared.messages.ResponseMessage;
 import com.google.web.bindery.requestfactory.shared.messages.ServerFailureMessage;
 import com.google.web.bindery.requestfactory.shared.messages.ViolationMessage;
-import com.google.web.bindery.requestfactory.shared.messages.IdMessage.Strength;
 
 import java.io.UnsupportedEncodingException;
 import java.lang.reflect.Method;
@@ -86,7 +86,7 @@
   /**
    * Vends message objects.
    */
-  static final MessageFactory FACTORY = AutoBeanFactoryMagic.create(MessageFactory.class);
+  static final MessageFactory FACTORY = AutoBeanFactorySource.create(MessageFactory.class);
 
   static String fromBase64(String encoded) {
     try {
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/DefaultProxyStore.java b/user/src/com/google/web/bindery/requestfactory/shared/DefaultProxyStore.java
index f594bdc..44dcbce 100644
--- a/user/src/com/google/web/bindery/requestfactory/shared/DefaultProxyStore.java
+++ b/user/src/com/google/web/bindery/requestfactory/shared/DefaultProxyStore.java
@@ -15,9 +15,9 @@
  */
 package com.google.web.bindery.requestfactory.shared;
 
-import com.google.gwt.autobean.shared.AutoBean;
-import com.google.gwt.autobean.shared.AutoBeanCodex;
-import com.google.gwt.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanCodex;
+import com.google.web.bindery.autobean.shared.Splittable;
 import com.google.web.bindery.requestfactory.shared.impl.MessageFactoryHolder;
 import com.google.web.bindery.requestfactory.shared.messages.OperationMessage;
 
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/ProxyStore.java b/user/src/com/google/web/bindery/requestfactory/shared/ProxyStore.java
index 4040217..ffb3f87 100644
--- a/user/src/com/google/web/bindery/requestfactory/shared/ProxyStore.java
+++ b/user/src/com/google/web/bindery/requestfactory/shared/ProxyStore.java
@@ -15,7 +15,7 @@
  */
 package com.google.web.bindery.requestfactory.shared;
 
-import com.google.gwt.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.shared.Splittable;
 
 /**
  * A ProxyStore provides a {@link ProxySerializer} with access to a low-level
@@ -50,4 +50,4 @@
    * @see Splittable#getPayload()
    */
   void put(String key, Splittable value);
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequest.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequest.java
index fad1919..f7faed5 100644
--- a/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequest.java
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequest.java
@@ -15,7 +15,7 @@
  */
 package com.google.web.bindery.requestfactory.shared.impl;
 
-import com.google.gwt.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.shared.Splittable;
 import com.google.web.bindery.requestfactory.shared.BaseProxy;
 import com.google.web.bindery.requestfactory.shared.InstanceRequest;
 import com.google.web.bindery.requestfactory.shared.Receiver;
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..dd61fa2 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
@@ -19,16 +19,16 @@
 import static com.google.web.bindery.requestfactory.shared.impl.Constants.REQUEST_CONTEXT;
 import static com.google.web.bindery.requestfactory.shared.impl.Constants.STABLE_ID;
 
-import com.google.gwt.autobean.shared.AutoBean;
-import com.google.gwt.autobean.shared.AutoBeanCodex;
-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.ValueCodex;
-import com.google.gwt.autobean.shared.impl.AbstractAutoBean;
-import com.google.gwt.autobean.shared.impl.EnumMap;
-import com.google.gwt.autobean.shared.impl.StringQuoter;
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanCodex;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory;
+import com.google.web.bindery.autobean.shared.AutoBeanUtils;
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor;
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.shared.ValueCodex;
+import com.google.web.bindery.autobean.shared.impl.AbstractAutoBean;
+import com.google.web.bindery.autobean.shared.impl.EnumMap;
+import com.google.web.bindery.autobean.shared.impl.StringQuoter;
 import com.google.gwt.event.shared.UmbrellaException;
 import com.google.web.bindery.requestfactory.shared.BaseProxy;
 import com.google.web.bindery.requestfactory.shared.EntityProxy;
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequestFactory.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequestFactory.java
index 37a9c3d..a13e62d 100644
--- a/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequestFactory.java
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequestFactory.java
@@ -15,7 +15,7 @@
  */
 package com.google.web.bindery.requestfactory.shared.impl;
 
-import com.google.gwt.autobean.shared.AutoBeanFactory;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory;
 import com.google.gwt.event.shared.EventBus;
 import com.google.web.bindery.requestfactory.shared.EntityProxy;
 import com.google.web.bindery.requestfactory.shared.EntityProxyId;
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/BaseProxyCategory.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/BaseProxyCategory.java
index 7dea94b..481f4c8 100644
--- a/user/src/com/google/web/bindery/requestfactory/shared/impl/BaseProxyCategory.java
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/BaseProxyCategory.java
@@ -18,8 +18,8 @@
 import static com.google.web.bindery.requestfactory.shared.impl.Constants.REQUEST_CONTEXT;
 import static com.google.web.bindery.requestfactory.shared.impl.Constants.STABLE_ID;
 
-import com.google.gwt.autobean.shared.AutoBean;
-import com.google.gwt.autobean.shared.AutoBeanUtils;
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanUtils;
 import com.google.web.bindery.requestfactory.shared.BaseProxy;
 
 /**
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..f498ac3 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
@@ -15,11 +15,11 @@
  */
 package com.google.web.bindery.requestfactory.shared.impl;
 
-import com.google.gwt.autobean.shared.AutoBean;
-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.StringQuoter;
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanUtils;
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.shared.ValueCodex;
+import com.google.web.bindery.autobean.shared.impl.StringQuoter;
 import com.google.web.bindery.requestfactory.shared.BaseProxy;
 import com.google.web.bindery.requestfactory.shared.EntityProxyId;
 
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/EntityProxyCategory.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/EntityProxyCategory.java
index bc5e04a..4349d23 100644
--- a/user/src/com/google/web/bindery/requestfactory/shared/impl/EntityProxyCategory.java
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/EntityProxyCategory.java
@@ -18,8 +18,8 @@
 import static com.google.web.bindery.requestfactory.shared.impl.BaseProxyCategory.requestContext;
 import static com.google.web.bindery.requestfactory.shared.impl.Constants.STABLE_ID;
 
-import com.google.gwt.autobean.shared.AutoBean;
-import com.google.gwt.autobean.shared.AutoBeanUtils;
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanUtils;
 import com.google.web.bindery.requestfactory.shared.EntityProxy;
 
 /**
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/MessageFactoryHolder.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/MessageFactoryHolder.java
index 160a2eb..9dc6b4a 100644
--- a/user/src/com/google/web/bindery/requestfactory/shared/impl/MessageFactoryHolder.java
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/MessageFactoryHolder.java
@@ -15,12 +15,12 @@
  */
 package com.google.web.bindery.requestfactory.shared.impl;
 
-import com.google.gwt.autobean.server.AutoBeanFactoryMagic;
+import com.google.web.bindery.autobean.vm.AutoBeanFactorySource;
 import com.google.web.bindery.requestfactory.shared.messages.MessageFactory;
 
 /**
  * This class has a super-source version with a client-only implementation.
  */
 public interface MessageFactoryHolder {
-  MessageFactory FACTORY = AutoBeanFactoryMagic.create(MessageFactory.class);
+  MessageFactory FACTORY = AutoBeanFactorySource.create(MessageFactory.class);
 }
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/ProxySerializerImpl.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/ProxySerializerImpl.java
index 9b3797c..f13942b 100644
--- a/user/src/com/google/web/bindery/requestfactory/shared/impl/ProxySerializerImpl.java
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/ProxySerializerImpl.java
@@ -15,12 +15,12 @@
  */
 package com.google.web.bindery.requestfactory.shared.impl;
 
-import com.google.gwt.autobean.shared.AutoBean;
-import com.google.gwt.autobean.shared.AutoBeanCodex;
-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.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanCodex;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory;
+import com.google.web.bindery.autobean.shared.AutoBeanUtils;
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor;
+import com.google.web.bindery.autobean.shared.Splittable;
 import com.google.web.bindery.requestfactory.shared.BaseProxy;
 import com.google.web.bindery.requestfactory.shared.EntityProxy;
 import com.google.web.bindery.requestfactory.shared.EntityProxyId;
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/ValueProxyCategory.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/ValueProxyCategory.java
index 775673a..95c5ca2 100644
--- a/user/src/com/google/web/bindery/requestfactory/shared/impl/ValueProxyCategory.java
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/ValueProxyCategory.java
@@ -17,8 +17,8 @@
 
 import static com.google.web.bindery.requestfactory.shared.impl.BaseProxyCategory.stableId;
 
-import com.google.gwt.autobean.shared.AutoBean;
-import com.google.gwt.autobean.shared.AutoBeanUtils;
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanUtils;
 import com.google.web.bindery.requestfactory.shared.ValueProxy;
 
 /**
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/messages/IdMessage.java b/user/src/com/google/web/bindery/requestfactory/shared/messages/IdMessage.java
index 48e42b2..6d1d6c1 100644
--- a/user/src/com/google/web/bindery/requestfactory/shared/messages/IdMessage.java
+++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/IdMessage.java
@@ -15,7 +15,7 @@
  */
 package com.google.web.bindery.requestfactory.shared.messages;
 
-import com.google.gwt.autobean.shared.AutoBean.PropertyName;
+import com.google.web.bindery.autobean.shared.AutoBean.PropertyName;
 
 /**
  * Used as a base type for messages that are about a particular id.
@@ -81,4 +81,4 @@
 
   @PropertyName(TYPE_TOKEN)
   void setTypeToken(String value);
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/messages/InvocationMessage.java b/user/src/com/google/web/bindery/requestfactory/shared/messages/InvocationMessage.java
index 38a124f..00fcac3 100644
--- a/user/src/com/google/web/bindery/requestfactory/shared/messages/InvocationMessage.java
+++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/InvocationMessage.java
@@ -15,8 +15,8 @@
  */
 package com.google.web.bindery.requestfactory.shared.messages;
 
-import com.google.gwt.autobean.shared.AutoBean.PropertyName;
-import com.google.gwt.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.shared.AutoBean.PropertyName;
+import com.google.web.bindery.autobean.shared.Splittable;
 
 import java.util.List;
 import java.util.Set;
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/messages/JsonRpcRequest.java b/user/src/com/google/web/bindery/requestfactory/shared/messages/JsonRpcRequest.java
index 399a214..275c7d5 100644
--- a/user/src/com/google/web/bindery/requestfactory/shared/messages/JsonRpcRequest.java
+++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/JsonRpcRequest.java
@@ -15,8 +15,8 @@
  */
 package com.google.web.bindery.requestfactory.shared.messages;
 
-import com.google.gwt.autobean.shared.AutoBean.PropertyName;
-import com.google.gwt.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.shared.AutoBean.PropertyName;
+import com.google.web.bindery.autobean.shared.Splittable;
 
 import java.util.Map;
 
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/messages/MessageFactory.java b/user/src/com/google/web/bindery/requestfactory/shared/messages/MessageFactory.java
index 09fe6d6..e22048b 100644
--- a/user/src/com/google/web/bindery/requestfactory/shared/messages/MessageFactory.java
+++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/MessageFactory.java
@@ -15,8 +15,8 @@
  */
 package com.google.web.bindery.requestfactory.shared.messages;
 
-import com.google.gwt.autobean.shared.AutoBean;
-import com.google.gwt.autobean.shared.AutoBeanFactory;
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory;
 
 /**
  * The factory for creating RequestFactory wire messages.
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/messages/OperationMessage.java b/user/src/com/google/web/bindery/requestfactory/shared/messages/OperationMessage.java
index 0fff04f..9ed7e67 100644
--- a/user/src/com/google/web/bindery/requestfactory/shared/messages/OperationMessage.java
+++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/OperationMessage.java
@@ -15,8 +15,8 @@
  */
 package com.google.web.bindery.requestfactory.shared.messages;
 
-import com.google.gwt.autobean.shared.Splittable;
-import com.google.gwt.autobean.shared.AutoBean.PropertyName;
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.shared.AutoBean.PropertyName;
 import com.google.web.bindery.requestfactory.shared.WriteOperation;
 
 import java.util.Map;
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/messages/RequestMessage.java b/user/src/com/google/web/bindery/requestfactory/shared/messages/RequestMessage.java
index d8dc38d..8f35588 100644
--- a/user/src/com/google/web/bindery/requestfactory/shared/messages/RequestMessage.java
+++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/RequestMessage.java
@@ -15,7 +15,7 @@
  */
 package com.google.web.bindery.requestfactory.shared.messages;
 
-import com.google.gwt.autobean.shared.AutoBean.PropertyName;
+import com.google.web.bindery.autobean.shared.AutoBean.PropertyName;
 
 import java.util.List;
 
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/messages/ResponseMessage.java b/user/src/com/google/web/bindery/requestfactory/shared/messages/ResponseMessage.java
index 08d9a91..47efd4d 100644
--- a/user/src/com/google/web/bindery/requestfactory/shared/messages/ResponseMessage.java
+++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/ResponseMessage.java
@@ -15,8 +15,8 @@
  */
 package com.google.web.bindery.requestfactory.shared.messages;
 
-import com.google.gwt.autobean.shared.AutoBean.PropertyName;
-import com.google.gwt.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.shared.AutoBean.PropertyName;
+import com.google.web.bindery.autobean.shared.Splittable;
 
 import java.util.List;
 
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/messages/ServerFailureMessage.java b/user/src/com/google/web/bindery/requestfactory/shared/messages/ServerFailureMessage.java
index cda32c3..7e101e3 100644
--- a/user/src/com/google/web/bindery/requestfactory/shared/messages/ServerFailureMessage.java
+++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/ServerFailureMessage.java
@@ -15,7 +15,7 @@
  */
 package com.google.web.bindery.requestfactory.shared.messages;
 
-import com.google.gwt.autobean.shared.AutoBean.PropertyName;
+import com.google.web.bindery.autobean.shared.AutoBean.PropertyName;
 
 /**
  * Encapsulates a ServerFailure object.
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/messages/VersionedMessage.java b/user/src/com/google/web/bindery/requestfactory/shared/messages/VersionedMessage.java
index ea3d06b..51fe40e 100644
--- a/user/src/com/google/web/bindery/requestfactory/shared/messages/VersionedMessage.java
+++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/VersionedMessage.java
@@ -15,7 +15,7 @@
  */
 package com.google.web.bindery.requestfactory.shared.messages;
 
-import com.google.gwt.autobean.shared.AutoBean.PropertyName;
+import com.google.web.bindery.autobean.shared.AutoBean.PropertyName;
 
 /**
  * Describes a message that contains version information.
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/messages/ViolationMessage.java b/user/src/com/google/web/bindery/requestfactory/shared/messages/ViolationMessage.java
index e7b86d2..5b7725f 100644
--- a/user/src/com/google/web/bindery/requestfactory/shared/messages/ViolationMessage.java
+++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/ViolationMessage.java
@@ -15,7 +15,7 @@
  */
 package com.google.web.bindery.requestfactory.shared.messages;
 
-import com.google.gwt.autobean.shared.AutoBean.PropertyName;
+import com.google.web.bindery.autobean.shared.AutoBean.PropertyName;
 
 /**
  * Represents a ConstraintViolation.
diff --git a/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestContext.java b/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestContext.java
index cd63ab9..bcdb434 100644
--- a/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestContext.java
+++ b/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestContext.java
@@ -15,10 +15,10 @@
  */
 package com.google.web.bindery.requestfactory.vm;
 
-import com.google.gwt.autobean.server.impl.BeanMethod;
-import com.google.gwt.autobean.server.impl.TypeUtils;
-import com.google.gwt.autobean.shared.AutoBean.PropertyName;
-import com.google.gwt.autobean.shared.AutoBeanFactory;
+import com.google.web.bindery.autobean.shared.AutoBean.PropertyName;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory;
+import com.google.web.bindery.autobean.vm.impl.BeanMethod;
+import com.google.web.bindery.autobean.vm.impl.TypeUtils;
 import com.google.web.bindery.requestfactory.shared.InstanceRequest;
 import com.google.web.bindery.requestfactory.shared.JsonRpcContent;
 import com.google.web.bindery.requestfactory.shared.JsonRpcWireName;
diff --git a/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestFactory.java b/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestFactory.java
index 718c324..3d931ca 100644
--- a/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestFactory.java
+++ b/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestFactory.java
@@ -15,10 +15,10 @@
  */
 package com.google.web.bindery.requestfactory.vm;
 
-import com.google.gwt.autobean.server.AutoBeanFactoryMagic;
-import com.google.gwt.autobean.shared.AutoBeanFactory;
-import com.google.gwt.autobean.shared.AutoBeanFactory.Category;
-import com.google.gwt.autobean.shared.AutoBeanFactory.NoWrap;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory.Category;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory.NoWrap;
+import com.google.web.bindery.autobean.vm.AutoBeanFactorySource;
 import com.google.gwt.event.shared.EventBus;
 import com.google.web.bindery.requestfactory.shared.BaseProxy;
 import com.google.web.bindery.requestfactory.shared.EntityProxy;
@@ -92,7 +92,7 @@
 
   @Override
   protected AutoBeanFactory getAutoBeanFactory() {
-    return AutoBeanFactoryMagic.create(Factory.class);
+    return AutoBeanFactorySource.create(Factory.class);
   }
 
   @Override