Cherrypick user/{src,super,test}/com/google/web/bindery/...
Update GWT references to point to the new packages
Deprecate the old packages


git-svn-id: https://google-web-toolkit.googlecode.com/svn/releases/2.3@10070 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/build.xml b/build.xml
index e17454d..81a956a 100755
--- a/build.xml
+++ b/build.xml
@@ -37,6 +37,7 @@
           description="[action] Minimal one-platform devel build, without distro packaging">
     <call-subproject subproject="dev" subtarget="build" />
     <call-subproject subproject="user" subtarget="build" />
+    <call-subproject subproject="requestfactory" subtarget="build" />
     <call-subproject subproject="servlet" subtarget="build" />
     <call-subproject subproject="jni" subtarget="build" />
   </target>
@@ -70,6 +71,11 @@
     <gwt.ant dir="tools" />
   </target>
 
+  <target name="requestfactory" description="[subdir] Builds (or runs ${target} if set) only the requestfactory jars">
+    <call-subproject subproject="user" subtarget="build" />
+    <gwt.ant dir="requestfactory" />
+  </target>
+
   <target name="servlet" description="[subdir] Builds (or runs ${target} if set) only the servlet jar">
     <call-subproject subproject="user" subtarget="build" />
     <gwt.ant dir="servlet" />
@@ -94,18 +100,20 @@
   </target>
 
   <target name="build" description="[action] Builds GWT, including samples, but without distro packaging">
-     <call-subproject subproject="dev" subtarget="build"/>
-     <call-subproject subproject="user" subtarget="build"/>
-     <call-subproject subproject="servlet" subtarget="build"/>
-     <call-subproject subproject="tools" subtarget="build"/>
-     <call-subproject subproject="jni" subtarget="build"/>
-     <call-subproject subproject="samples" subtarget="build"/>
+    <call-subproject subproject="dev" subtarget="build"/>
+    <call-subproject subproject="user" subtarget="build"/>
+    <call-subproject subproject="requestfactory" subtarget="build"/>
+    <call-subproject subproject="servlet" subtarget="build"/>
+    <call-subproject subproject="tools" subtarget="build"/>
+    <call-subproject subproject="jni" subtarget="build"/>
+    <call-subproject subproject="samples" subtarget="build"/>
   </target>
 
   <target name="checkstyle" description="[action] Does static analysis of GWT source">
     <call-subproject subproject="buildtools" subtarget="checkstyle" />
     <call-subproject subproject="dev" subtarget="checkstyle" />
     <call-subproject subproject="user" subtarget="checkstyle" />
+    <call-subproject subproject="requestfactory" subtarget="checkstyle" />
     <call-subproject subproject="servlet" subtarget="checkstyle" />
     <call-subproject subproject="tools" subtarget="checkstyle" />
     <call-subproject subproject="samples" subtarget="checkstyle" />
@@ -116,10 +124,18 @@
     <call-subproject subproject="buildtools" subtarget="test" />
     <call-subproject subproject="dev" subtarget="test" />
     <call-subproject subproject="user" subtarget="test" />
+    <call-subproject subproject="requestfactory" subtarget="test" />
     <call-subproject subproject="servlet" subtarget="test" />
     <call-subproject subproject="tools" subtarget="test" />
   </target>
 
+  <target name="testrf" depends="dist-dev" 
+          description="[action] Runs the GWT RequestFactory tests">
+    <call-subproject subproject="dev" subtarget="compile.tests" />
+    <call-subproject subproject="user" subtarget="compile.tests" />
+    <call-subproject subproject="requestfactory" subtarget="test" />
+  </target>
+
   <target name="benchmark" depends="dist-dev" 
           description="[action] Runs all the GWT benchmarks">
     <call-subproject subproject="user" subtarget="benchmark" />
diff --git a/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java b/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
index d5ed6ed..ea04297 100644
--- a/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
+++ b/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
@@ -534,7 +534,7 @@
            */
           JClassType implementingType = typeOracle.getSingleJsoImpl(intfMethod.getEnclosingType());
 
-          if (implementingType == null) {
+          if (implementingType == null || implementingType.isAnnotationPresent(GwtScriptOnly.class)) {
             /*
              * This means that there is no concrete implementation of the
              * interface by a JSO. Any implementation that might be created by a
diff --git a/requestfactory/build.xml b/requestfactory/build.xml
new file mode 100755
index 0000000..5139e8c
--- /dev/null
+++ b/requestfactory/build.xml
@@ -0,0 +1,103 @@
+<project name="requestfactory" default="build" basedir=".">
+  <property name="gwt.root" location=".." />
+  <property name="project.tail" value="requestfactory" />
+  <import file="${gwt.root}/common.ant.xml" />
+
+  <!-- Remove all output files -->
+  <target name="clean" description="Cleans this project's output files">
+    <delete file="${gwt.build.lib}/requestfactory-client.jar" />
+    <delete file="${gwt.build.lib}/requestfactory-client-src.jar" />
+    <delete file="${gwt.build.lib}/requestfactory-client+src.jar" />
+    <delete file="${gwt.build.lib}/requestfactory-server.jar" />
+    <delete file="${gwt.build.lib}/requestfactory-server-src.jar" />
+    <delete file="${gwt.build.lib}/requestfactory-server+src.jar" />
+    <delete file="${gwt.build.lib}/requestfactory-test+src.jar" />
+  </target>
+
+  <!-- Build a jar file containing a subset of requestfactory -->
+  <macrodef name="requestfactory-jar">
+    <!--
+         "target" should be one of {client,server,all}[(+|-)src] or test+src.
+         -src includes .java files only, +src includes .java and .class files
+    -->
+    <attribute name="target" default="client"/>
+    <sequential>
+      <java failonerror="true" fork="true"
+            classname="com.google.web.bindery.requestfactory.server.RequestFactoryJarExtractor">
+        <classpath>
+          <fileset dir="${gwt.build.lib}" includes="gwt-user.jar,gwt-dev.jar" />
+          <fileset dir="${gwt.tools.redist}" includes="json/r2_20080312/json-1.5.jar" />
+          <pathelement location="${gwt.tools.lib}/junit/junit-4.8.2.jar" />
+          <pathelement path="${gwt.build.out}/user/bin" />
+          <pathelement path="${gwt.build.out}/dev/bin-test" />
+          <pathelement path="${gwt.build.out}/user/bin-test" />
+        </classpath>
+        <arg value="@{target}"/>
+        <arg file="${gwt.build.lib}/requestfactory-@{target}.jar"/>
+      </java>
+    </sequential>
+  </macrodef>
+ 
+  <!-- Targets for individual jars -->
+
+  <target name="requestfactory-client" description="Build RequestFactory client jar">
+    <requestfactory-jar target="client"/>
+  </target>
+
+  <target name="requestfactory-client-src" description="Build RequestFactory client source jar">
+    <requestfactory-jar target="client-src"/>
+  </target>
+
+  <target name="requestfactory-client+src" description="Build RequestFactory client source/class jar">
+    <requestfactory-jar target="client+src"/>
+  </target>
+
+  <target name="requestfactory-server" description="Build RequestFactory server jar">
+    <requestfactory-jar target="server"/>
+  </target>
+
+  <target name="requestfactory-server-src" description="Build RequestFactory server source jar">
+    <requestfactory-jar target="server-src"/>
+  </target>
+
+  <target name="requestfactory-server+src" description="Build RequestFactory server source/class jar">
+    <requestfactory-jar target="server+src"/>
+  </target>
+
+  <!-- This target requires classes from ../build/{dev,user}/bin-test -->
+  <target name="requestfactory-test+src" description="Build RequestFactory test source/class jar">
+    <requestfactory-jar target="test+src" />
+  </target>
+
+  <!-- Build all client jars -->
+  <target name="clientjars" depends="requestfactory-client,requestfactory-client-src,requestfactory-client+src" description="Build requestfactory client jars" />
+
+  <!-- Build all server jars -->
+  <target name="serverjars" depends="requestfactory-server,requestfactory-server-src,requestfactory-server+src" description="Build requestfactory server jars" />
+
+  <!-- Default target, build client and server jars.
+       Assumes the 'user' target has been built in the trunk directory
+  -->
+  <target name="build" depends="clientjars,serverjars" />
+
+  <!-- Run RequestFactoryJreSuite from the requestfactory-test+src jar.
+       Assumes test classes have been built in the trunk directory
+  -->
+  <target name="test" depends="requestfactory-test+src" description="Run RequestFactoryJreSuite">
+    <java failonerror="true" fork="true"
+      classname="com.google.web.bindery.requestfactory.vm.RequestFactoryJreSuite">
+      <jvmarg value="-Xss8m" />
+      <classpath>
+        <fileset dir="${gwt.tools.lib}" includes="tomcat/servlet-api-2.5.jar" />
+        <fileset dir="${gwt.tools.lib}" includes="apache/log4j/log4j-1.2.16.jar" />
+        <fileset dir="${gwt.tools.lib}" includes="slf4j/slf4j-api/slf4j-api-1.6.1.jar" />
+        <fileset dir="${gwt.tools.lib}" includes="slf4j/slf4j-log4j12/slf4j-log4j12-1.6.1.jar" />
+        <fileset dir="${gwt.tools.lib}" includes="hibernate/validator/hibernate-validator-4.1.0.Final.jar" />
+        <fileset dir="${gwt.tools.lib}" includes="javax/validation/validation-api-1.0.0.GA.jar" />
+        <fileset dir="${gwt.tools.lib}" includes="javax/xml/bind/jaxb-api-2.1.jar" />
+        <fileset dir="${gwt.build.lib}" includes="requestfactory-test+src.jar" />
+      </classpath>
+    </java>
+  </target>
+
+</project>
diff --git a/tools/api-checker/config/gwt22_23userApi.conf b/tools/api-checker/config/gwt22_23userApi.conf
index acbb7e5..c9d3227 100644
--- a/tools/api-checker/config/gwt22_23userApi.conf
+++ b/tools/api-checker/config/gwt22_23userApi.conf
@@ -14,9 +14,10 @@
 :**/rebind/**\
 :**/server/**\
 :**/tools/**\
+:**/vm/**\
 :com/google/gwt/regexp/shared/**\
-:com/google/gwt/autobean/**/impl/**\
-:com/google/gwt/autobean/shared/ValueCodexHelper.java\
+:com/google/gwt/autobean/**\
+:com/google/web/bindery/autobean/**\
 :com/google/gwt/core/client/impl/WeakMapping.java\
 :com/google/gwt/core/ext/**\
 :com/google/gwt/dev/*.java\
@@ -42,11 +43,8 @@
 :com/google/gwt/resources/css/**\
 :com/google/gwt/resources/ext/**\
 :com/google/gwt/resources/rg/**\
-:com/google/gwt/requestfactory/client/impl/FindRequest.java\
-:com/google/gwt/requestfactory/shared/impl/MessageFactoryHolder.java\
-:com/google/gwt/requestfactory/shared/UserInformationProxy.java\
-:com/google/gwt/requestfactory/shared/UserInformationRequest.java\
-:com/google/gwt/requestfactory/ui/client/LoginWidget.java\
+:com/google/gwt/requestfactory/**\
+:com/google/web/bindery/requestfactory/**\
 :com/google/gwt/rpc/client/impl/ClientWriterFactory.java\
 :com/google/gwt/rpc/client/impl/EscapeUtil.java\
 :com/google/gwt/rpc/client/impl/RpcCallbackAdapter.java\
@@ -80,9 +78,12 @@
 :**/rebind/**\
 :**/server/**\
 :**/tools/**\
-:user/src/com/google/gwt/regexp/shared/**\
+:**/vm/**\
 :user/src/com/google/gwt/autobean/shared/ValueCodexHelper.java\
-:user/src/com/google/gwt/autobean/**/impl/**\
+:user/src/com/google/web/bindery/autobean/shared/ValueCodexHelper.java\
+:user/src/com/google/gwt/autobean/shared/impl/StringQuoter.java\
+:user/src/com/google/web/bindery/autobean/shared/impl/StringQuoter.java\
+:user/src/com/google/gwt/regexp/shared/**\
 :user/src/com/google/gwt/core/client/impl/WeakMapping.java\
 :user/src/com/google/gwt/junit/*.java\
 :user/src/com/google/gwt/junit/client/GWTTestCase.java\
@@ -92,6 +93,7 @@
 :user/src/com/google/gwt/resources/ext/**\
 :user/src/com/google/gwt/resources/rg/**\
 :user/src/com/google/gwt/requestfactory/shared/impl/MessageFactoryHolder.java\
+:user/src/com/google/web/bindery/requestfactory/shared/impl/MessageFactoryHolder.java\
 :user/src/com/google/gwt/rpc/client/impl/ClientWriterFactory.java\
 :user/src/com/google/gwt/rpc/client/impl/EscapeUtil.java\
 :user/src/com/google/gwt/rpc/client/impl/RpcCallbackAdapter.java\
@@ -109,11 +111,59 @@
 ##############################################
 #excluded packages colon separated list
 excludedPackages com.google.gwt.editor.client.impl\
-:com.google.gwt.requestfactory.client.impl.messages\
-:com.google.gwt.requestfactory.client.impl\
-:com.google.gwt.requestfactory.shared.impl\
+:com/google/gwt/requestfactory\
+:com/google/gwt/requestfactory/client\
+:com/google/gwt/requestfactory/client/testing\
+:com/google/gwt/requestfactory/client/impl\
+:com/google/gwt/requestfactory/rebind\
+:com/google/gwt/requestfactory/rebind/model\
+:com/google/gwt/requestfactory/server\
+:com/google/gwt/requestfactory/server/testing\
+:com/google/gwt/requestfactory/server/impl\
+:com/google/gwt/requestfactory/shared\
+:com/google/gwt/requestfactory/shared/messages\
+:com/google/gwt/requestfactory/shared/impl\
+:com/google/gwt/requestfactory/shared/impl/posers\
+:com/google/gwt/requestfactory/ui\
+:com/google/gwt/requestfactory/ui/client\
 :com.google.gwt.junit.client.impl\
 :com.google.gwt.benchmarks.client.impl\
+:com/google/web/bindery/requestfactory\
+:com/google/web/bindery/requestfactory/gwt\
+:com/google/web/bindery/requestfactory/gwt/client\
+:com/google/web/bindery/requestfactory/gwt/client/impl\
+:com/google/web/bindery/requestfactory/gwt/client/testing\
+:com/google/web/bindery/requestfactory/gwt/rebind\
+:com/google/web/bindery/requestfactory/gwt/rebind/model\
+:com/google/web/bindery/requestfactory/gwt/ui\
+:com/google/web/bindery/requestfactory/gwt/ui/client\
+:com/google/web/bindery/requestfactory/server\
+:com/google/web/bindery/requestfactory/server/impl\
+:com/google/web/bindery/requestfactory/server/testing\
+:com/google/web/bindery/requestfactory/shared\
+:com/google/web/bindery/requestfactory/shared/impl\
+:com/google/web/bindery/requestfactory/shared/impl/posers\
+:com/google/web/bindery/requestfactory/shared/messages\
+:com/google/web/bindery/requestfactory/vm\
+:com/google/gwt/autobean\
+:com/google/gwt/autobean/client\
+:com/google/gwt/autobean/client/impl\
+:com/google/gwt/autobean/rebind\
+:com/google/gwt/autobean/rebind/model\
+:com/google/gwt/autobean/server\
+:com/google/gwt/autobean/server/impl\
+:com/google/gwt/autobean/shared\
+:com/google/gwt/autobean/shared/impl\
+:com/google/web/bindery/autobean\
+:com/google/web/bindery/autobean/gwt\
+:com/google/web/bindery/autobean/gwt/client\
+:com/google/web/bindery/autobean/gwt/client/impl\
+:com/google/web/bindery/autobean/gwt/rebind\
+:com/google/web/bindery/autobean/gwt/rebind/model\
+:com/google/web/bindery/autobean/shared\
+:com/google/web/bindery/autobean/shared/impl\
+:com/google/web/bindery/autobean/vm\
+:com/google/web/bindery/autobean/vm/impl\
 
 ##############################################
 #Api  whitelist
@@ -123,6 +173,24 @@
 # Overloaded SimplePanel constructor to accept a Widget.
 com.google.gwt.user.client.ui.SimplePanel::SimplePanel(Lcom/google/gwt/dom/client/Element;) OVERLOADED_METHOD_CALL
 
+# Overloads for legacy compatibility after introduction of com.google.web.bindery
+#
+
+com.google.gwt.event.shared.EventBus::addHandler(Lcom/google/gwt/event/shared/GwtEvent$Type;Lcom/google/gwt/event/shared/EventHandler;) OVERLOADED_METHOD_CALL
+com.google.gwt.event.shared.EventBus::addHandlerToSource(Lcom/google/gwt/event/shared/GwtEvent$Type;Ljava/lang/Object;Lcom/google/gwt/event/shared/EventHandler;) OVERLOADED_METHOD_CALL
+
+com.google.gwt.event.shared.ResettableEventBus::addHandler(Lcom/google/gwt/event/shared/GwtEvent$Type;Lcom/google/gwt/event/shared/EventHandler;) OVERLOADED_METHOD_CALL
+com.google.gwt.event.shared.ResettableEventBus::addHandlerToSource(Lcom/google/gwt/event/shared/GwtEvent$Type;Ljava/lang/Object;Lcom/google/gwt/event/shared/EventHandler;) OVERLOADED_METHOD_CALL
+
+com.google.gwt.event.shared.SimpleEventBus::addHandler(Lcom/google/gwt/event/shared/GwtEvent$Type;Lcom/google/gwt/event/shared/EventHandler;) OVERLOADED_METHOD_CALL
+com.google.gwt.event.shared.SimpleEventBus::addHandlerToSource(Lcom/google/gwt/event/shared/GwtEvent$Type;Ljava/lang/Object;Lcom/google/gwt/event/shared/EventHandler;) OVERLOADED_METHOD_CALL
+
+com.google.gwt.event.shared.testing.CountingEventBus::addHandler(Lcom/google/gwt/event/shared/GwtEvent$Type;Lcom/google/gwt/event/shared/EventHandler;) OVERLOADED_METHOD_CALL
+com.google.gwt.event.shared.testing.CountingEventBus::addHandlerToSource(Lcom/google/gwt/event/shared/GwtEvent$Type;Ljava/lang/Object;Lcom/google/gwt/event/shared/EventHandler;) OVERLOADED_METHOD_CALL
+
+#
+# end com.google.web.bindery changes
+
 # Renamed CellBasedWidgetImplSafari to CellBasedWidgetImplStandardBase.
 com.google.gwt.user.cellview.client.CellBasedWidgetImplSafari MISSING
 
diff --git a/user/src/com/google/gwt/activity/shared/AbstractActivity.java b/user/src/com/google/gwt/activity/shared/AbstractActivity.java
index 59e596c..51f3abd 100644
--- a/user/src/com/google/gwt/activity/shared/AbstractActivity.java
+++ b/user/src/com/google/gwt/activity/shared/AbstractActivity.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -16,8 +16,8 @@
 package com.google.gwt.activity.shared;
 
 /**
- * Simple Activity implementation that is always willing to stop,
- * and does nothing onStop and onCancel.
+ * Simple Activity implementation that is always willing to stop, and does
+ * nothing onStop and onCancel.
  */
 public abstract class AbstractActivity implements Activity {
 
diff --git a/user/src/com/google/gwt/activity/shared/Activity.java b/user/src/com/google/gwt/activity/shared/Activity.java
index f141171..3c473dc 100644
--- a/user/src/com/google/gwt/activity/shared/Activity.java
+++ b/user/src/com/google/gwt/activity/shared/Activity.java
@@ -49,7 +49,7 @@
    * Called when the Activity should ready its widget for the user. When the
    * widget is ready (typically after an RPC response has been received),
    * receiver should present it by calling
-   * {@link AcceptsOneWidget#setWidget(IsWidget)} on the given panel.
+   * {@link AcceptsOneWidget#setWidget} on the given panel.
    * <p>
    * Any handlers attached to the provided event bus will be de-registered when
    * the activity is stopped, so activities will rarely need to hold on to the
diff --git a/user/src/com/google/gwt/autobean/client/impl/AbstractAutoBeanFactory.java b/user/src/com/google/gwt/autobean/client/impl/AbstractAutoBeanFactory.java
index ce068ca..eb08f85 100644
--- a/user/src/com/google/gwt/autobean/client/impl/AbstractAutoBeanFactory.java
+++ b/user/src/com/google/gwt/autobean/client/impl/AbstractAutoBeanFactory.java
@@ -25,7 +25,12 @@
 
 /**
  * Provides base implementations of AutoBeanFactory methods.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public abstract class AbstractAutoBeanFactory implements AutoBeanFactory,
     EnumMap {
 
diff --git a/user/src/com/google/gwt/autobean/client/impl/ClientPropertyContext.java b/user/src/com/google/gwt/autobean/client/impl/ClientPropertyContext.java
index c89ae2d..e1129b2 100644
--- a/user/src/com/google/gwt/autobean/client/impl/ClientPropertyContext.java
+++ b/user/src/com/google/gwt/autobean/client/impl/ClientPropertyContext.java
@@ -27,13 +27,23 @@
 
 /**
  * Provides base methods for generated implementations of PropertyContext.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public final class ClientPropertyContext implements PropertyContext,
     CollectionPropertyContext, MapPropertyContext {
 
   /**
    * A reference to an instance setter method.
+   *
+   * <p><span style='color:red'>AutoBeans has moved to
+   * <code>com.google.web.bindery.autobeans</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
    */
+  @Deprecated
   public static final class Setter extends JavaScriptObject {
     /**
      * Create a trivial Setter that calls {@link Map#put(Object, Object)}.
diff --git a/user/src/com/google/gwt/autobean/client/impl/JsniCreatorMap.java b/user/src/com/google/gwt/autobean/client/impl/JsniCreatorMap.java
index 67f34fe..bf137da 100644
--- a/user/src/com/google/gwt/autobean/client/impl/JsniCreatorMap.java
+++ b/user/src/com/google/gwt/autobean/client/impl/JsniCreatorMap.java
@@ -22,7 +22,12 @@
 /**
  * Used in prod-mode code to create instances of generated AutoBean subtypes via
  * JSNI references to their constructor methods.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public final class JsniCreatorMap extends JavaScriptObject {
   public static JsniCreatorMap createMap() {
     return JavaScriptObject.createObject().cast();
diff --git a/user/src/com/google/gwt/autobean/client/impl/JsoSplittable.java b/user/src/com/google/gwt/autobean/client/impl/JsoSplittable.java
index c7d227f..cf26cbe 100644
--- a/user/src/com/google/gwt/autobean/client/impl/JsoSplittable.java
+++ b/user/src/com/google/gwt/autobean/client/impl/JsoSplittable.java
@@ -25,11 +25,21 @@
 
 /**
  * Implements the EntityCodex.Splittable interface using a raw JavaScriptObject.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public final class JsoSplittable extends JavaScriptObject implements Splittable {
   /**
    * This type is used because we can't treat Strings as JSOs.
+   *
+   * <p><span style='color:red'>AutoBeans has moved to
+   * <code>com.google.web.bindery.autobeans</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
    */
+  @Deprecated
   public static class StringSplittable implements Splittable {
     private final String value;
 
diff --git a/user/src/com/google/gwt/autobean/rebind/AutoBeanFactoryGenerator.java b/user/src/com/google/gwt/autobean/rebind/AutoBeanFactoryGenerator.java
index 509cadc..8392bb8 100644
--- a/user/src/com/google/gwt/autobean/rebind/AutoBeanFactoryGenerator.java
+++ b/user/src/com/google/gwt/autobean/rebind/AutoBeanFactoryGenerator.java
@@ -58,7 +58,12 @@
 
 /**
  * Generates implementations of AutoBeanFactory.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class AutoBeanFactoryGenerator extends Generator {
 
   private GeneratorContext context;
diff --git a/user/src/com/google/gwt/autobean/rebind/model/AutoBeanFactoryMethod.java b/user/src/com/google/gwt/autobean/rebind/model/AutoBeanFactoryMethod.java
index ee9cdae..52110d4 100644
--- a/user/src/com/google/gwt/autobean/rebind/model/AutoBeanFactoryMethod.java
+++ b/user/src/com/google/gwt/autobean/rebind/model/AutoBeanFactoryMethod.java
@@ -20,11 +20,21 @@
 
 /**
  * Represents a single method in an AutoBeanFactory interface.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class AutoBeanFactoryMethod {
   /**
    * Builds AutoBeanFactoryMethods.
+   *
+   * <p><span style='color:red'>AutoBeans has moved to
+   * <code>com.google.web.bindery.autobeans</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
    */
+  @Deprecated
   public static class Builder {
     private AutoBeanFactoryMethod toReturn = new AutoBeanFactoryMethod();
 
diff --git a/user/src/com/google/gwt/autobean/rebind/model/AutoBeanFactoryModel.java b/user/src/com/google/gwt/autobean/rebind/model/AutoBeanFactoryModel.java
index 1ec0452..ba7cc4c 100644
--- a/user/src/com/google/gwt/autobean/rebind/model/AutoBeanFactoryModel.java
+++ b/user/src/com/google/gwt/autobean/rebind/model/AutoBeanFactoryModel.java
@@ -44,8 +44,11 @@
 import java.util.Set;
 
 /**
- * 
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class AutoBeanFactoryModel {
   private static final JType[] EMPTY_JTYPE = new JType[0];
 
diff --git a/user/src/com/google/gwt/autobean/rebind/model/AutoBeanMethod.java b/user/src/com/google/gwt/autobean/rebind/model/AutoBeanMethod.java
index e936479..1c1f93a 100644
--- a/user/src/com/google/gwt/autobean/rebind/model/AutoBeanMethod.java
+++ b/user/src/com/google/gwt/autobean/rebind/model/AutoBeanMethod.java
@@ -30,11 +30,21 @@
 
 /**
  * Describes a method implemented by an AutoBean.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class AutoBeanMethod {
   /**
    * Creates AutoBeanMethods.
+   *
+   * <p><span style='color:red'>AutoBeans has moved to
+   * <code>com.google.web.bindery.autobeans</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
    */
+  @Deprecated
   public static class Builder {
     private AutoBeanMethod toReturn = new AutoBeanMethod();
 
@@ -216,4 +226,4 @@
   public String toString() {
     return method.toString();
   }
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/autobean/rebind/model/AutoBeanType.java b/user/src/com/google/gwt/autobean/rebind/model/AutoBeanType.java
index 5fd64e5..0a6335c 100644
--- a/user/src/com/google/gwt/autobean/rebind/model/AutoBeanType.java
+++ b/user/src/com/google/gwt/autobean/rebind/model/AutoBeanType.java
@@ -25,12 +25,22 @@
 
 /**
  * Describes an AutoBean.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class AutoBeanType {
 
   /**
    * Builder.
+   *
+   * <p><span style='color:red'>AutoBeans has moved to
+   * <code>com.google.web.bindery.autobeans</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
    */
+  @Deprecated
   public static class Builder {
     private boolean affectedByCategories;
     private String beanSimpleSourceName;
@@ -165,4 +175,4 @@
   public String toString() {
     return peerType.toString();
   }
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/autobean/rebind/model/JBeanMethod.java b/user/src/com/google/gwt/autobean/rebind/model/JBeanMethod.java
index 9880b61..661a585 100644
--- a/user/src/com/google/gwt/autobean/rebind/model/JBeanMethod.java
+++ b/user/src/com/google/gwt/autobean/rebind/model/JBeanMethod.java
@@ -30,9 +30,14 @@
 /**
  * Common utility code for matching {@link JMethod} and against bean-style
  * accessor semantics.
- * 
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @see com.google.gwt.autobean.server.impl.BeanMethod
  */
+@Deprecated
 public enum JBeanMethod {
   GET {
     @Override
@@ -151,4 +156,4 @@
    * Returns {@code true} if the BeanLikeMethod matches the method.
    */
   public abstract boolean matches(JMethod method);
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/autobean/server/AutoBeanFactoryMagic.java b/user/src/com/google/gwt/autobean/server/AutoBeanFactoryMagic.java
index 6ab47d1..46c5312 100644
--- a/user/src/com/google/gwt/autobean/server/AutoBeanFactoryMagic.java
+++ b/user/src/com/google/gwt/autobean/server/AutoBeanFactoryMagic.java
@@ -31,7 +31,14 @@
  * 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'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
+ * @deprecated Replaced by {@link com.google.web.bindery.autobean.vm.AutoBeanFactorySource}
  */
+@Deprecated
 public class AutoBeanFactoryMagic {
   /*
    * NB: This implementation is excessively dynamic, however the inability to
diff --git a/user/src/com/google/gwt/autobean/server/Configuration.java b/user/src/com/google/gwt/autobean/server/Configuration.java
index 8e96c36..ba95ce7 100644
--- a/user/src/com/google/gwt/autobean/server/Configuration.java
+++ b/user/src/com/google/gwt/autobean/server/Configuration.java
@@ -28,11 +28,21 @@
  * Used by {@link AutoBeanFactoryMagic#createBean(Class, Configuration)}. This
  * type replicates the annotations that may be applied to an AutoBeanFactory
  * declaration.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class Configuration {
   /**
    * Builds {@link Configuration} objects.
+   *
+   * <p><span style='color:red'>AutoBeans has moved to
+   * <code>com.google.web.bindery.autobeans</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
    */
+  @Deprecated
   public static class Builder {
     private Configuration toReturn = new Configuration();
 
diff --git a/user/src/com/google/gwt/autobean/server/impl/BeanMethod.java b/user/src/com/google/gwt/autobean/server/impl/BeanMethod.java
index 59e90ed..6591813 100644
--- a/user/src/com/google/gwt/autobean/server/impl/BeanMethod.java
+++ b/user/src/com/google/gwt/autobean/server/impl/BeanMethod.java
@@ -24,9 +24,14 @@
 /**
  * Breakout of method types that an AutoBean shim interface can implement. The
  * order of the values of the enum is important.
- * 
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @see com.google.gwt.autobean.rebind.model.JBeanMethod
  */
+@Deprecated
 public enum BeanMethod {
   /**
    * Methods defined in Object.
@@ -226,4 +231,4 @@
    * Determine if the method maches the given type.
    */
   abstract boolean matches(SimpleBeanHandler<?> handler, Method method);
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/autobean/server/impl/BeanPropertyContext.java b/user/src/com/google/gwt/autobean/server/impl/BeanPropertyContext.java
index 8b1a7a5..4d0013a 100644
--- a/user/src/com/google/gwt/autobean/server/impl/BeanPropertyContext.java
+++ b/user/src/com/google/gwt/autobean/server/impl/BeanPropertyContext.java
@@ -22,7 +22,12 @@
 /**
  * A property context that allows setters to be called on a simple peer,
  * regardless of whether or not the interface actually has a setter.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 class BeanPropertyContext extends MethodPropertyContext {
   private final String propertyName;
   private final Map<String, Object> map;
diff --git a/user/src/com/google/gwt/autobean/server/impl/FactoryHandler.java b/user/src/com/google/gwt/autobean/server/impl/FactoryHandler.java
index 4d21c6d..fc1f883 100644
--- a/user/src/com/google/gwt/autobean/server/impl/FactoryHandler.java
+++ b/user/src/com/google/gwt/autobean/server/impl/FactoryHandler.java
@@ -27,7 +27,12 @@
 
 /**
  * Handles dispatches on AutoBeanFactory interfaces.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class FactoryHandler implements InvocationHandler {
   private final Configuration configuration;
 
@@ -126,4 +131,4 @@
       return e.name();
     }
   }
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/autobean/server/impl/GetterPropertyContext.java b/user/src/com/google/gwt/autobean/server/impl/GetterPropertyContext.java
index ba7814d..dd850af 100644
--- a/user/src/com/google/gwt/autobean/server/impl/GetterPropertyContext.java
+++ b/user/src/com/google/gwt/autobean/server/impl/GetterPropertyContext.java
@@ -21,7 +21,12 @@
 
 /**
  * Used by {@link ProxyAutoBean#traverseProperties()}.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 class GetterPropertyContext extends MethodPropertyContext {
   private final Method setter;
   private final Object shim;
@@ -66,4 +71,4 @@
       throw new RuntimeException(e.getCause());
     }
   }
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/autobean/server/impl/JsonSplittable.java b/user/src/com/google/gwt/autobean/server/impl/JsonSplittable.java
index 6dd80e3..2ce6c20 100644
--- a/user/src/com/google/gwt/autobean/server/impl/JsonSplittable.java
+++ b/user/src/com/google/gwt/autobean/server/impl/JsonSplittable.java
@@ -28,7 +28,12 @@
 
 /**
  * Uses the org.json packages to slice and dice request payloads.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class JsonSplittable implements Splittable {
   public static Splittable create(String payload) {
     try {
@@ -173,4 +178,4 @@
     }
     return new JsonSplittable(object.toString());
   }
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/autobean/server/impl/MethodPropertyContext.java b/user/src/com/google/gwt/autobean/server/impl/MethodPropertyContext.java
index efbf8f7..e3aae3b 100644
--- a/user/src/com/google/gwt/autobean/server/impl/MethodPropertyContext.java
+++ b/user/src/com/google/gwt/autobean/server/impl/MethodPropertyContext.java
@@ -28,7 +28,12 @@
 /**
  * A base type to handle analyzing the return value of a getter method. The
  * accessor methods are implemented in subtypes.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 abstract class MethodPropertyContext implements CollectionPropertyContext,
     MapPropertyContext {
   private static class Data {
diff --git a/user/src/com/google/gwt/autobean/server/impl/ProxyAutoBean.java b/user/src/com/google/gwt/autobean/server/impl/ProxyAutoBean.java
index 4c93baa..cbcc999 100644
--- a/user/src/com/google/gwt/autobean/server/impl/ProxyAutoBean.java
+++ b/user/src/com/google/gwt/autobean/server/impl/ProxyAutoBean.java
@@ -36,9 +36,14 @@
 
 /**
  * An implementation of an AutoBean that uses reflection.
- * 
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @param <T> the type of interface being wrapped
  */
+@Deprecated
 public class ProxyAutoBean<T> extends AbstractAutoBean<T> {
   private static class Data {
     final List<Method> getters = new ArrayList<Method>();
@@ -322,4 +327,4 @@
     WeakMapping.set(toReturn, AutoBean.class.getName(), this);
     return toReturn;
   }
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/autobean/server/impl/ShimHandler.java b/user/src/com/google/gwt/autobean/server/impl/ShimHandler.java
index 23c190d..273a53f 100644
--- a/user/src/com/google/gwt/autobean/server/impl/ShimHandler.java
+++ b/user/src/com/google/gwt/autobean/server/impl/ShimHandler.java
@@ -25,9 +25,14 @@
 /**
  * Implements an AutoBean's shim interface that intercepts calls to the backing
  * object.
- * 
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @param <T> the interface type of the AutoBean
  */
+@Deprecated
 class ShimHandler<T> implements InvocationHandler {
   private final ProxyAutoBean<T> bean;
   private final Method interceptor;
@@ -128,4 +133,4 @@
         bean.getFactory(), intf, bean.getConfiguration(), toReturn);
     return newBean.as();
   }
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/autobean/server/impl/SimpleBeanHandler.java b/user/src/com/google/gwt/autobean/server/impl/SimpleBeanHandler.java
index d25468d..2f39e33 100644
--- a/user/src/com/google/gwt/autobean/server/impl/SimpleBeanHandler.java
+++ b/user/src/com/google/gwt/autobean/server/impl/SimpleBeanHandler.java
@@ -20,9 +20,14 @@
 
 /**
  * Dynamic implementation of an AutoBean's simple peer object.
- * 
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @param <T> the type of interface the shim allows access to
  */
+@Deprecated
 class SimpleBeanHandler<T> implements InvocationHandler {
   private final ProxyAutoBean<T> bean;
 
@@ -55,4 +60,4 @@
   public String toString() {
     return bean.getPropertyMap().toString();
   }
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/autobean/server/impl/TypeUtils.java b/user/src/com/google/gwt/autobean/server/impl/TypeUtils.java
index c7c64ab..db60e62 100644
--- a/user/src/com/google/gwt/autobean/server/impl/TypeUtils.java
+++ b/user/src/com/google/gwt/autobean/server/impl/TypeUtils.java
@@ -34,7 +34,12 @@
 /**
  * Shared code for answering question about Class objects. This is a
  * server-compatible analog to ModelUtils.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class TypeUtils {
   static final Map<Class<?>, Class<?>> AUTOBOX_MAP;
   static final Map<Class<?>, Object> DEFAULT_PRIMITIVE_VALUES;
diff --git a/user/src/com/google/gwt/autobean/server/package-info.java b/user/src/com/google/gwt/autobean/server/package-info.java
index 88e1b04..d761402 100644
--- a/user/src/com/google/gwt/autobean/server/package-info.java
+++ b/user/src/com/google/gwt/autobean/server/package-info.java
@@ -16,13 +16,17 @@
 
 /**
  * Contains JVM-compatible implementations of the AutoBean framework.
- * 
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @see <a
  *      href="http://code.google.com/p/google-web-toolkit/wiki/AutoBean">AutoBean
  *      wiki page</a>
  * @see com.google.gwt.autobean.shared.AutoBeanFactory
  * @see com.google.gwt.autobean.server.AutoBeanFactoryMagic
  */
+@Deprecated
 @com.google.gwt.util.PreventSpuriousRebuilds
 package com.google.gwt.autobean.server;
-
diff --git a/user/src/com/google/gwt/autobean/shared/AutoBean.java b/user/src/com/google/gwt/autobean/shared/AutoBean.java
index 8b8613f..b265aac 100644
--- a/user/src/com/google/gwt/autobean/shared/AutoBean.java
+++ b/user/src/com/google/gwt/autobean/shared/AutoBean.java
@@ -24,9 +24,14 @@
 /**
  * A controller for an implementation of a bean interface. Instances of
  * AutoBeans are obtained from an {@link AutoBeanFactory}.
- * 
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @param <T> the type of interface that will be wrapped.
  */
+@Deprecated
 public interface AutoBean<T> {
   /**
    * An annotation that allows inferred property names to be overridden.
@@ -34,7 +39,12 @@
    * 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.
+   *
+   * <p><span style='color:red'>AutoBeans has moved to
+   * <code>com.google.web.bindery.autobeans</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
    */
+  @Deprecated
   @Documented
   @Retention(RetentionPolicy.RUNTIME)
   @Target(value = {ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
diff --git a/user/src/com/google/gwt/autobean/shared/AutoBeanCodex.java b/user/src/com/google/gwt/autobean/shared/AutoBeanCodex.java
index bcded2c..bf73dd9 100644
--- a/user/src/com/google/gwt/autobean/shared/AutoBeanCodex.java
+++ b/user/src/com/google/gwt/autobean/shared/AutoBeanCodex.java
@@ -34,13 +34,23 @@
  * 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.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class AutoBeanCodex {
 
   /**
    * Describes a means of encoding or decoding a particular type of data to or
    * from a wire format representation.
+   *
+   * <p><span style='color:red'>AutoBeans has moved to
+   * <code>com.google.web.bindery.autobeans</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
    */
+  @Deprecated
   interface Coder {
     Object decode(Splittable data);
 
@@ -148,7 +158,12 @@
 
   /**
    * Used to stop processing.
+   *
+   * <p><span style='color:red'>AutoBeans has moved to
+   * <code>com.google.web.bindery.autobeans</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
    */
+  @Deprecated
   static class HaltException extends RuntimeException {
     public HaltException(RuntimeException cause) {
       super(cause);
diff --git a/user/src/com/google/gwt/autobean/shared/AutoBeanFactory.java b/user/src/com/google/gwt/autobean/shared/AutoBeanFactory.java
index 5f1a8f9..5bf39fe 100644
--- a/user/src/com/google/gwt/autobean/shared/AutoBeanFactory.java
+++ b/user/src/com/google/gwt/autobean/shared/AutoBeanFactory.java
@@ -28,7 +28,7 @@
  * 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
@@ -37,11 +37,16 @@
  *   AutoBean&lt;ArbitraryInterface> wrapper(ArbitraryInterface delegate);
  * }
  * </pre>
- * 
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @see <a
  *      href="http://code.google.com/p/google-web-toolkit/wiki/AutoBean">AutoBean
  *      wiki page</a>
  */
+@Deprecated
 public interface AutoBeanFactory {
   /**
    * Allows non-property methods on simple bean implementations when applied.
@@ -73,7 +78,12 @@
    *   AutoBean&lt;HasMethod> hasMethod();
    * }
    * </pre>
+   *
+   * <p><span style='color:red'>AutoBeans has moved to
+   * <code>com.google.web.bindery.autobeans</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
    */
+  @Deprecated
   @Documented
   @Retention(RetentionPolicy.RUNTIME)
   @Target(ElementType.TYPE)
@@ -84,7 +94,12 @@
   /**
    * The types specified by this annotation will not be wrapped by an AutoBean
    * when returned from an AutoBean-controlled method.
+   *
+   * <p><span style='color:red'>AutoBeans has moved to
+   * <code>com.google.web.bindery.autobeans</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
    */
+  @Deprecated
   @Retention(RetentionPolicy.RUNTIME)
   @Target(ElementType.TYPE)
   public @interface NoWrap {
diff --git a/user/src/com/google/gwt/autobean/shared/AutoBeanUtils.java b/user/src/com/google/gwt/autobean/shared/AutoBeanUtils.java
index ed1930f..3adcc47 100644
--- a/user/src/com/google/gwt/autobean/shared/AutoBeanUtils.java
+++ b/user/src/com/google/gwt/autobean/shared/AutoBeanUtils.java
@@ -29,7 +29,12 @@
 
 /**
  * Utility methods for working with AutoBeans.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public final class AutoBeanUtils {
   /*
    * TODO(bobv): Make Comparison a real type that holds a map contain the diff
diff --git a/user/src/com/google/gwt/autobean/shared/AutoBeanVisitor.java b/user/src/com/google/gwt/autobean/shared/AutoBeanVisitor.java
index 39358ac..931ce30 100644
--- a/user/src/com/google/gwt/autobean/shared/AutoBeanVisitor.java
+++ b/user/src/com/google/gwt/autobean/shared/AutoBeanVisitor.java
@@ -20,12 +20,22 @@
 
 /**
  * Allows traversal of an AutoBean object graph.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class AutoBeanVisitor {
   /**
    * A PropertyContext that describes the parameterization of the Collection
    * being visited.
+   *
+   * <p><span style='color:red'>AutoBeans has moved to
+   * <code>com.google.web.bindery.autobeans</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
    */
+  @Deprecated
   public interface CollectionPropertyContext extends PropertyContext {
     /**
      * Returns the collection's element type.
@@ -37,14 +47,24 @@
 
   /**
    * Reserved for future expansion to avoid API breaks.
+   *
+   * <p><span style='color:red'>AutoBeans has moved to
+   * <code>com.google.web.bindery.autobeans</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
    */
+  @Deprecated
   public interface Context {
   }
 
   /**
    * A PropertyContext that describes the parameterization of the Map being
    * visited.
+   *
+   * <p><span style='color:red'>AutoBeans has moved to
+   * <code>com.google.web.bindery.autobeans</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
    */
+  @Deprecated
   public interface MapPropertyContext extends PropertyContext {
     /**
      * Returns the map's key type.
@@ -85,7 +105,12 @@
    *   endVisitParameter();
    * endVisitType(Map.class);
    * </pre>
+   *
+   * <p><span style='color:red'>AutoBeans has moved to
+   * <code>com.google.web.bindery.autobeans</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
    */
+  @Deprecated
   public static class ParameterizationVisitor {
     /**
      * Called when finished with a type parameter.
@@ -120,7 +145,12 @@
 
   /**
    * Allows properties to be reset.
+   *
+   * <p><span style='color:red'>AutoBeans has moved to
+   * <code>com.google.web.bindery.autobeans</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
    */
+  @Deprecated
   public interface PropertyContext {
     /**
      * Allows deeper inspection of the declared parameterization of the
diff --git a/user/src/com/google/gwt/autobean/shared/Splittable.java b/user/src/com/google/gwt/autobean/shared/Splittable.java
index 65a2286..357c5c9 100644
--- a/user/src/com/google/gwt/autobean/shared/Splittable.java
+++ b/user/src/com/google/gwt/autobean/shared/Splittable.java
@@ -21,7 +21,12 @@
  * This interface provides an abstraction around the underlying data model
  * (JavaScriptObject, {@code org.json}, or XML) used to encode an AutoBeanCodex
  * payload.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public interface Splittable {
   /**
    * Returns a string representation of the data.
@@ -81,4 +86,4 @@
    * Returns the size of the list.
    */
   int size();
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/autobean/shared/ValueCodex.java b/user/src/com/google/gwt/autobean/shared/ValueCodex.java
index b4fb321..b8dc1e0 100644
--- a/user/src/com/google/gwt/autobean/shared/ValueCodex.java
+++ b/user/src/com/google/gwt/autobean/shared/ValueCodex.java
@@ -28,8 +28,19 @@
 
 /**
  * Provides unified encoding and decoding of value objects.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class ValueCodex {
+  /**
+   * <p><span style='color:red'>AutoBeans has moved to
+   * <code>com.google.web.bindery.autobeans</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
+   */
+  @Deprecated
   enum Type {
     BIG_DECIMAL(BigDecimal.class) {
       @Override
diff --git a/user/src/com/google/gwt/autobean/shared/ValueCodexHelper.java b/user/src/com/google/gwt/autobean/shared/ValueCodexHelper.java
index c6f72c8..dbad68b 100644
--- a/user/src/com/google/gwt/autobean/shared/ValueCodexHelper.java
+++ b/user/src/com/google/gwt/autobean/shared/ValueCodexHelper.java
@@ -20,7 +20,12 @@
 /**
  * Provides reflection-based operation for server (JVM) implementation. There is
  * a no-op super-source version for client (dev- and web-mode) code.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 class ValueCodexHelper {
   /**
    * Returns {@code true} if {@code clazz} is assignable to any of the value
diff --git a/user/src/com/google/gwt/autobean/shared/impl/AbstractAutoBean.java b/user/src/com/google/gwt/autobean/shared/impl/AbstractAutoBean.java
index ad2dc20..27d2bc7 100644
--- a/user/src/com/google/gwt/autobean/shared/impl/AbstractAutoBean.java
+++ b/user/src/com/google/gwt/autobean/shared/impl/AbstractAutoBean.java
@@ -29,13 +29,23 @@
 
 /**
  * Basic implementation.
- * 
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @param <T> the wrapper type
  */
+@Deprecated
 public abstract class AbstractAutoBean<T> implements AutoBean<T> {
   /**
    * Used to avoid cycles when visiting.
+   *
+   * <p><span style='color:red'>AutoBeans has moved to
+   * <code>com.google.web.bindery.autobeans</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
    */
+  @Deprecated
   public static class OneShotContext implements Context {
     private final Set<AbstractAutoBean<?>> seen = new HashSet<AbstractAutoBean<?>>();
 
diff --git a/user/src/com/google/gwt/autobean/shared/impl/EnumMap.java b/user/src/com/google/gwt/autobean/shared/impl/EnumMap.java
index 3b53d1b..f00ae90 100644
--- a/user/src/com/google/gwt/autobean/shared/impl/EnumMap.java
+++ b/user/src/com/google/gwt/autobean/shared/impl/EnumMap.java
@@ -18,11 +18,21 @@
 /**
  * This interface is implemented by our generated AutoBeanFactory types to
  * convert enum values to strings.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public interface EnumMap {
   /**
    * Extra enums that should be included in the AutoBeanFactory.
+   *
+   * <p><span style='color:red'>AutoBeans has moved to
+   * <code>com.google.web.bindery.autobeans</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
    */
+  @Deprecated
   public @interface ExtraEnums {
     Class<? extends Enum<?>>[] value();
   }
diff --git a/user/src/com/google/gwt/autobean/shared/impl/LazySplittable.java b/user/src/com/google/gwt/autobean/shared/impl/LazySplittable.java
index 2fb4749..5a17a71 100644
--- a/user/src/com/google/gwt/autobean/shared/impl/LazySplittable.java
+++ b/user/src/com/google/gwt/autobean/shared/impl/LazySplittable.java
@@ -22,7 +22,12 @@
 /**
  * Holds a string payload with the expectation that the object will be used only
  * for creating a larger payload.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class LazySplittable implements Splittable {
   public static final Splittable NULL = new LazySplittable("null");
 
diff --git a/user/src/com/google/gwt/autobean/shared/impl/StringQuoter.java b/user/src/com/google/gwt/autobean/shared/impl/StringQuoter.java
index 899ed52..8568db7 100644
--- a/user/src/com/google/gwt/autobean/shared/impl/StringQuoter.java
+++ b/user/src/com/google/gwt/autobean/shared/impl/StringQuoter.java
@@ -28,7 +28,12 @@
 
 /**
  * This class has a super-source version with a client-only implementation.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class StringQuoter {
   private static final String ISO8601_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSz";
   private static final DateFormat ISO8601 = new SimpleDateFormat(
diff --git a/user/src/com/google/gwt/autobean/shared/package-info.java b/user/src/com/google/gwt/autobean/shared/package-info.java
index 5c13167..9812599 100644
--- a/user/src/com/google/gwt/autobean/shared/package-info.java
+++ b/user/src/com/google/gwt/autobean/shared/package-info.java
@@ -19,13 +19,17 @@
  * 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.
- * 
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @see <a
  *      href="http://code.google.com/p/google-web-toolkit/wiki/AutoBean">AutoBean
  *      wiki page</a>
  * @see com.google.gwt.autobean.shared.AutoBeanFactory
  * @see com.google.gwt.autobean.server.AutoBeanFactoryMagic
  */
+@Deprecated
 @com.google.gwt.util.PreventSpuriousRebuilds
 package com.google.gwt.autobean.shared;
-
diff --git a/user/src/com/google/gwt/core/client/JsonUtils.java b/user/src/com/google/gwt/core/client/JsonUtils.java
index 4fd9942..7088266 100644
--- a/user/src/com/google/gwt/core/client/JsonUtils.java
+++ b/user/src/com/google/gwt/core/client/JsonUtils.java
@@ -19,10 +19,8 @@
  * Provides JSON-related utility methods.
  */
 public class JsonUtils {
-  @SuppressWarnings("unused")
   private static JavaScriptObject escapeTable = initEscapeTable();
 
-  @SuppressWarnings("unused")
   private static final boolean hasJsonParse = hasJsonParse();
 
   /**
@@ -62,17 +60,17 @@
       try {
         return JSON.parse(json);
       } catch (e) {
-        return @com.google.gwt.core.client.JsonUtils::throwIllegalArgumentException(Ljava/lang/String;)("Error parsing JSON: " + e);
+        return @com.google.gwt.core.client.JsonUtils::throwIllegalArgumentException(*)("Error parsing JSON: " + e, json);
       }
     } else {
       if (!@com.google.gwt.core.client.JsonUtils::safeToEval(Ljava/lang/String;)(json)) {
-        return @com.google.gwt.core.client.JsonUtils::throwIllegalArgumentException(Ljava/lang/String;)("Illegal character in JSON string");
+        return @com.google.gwt.core.client.JsonUtils::throwIllegalArgumentException(*)("Illegal character in JSON string", json);
       }
       json = @com.google.gwt.core.client.JsonUtils::escapeJsonForEval(Ljava/lang/String;)(json);
       try {
         return eval('(' + json + ')');
       } catch (e) {
-        return @com.google.gwt.core.client.JsonUtils::throwIllegalArgumentException(Ljava/lang/String;)("Error parsing JSON: " + e);
+        return @com.google.gwt.core.client.JsonUtils::throwIllegalArgumentException(*)("Error parsing JSON: " + e, json);
       }
     }
   }-*/;
@@ -112,15 +110,14 @@
     try {
       return eval('(' + escaped + ')');
     } catch (e) {
-      return @com.google.gwt.core.client.JsonUtils::throwIllegalArgumentException(Ljava/lang/String;)("Error parsing JSON: " + e);
+      return @com.google.gwt.core.client.JsonUtils::throwIllegalArgumentException(*)("Error parsing JSON: " + e, json);
     }
   }-*/;
 
-  static void throwIllegalArgumentException(String message) {
-    throw new IllegalArgumentException(message);
+  static void throwIllegalArgumentException(String message, String data) {
+    throw new IllegalArgumentException(message + "\n" + data);
   }
 
-  @SuppressWarnings("unused")
   private static native String escapeChar(String c) /*-{
     var lookedUp = @com.google.gwt.core.client.JsonUtils::escapeTable[c.charCodeAt(0)];
     return (lookedUp == null) ? c : lookedUp;
diff --git a/user/src/com/google/gwt/editor/client/EditorDriver.java b/user/src/com/google/gwt/editor/client/EditorDriver.java
index 8fff6c6..414c8e2 100644
--- a/user/src/com/google/gwt/editor/client/EditorDriver.java
+++ b/user/src/com/google/gwt/editor/client/EditorDriver.java
@@ -28,7 +28,7 @@
  * 
  * @param <T> the type of data returned from {@link #flush()}
  * @see com.google.gwt.editor.client.SimpleBeanEditorDriver
- * @see com.google.gwt.requestfactory.client.RequestFactoryEditorDriver
+ * @see com.google.web.bindery.requestfactory.gwt.client.RequestFactoryEditorDriver
  */
 public interface EditorDriver<T> {
   /**
@@ -59,7 +59,7 @@
 
   /**
    * Returns {@code true} if any of the Editors in the hierarchy have been
-   * modified relative to the last value passed into {@link #edit(Object)}.
+   * modified relative to the last value passed into {@link SimpleBeanEditorDriver#edit(Object)}.
    * <p>
    * This method is not affected by {@link #flush()} to support the following
    * workflow:
diff --git a/user/src/com/google/gwt/editor/rebind/model/ModelUtils.java b/user/src/com/google/gwt/editor/rebind/model/ModelUtils.java
index 310b9dd..241a1c5 100644
--- a/user/src/com/google/gwt/editor/rebind/model/ModelUtils.java
+++ b/user/src/com/google/gwt/editor/rebind/model/ModelUtils.java
@@ -15,7 +15,7 @@
  */
 package com.google.gwt.editor.rebind.model;
 
-import com.google.gwt.autobean.shared.ValueCodex;
+import com.google.web.bindery.autobean.shared.ValueCodex;
 import com.google.gwt.core.ext.typeinfo.JArrayType;
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.JParameterizedType;
diff --git a/user/src/com/google/gwt/event/Event.gwt.xml b/user/src/com/google/gwt/event/Event.gwt.xml
index 3dafaf8..14f6dab 100644
--- a/user/src/com/google/gwt/event/Event.gwt.xml
+++ b/user/src/com/google/gwt/event/Event.gwt.xml
@@ -1,5 +1,20 @@
+<!--
+  Copyright 2008 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.
+-->
 <module>
-	<inherits name="com.google.gwt.event.EventBase" />
-	<inherits name="com.google.gwt.event.dom.DomEvent" />
-	<inherits name="com.google.gwt.event.logical.LogicalEvent" />
+  <inherits name="com.google.gwt.event.EventBase" />
+  <inherits name="com.google.gwt.event.dom.DomEvent" />
+  <inherits name="com.google.gwt.event.logical.LogicalEvent" />
 </module>
diff --git a/user/src/com/google/gwt/event/EventBase.gwt.xml b/user/src/com/google/gwt/event/EventBase.gwt.xml
index 9f0de72..895f0ee 100644
--- a/user/src/com/google/gwt/event/EventBase.gwt.xml
+++ b/user/src/com/google/gwt/event/EventBase.gwt.xml
@@ -1,5 +1,21 @@
+<!--
+  Copyright 2008 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.
+-->
 <module>
-	<inherits name="com.google.gwt.core.Core" />
+  <inherits name="com.google.web.bindery.event.Event" />
+  <inherits name="com.google.gwt.core.Core" />
 
-	<source path="shared" />
+  <source path="shared" />
 </module>
diff --git a/user/src/com/google/gwt/event/shared/EventBus.java b/user/src/com/google/gwt/event/shared/EventBus.java
index 74460c3..267cef4 100644
--- a/user/src/com/google/gwt/event/shared/EventBus.java
+++ b/user/src/com/google/gwt/event/shared/EventBus.java
@@ -15,81 +15,71 @@
  */
 package com.google.gwt.event.shared;
 
-import com.google.gwt.event.shared.GwtEvent.Type;
+import com.google.web.bindery.event.shared.Event;
 
 /**
- * Dispatches {@link GwtEvent}s to interested parties. Eases decoupling by
- * allowing objects to interact without having direct dependencies upon one
- * another, and without requiring event sources to deal with maintaining handler
- * lists. There will typically be one EventBus per application, broadcasting
- * events that may be of general interest.
- * 
- * @see SimpleEventBus
- * @see ResettableEventBus
- * @see com.google.gwt.event.shared.testing.CountingEventBus
+ * Extends {com.google.web.bindery.event.shared.EventBus} for legacy
+ * compatibility.
  */
-public abstract class EventBus implements HasHandlers {
+public abstract class EventBus extends com.google.web.bindery.event.shared.EventBus implements
+    HasHandlers {
 
-  /**
-   * Adds an unfiltered handler to receive events of this type from all sources.
-   * <p>
-   * It is rare to call this method directly. More typically a {@link GwtEvent}
-   * subclass will provide a static <code>register</code> method, or a widget
-   * will accept handlers directly.
-   * <p>
-   * A tip: to make a handler de-register itself, the following works:
-   * <code><pre>new MyHandler() {
-   *  HandlerRegistration reg = MyEvent.register(eventBus, this);
-   * 
-   *  public void onMyThing(MyEvent event) {
-   *    {@literal /}* do your thing *{@literal /}
-   *    reg.removeHandler();
-   *  }
-   * };
-   * </pre></code>
-   * 
-   * @param <H> The type of handler
-   * @param type the event type associated with this handler
-   * @param handler the handler
-   * @return the handler registration, can be stored in order to remove the
-   *         handler later
-   */
-  public abstract <H extends EventHandler> HandlerRegistration addHandler(
-      Type<H> type, H handler);
+  @Override
+  public <H> com.google.web.bindery.event.shared.HandlerRegistration addHandler(Event.Type<H> type, H handler) {
+    throw new UnsupportedOperationException("Subclass responsibility. "
+        + "This class is a legacy wrapper for com.google.web.bindery.event.shared.EventBus. "
+        + "Use that directly, or try com.google.gwt.event.shared.SimpleEventBus");
+  }
+  
+  public abstract <H extends EventHandler> HandlerRegistration addHandler(GwtEvent.Type<H> type, H handler);
 
-  /**
-   * Adds a handler to receive events of this type from the given source.
-   * <p>
-   * It is rare to call this method directly. More typically a {@link GwtEvent}
-   * subclass will provide a static <code>register</code> method, or a widget
-   * will accept handlers directly.
-   * 
-   * @param <H> The type of handler
-   * @param type the event type associated with this handler
-   * @param source the source associated with this handler
-   * @param handler the handler
-   * @return the handler registration, can be stored in order to remove the
-   *         handler later
-   */
-  public abstract <H extends EventHandler> HandlerRegistration addHandlerToSource(
-      Type<H> type, Object source, H handler);
+  @Override
+  public <H> com.google.web.bindery.event.shared.HandlerRegistration addHandlerToSource(Event.Type<H> type,
+      Object source, H handler) {
+    throw new UnsupportedOperationException("Subclass responsibility. "
+        + "This class is a legacy wrapper for com.google.web.bindery.event.shared.EventBus. "
+        + "Use that directly, or try com.google.gwt.event.shared.SimpleEventBus");
+  }
 
-  /**
-   * Fires the event from no source. Only unfiltered handlers will receive it.
-   * 
-   * @param event the event to fire
-   */
+  public abstract <H extends EventHandler> HandlerRegistration addHandlerToSource(GwtEvent.Type<H> type,
+      Object source, H handler);
+
+  @Override
+  public void fireEvent(Event<?> event) {
+    throw new UnsupportedOperationException("Subclass responsibility. "
+        + "This class is a legacy wrapper for com.google.web.bindery.event.shared.EventBus. "
+        + "Use that directly, or try com.google.gwt.event.shared.SimpleEventBus");
+  }
+
   public abstract void fireEvent(GwtEvent<?> event);
 
-  /**
-   * Fires the given event to the handlers listening to the event's type.
-   * <p>
-   * Any exceptions thrown by handlers will be bundled into a
-   * {@link UmbrellaException} and then re-thrown after all handlers have
-   * completed. An exception thrown by a handler will not prevent other handlers
-   * from executing.
-   * 
-   * @param event the event to fire
-   */
+
+  @Override
+  public void fireEventFromSource(Event<?> event, Object source) {
+    throw new UnsupportedOperationException("Subclass responsibility. "
+        + "This class is a legacy wrapper for com.google.web.bindery.event.shared.EventBus. "
+        + "Use that directly, or try com.google.gwt.event.shared.SimpleEventBus");
+  }
+
   public abstract void fireEventFromSource(GwtEvent<?> event, Object source);
+
+  protected void castFireEvent(GwtEvent<?> event) {
+    try {
+      fireEvent((Event<?>) event);
+    } catch (com.google.web.bindery.event.shared.UmbrellaException e) {
+      throw new UmbrellaException(e.getCauses());
+    }
+  }
+
+  protected void castFireEventFromSource(GwtEvent<?> event, Object source) {
+    try {
+      fireEventFromSource((Event<?>) event, source);
+    } catch (com.google.web.bindery.event.shared.UmbrellaException e) {
+      throw new UmbrellaException(e.getCauses());
+    }
+  }
+
+  protected HandlerRegistration wrap(com.google.web.bindery.event.shared.HandlerRegistration reg) {
+    return new LegacyHandlerWrapper(reg);
+  }
 }
diff --git a/user/src/com/google/gwt/event/shared/EventHandler.java b/user/src/com/google/gwt/event/shared/EventHandler.java
index 2558629..7860744 100644
--- a/user/src/com/google/gwt/event/shared/EventHandler.java
+++ b/user/src/com/google/gwt/event/shared/EventHandler.java
@@ -16,8 +16,8 @@
 package com.google.gwt.event.shared;
 
 /**
- * Marker interface for event handlers. All GWT event handlers should extend
- * {@link EventHandler}.
+ * Marker interface for event handlers. All stock GWT Widget and dom event
+ * handlers extend {@link EventHandler}.
  */
 public interface EventHandler {
 
diff --git a/user/src/com/google/gwt/event/shared/GwtEvent.java b/user/src/com/google/gwt/event/shared/GwtEvent.java
index ef74c39..a4a8465 100644
--- a/user/src/com/google/gwt/event/shared/GwtEvent.java
+++ b/user/src/com/google/gwt/event/shared/GwtEvent.java
@@ -15,16 +15,20 @@
  */
 package com.google.gwt.event.shared;
 
+import com.google.web.bindery.event.shared.Event;
+
 /**
- * Root of all GWT events. All GWT events are considered dead and should no
- * longer be accessed once the {@link HandlerManager} which originally fired the
- * event finishes with it. That is, don't hold on to event objects outside of
- * your handler methods.
+ * Root of all GWT widget and dom events sourced by a {@link HandlerManager}.
+ * All GWT events are considered dead and should no longer be accessed once the
+ * {@link HandlerManager} which originally fired the event finishes with it.
+ * That is, don't hold on to event objects outside of your handler methods.
+ * <p>
+ * There is no need for an application's custom event types to extend GwtEvent.
+ * Prefer {@link Event} instead.
  * 
  * @param <H> handler type
- * 
  */
-public abstract class GwtEvent<H extends EventHandler> {
+public abstract class GwtEvent<H extends EventHandler> extends Event<H> {
   /**
    * Type class used to register events with the {@link HandlerManager}.
    * <p>
@@ -34,79 +38,24 @@
    * 
    * @param <H> handler type
    */
-  public static class Type<H> {
-    private static int nextHashCode;
-    private final int index;
-
-    /**
-     * Constructor.
-     */
-    public Type() {
-      index = ++nextHashCode;
-    }
-
-    // We override hash code to make it as efficient as possible.
-    @Override
-    public final int hashCode() {
-      return index;
-    }
-
-    @Override
-    public String toString() {
-      return "Event type";
-    }
+  public static class Type<H> extends com.google.web.bindery.event.shared.Event.Type<H> {
   }
 
   private boolean dead;
 
-  private Object source;
-
   /**
    * Constructor.
    */
   protected GwtEvent() {
   }
 
-  /**
-   * Returns the type used to register this event. Used by handler manager to
-   * dispatch events to the correct handlers.
-   * 
-   * @return the type
-   */
-  public abstract Type<H> getAssociatedType();
+  @Override
+  public abstract GwtEvent.Type<H> getAssociatedType();
 
-  /**
-   * Returns the source that last fired this event.
-   * 
-   * @return object representing the source of this event
-   */
+  @Override
   public Object getSource() {
     assertLive();
-    return source;
-  }
-
-  /**
-   * This is a method used primarily for debugging. It gives a string
-   * representation of the event details. This does not override the toString
-   * method because the compiler cannot always optimize toString out correctly.
-   * Event types should override as desired.
-   * 
-   * @return a string representing the event's specifics.
-   */
-  public String toDebugString() {
-    String name = this.getClass().getName();
-    name = name.substring(name.lastIndexOf(".") + 1);
-    return "event: " + name + ":";
-  }
-
-  /**
-   * The toString() for abstract event is overridden to avoid accidently
-   * including class literals in the the compiled output. Use {@link GwtEvent}
-   * #toDebugString to get more information about the event.
-   */
-  @Override
-  public String toString() {
-    return "An event type";
+    return super.getSource();
   }
 
   /**
@@ -141,7 +90,7 @@
    */
   protected void kill() {
     dead = true;
-    source = null;
+    setSource(null);
   }
 
   /**
@@ -149,16 +98,10 @@
    */
   protected void revive() {
     dead = false;
-    source = null;
+    setSource(null);
   }
 
-  /**
-   * Set the source that triggered this event.
-   * 
-   * @param source the source of this event, should only be set by a
-   *          {@link HandlerManager}
-   */
-  void setSource(Object source) {
-    this.source = source;
+  void overrideSource(Object source) {
+    super.setSource(source);
   }
 }
diff --git a/user/src/com/google/gwt/event/shared/HandlerManager.java b/user/src/com/google/gwt/event/shared/HandlerManager.java
index 73d181e..9f4d350 100644
--- a/user/src/com/google/gwt/event/shared/HandlerManager.java
+++ b/user/src/com/google/gwt/event/shared/HandlerManager.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2009 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
@@ -15,7 +15,7 @@
  */
 package com.google.gwt.event.shared;
 
-import com.google.gwt.event.shared.GwtEvent.Type;
+import com.google.web.bindery.event.shared.Event;
 
 /**
  * Manager responsible for adding handlers to event sources and firing those
@@ -25,13 +25,40 @@
  * While widget authors should continue to use
  * {@link com.google.gwt.user.client.ui.Widget#addDomHandler(EventHandler, com.google.gwt.event.dom.client.DomEvent.Type)}
  * and
- * {@link com.google.gwt.user.client.ui.Widget#addHandler(EventHandler, Type)},
- * application developers are strongly discouraged from using a HandlerManager
+ * {@link com.google.gwt.user.client.ui.Widget#addHandler(EventHandler, com.google.gwt.event.shared.GwtEvent.Type)}
+ * , application developers are strongly discouraged from using a HandlerManager
  * instance as a global event dispatch mechanism.
  */
 public class HandlerManager implements HasHandlers {
 
-  private final SimpleEventBus eventBus;
+  @SuppressWarnings("deprecation")
+  private static class Bus extends com.google.web.bindery.event.shared.SimpleEventBus {
+    public Bus(boolean fireInReverseOrder) {
+      super(fireInReverseOrder);
+    }
+
+    @Override
+    protected <H> void doRemove(Event.Type<H> type, Object source, H handler) {
+      super.doRemove(type, source, handler);
+    }
+
+    @Override
+    protected <H> H getHandler(Event.Type<H> type, int index) {
+      return super.getHandler(type, index);
+    }
+
+    @Override
+    protected int getHandlerCount(Event.Type<?> eventKey) {
+      return super.getHandlerCount(eventKey);
+    }
+
+    @Override
+    protected boolean isEventHandled(Event.Type<?> eventKey) {
+      return super.isEventHandled(eventKey);
+    }
+  }
+
+  private final Bus eventBus;
 
   // source of the events
   private final Object source;
@@ -40,7 +67,7 @@
    * Creates a handler manager with a source to be set on all events fired via
    * {@link #fireEvent(GwtEvent)}. Handlers will be fired in the order that they
    * are added.
-   *
+   * 
    * @param source the default event source
    */
   public HandlerManager(Object source) {
@@ -50,28 +77,27 @@
   /**
    * Creates a handler manager with the given source, specifying the order in
    * which handlers are fired.
-   *
+   * 
    * @param source the event source
    * @param fireInReverseOrder true to fire handlers in reverse order
    */
-  @SuppressWarnings("deprecation")
   public HandlerManager(Object source, boolean fireInReverseOrder) {
-    eventBus = new SimpleEventBus(fireInReverseOrder);
+    eventBus = new Bus(fireInReverseOrder);
     this.source = source;
   }
 
   /**
    * Adds a handler.
-   *
+   * 
    * @param <H> The type of handler
    * @param type the event type associated with this handler
    * @param handler the handler
    * @return the handler registration, can be stored in order to remove the
    *         handler later
    */
-  public <H extends EventHandler> HandlerRegistration addHandler(
-      GwtEvent.Type<H> type, final H handler) {
-    return eventBus.addHandler(type, handler);
+  public <H extends EventHandler> HandlerRegistration addHandler(GwtEvent.Type<H> type,
+      final H handler) {
+    return new LegacyHandlerWrapper(eventBus.addHandler(type, handler));
   }
 
   /**
@@ -85,7 +111,7 @@
    * Note, any subclass should be very careful about overriding this method, as
    * adds/removes of handlers will not be safe except within this
    * implementation.
-   *
+   * 
    * @param event the event
    */
   public void fireEvent(GwtEvent<?> event) {
@@ -94,68 +120,65 @@
       event.revive();
     }
     Object oldSource = event.getSource();
-    event.setSource(source);
+    event.overrideSource(source);
     try {
 
       // May throw an UmbrellaException.
       eventBus.fireEvent(event);
-
+    } catch (com.google.web.bindery.event.shared.UmbrellaException e) {
+      throw new UmbrellaException(e.getCauses());
     } finally {
       if (oldSource == null) {
         // This was my event, so I should kill it now that I'm done.
         event.kill();
       } else {
         // Restoring the source for the next handler to use.
-        event.setSource(oldSource);
+        event.overrideSource(oldSource);
       }
     }
   }
 
   /**
    * Gets the handler at the given index.
-   *
+   * 
    * @param <H> the event handler type
    * @param index the index
    * @param type the handler's event type
    * @return the given handler
    */
-  @SuppressWarnings("deprecation")
   public <H extends EventHandler> H getHandler(GwtEvent.Type<H> type, int index) {
     return eventBus.getHandler(type, index);
   }
 
   /**
    * Gets the number of handlers listening to the event type.
-   *
+   * 
    * @param type the event type
    * @return the number of registered handlers
    */
-  @SuppressWarnings("deprecation")
-  public int getHandlerCount(Type<?> type) {
+  public int getHandlerCount(GwtEvent.Type<?> type) {
     return eventBus.getHandlerCount(type);
   }
 
   /**
    * Does this handler manager handle the given event type?
-   *
+   * 
    * @param e the event type
    * @return whether the given event type is handled
    */
-  @SuppressWarnings("deprecation")
-  public boolean isEventHandled(Type<?> e) {
+  public boolean isEventHandled(GwtEvent.Type<?> e) {
     return eventBus.isEventHandled(e);
   }
 
   /**
    * Removes the given handler from the specified event type.
-   *
+   * 
    * @param <H> handler type
-   *
+   * 
    * @param type the event type
    * @param handler the handler
    */
-  public <H extends EventHandler> void removeHandler(GwtEvent.Type<H> type,
-      final H handler) {
-      eventBus.doRemove(type, null, handler);
+  public <H extends EventHandler> void removeHandler(GwtEvent.Type<H> type, final H handler) {
+    eventBus.doRemove(type, null, handler);
   }
 }
diff --git a/user/src/com/google/gwt/event/shared/HandlerRegistration.java b/user/src/com/google/gwt/event/shared/HandlerRegistration.java
index 9031343..5d04ac9 100644
--- a/user/src/com/google/gwt/event/shared/HandlerRegistration.java
+++ b/user/src/com/google/gwt/event/shared/HandlerRegistration.java
@@ -17,19 +17,8 @@
 package com.google.gwt.event.shared;
 
 /**
- * Registration returned from a call to
- * {@link HandlerManager#addHandler(com.google.gwt.event.shared.GwtEvent.Type, EventHandler)}
- * . Use the handler registration to remove handlers when they are no longer
- * needed.
- * 
- * Note, this interface is under the control of the {@link HandlerManager} class
- * and may be expanded over time, so extend {@link DefaultHandlerRegistration}
- * if you do not wish to get compiler errors if we extend the handler registry
- * functionality.
+ * Extends {com.google.bindery.event.shared.HandlerRegistration} for legacy
+ * compatibility.
  */
-public interface HandlerRegistration {
-  /**
-   * Removes the given handler from its manager.
-   */
-  void removeHandler();
+public interface HandlerRegistration extends com.google.web.bindery.event.shared.HandlerRegistration {
 }
diff --git a/user/src/com/google/gwt/event/shared/LegacyHandlerWrapper.java b/user/src/com/google/gwt/event/shared/LegacyHandlerWrapper.java
new file mode 100644
index 0000000..b5396da
--- /dev/null
+++ b/user/src/com/google/gwt/event/shared/LegacyHandlerWrapper.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.event.shared;
+
+class LegacyHandlerWrapper implements HandlerRegistration {
+  private final com.google.web.bindery.event.shared.HandlerRegistration real;
+  
+  LegacyHandlerWrapper(com.google.web.bindery.event.shared.HandlerRegistration real) {
+    this.real = real;
+  }
+
+  public void removeHandler() {
+    real.removeHandler();
+  }
+}
diff --git a/user/src/com/google/gwt/event/shared/ResettableEventBus.java b/user/src/com/google/gwt/event/shared/ResettableEventBus.java
index 5769a79..4e12fe8 100644
--- a/user/src/com/google/gwt/event/shared/ResettableEventBus.java
+++ b/user/src/com/google/gwt/event/shared/ResettableEventBus.java
@@ -15,57 +15,83 @@
  */
 package com.google.gwt.event.shared;
 
-import com.google.gwt.event.shared.GwtEvent.Type;
-
-import java.util.HashSet;
-import java.util.Set;
+import com.google.web.bindery.event.shared.Event;
+import com.google.web.bindery.event.shared.Event.Type;
+import com.google.web.bindery.event.shared.HandlerRegistration;
 
 /**
- * Wraps an EventBus to hold on to any HandlerRegistrations, so that they can
- * easily all be cleared at once.
+ * Wraps {com.google.web.bindery.event.shared.ResettableEventBus} for legacy
+ * compatibility.
  */
 public class ResettableEventBus extends EventBus {
-  private final EventBus wrapped;
-  private final Set<HandlerRegistration> registrations = new HashSet<HandlerRegistration>();
+  private static class TestableResettableEventBus extends com.google.web.bindery.event.shared.ResettableEventBus {
+    /**
+     * @param wrappedBus
+     */
+    public TestableResettableEventBus(EventBus wrappedBus) {
+      super(wrappedBus);
+    }
+
+    @Override
+    public int getRegistrationSize() {
+      return super.getRegistrationSize();
+    }
+  }
+
+  private final TestableResettableEventBus real;
 
   public ResettableEventBus(EventBus wrappedBus) {
-    this.wrapped = wrappedBus;
+    real = new TestableResettableEventBus(wrappedBus);
+  }
+
+  public <H extends EventHandler> com.google.gwt.event.shared.HandlerRegistration addHandler(
+      GwtEvent.Type<H> type, H handler) {
+    return wrap(addHandler((Event.Type<H>) type, handler));
   }
 
   @Override
-  public <H extends EventHandler> HandlerRegistration addHandler(Type<H> type,
-      H handler) {
-    HandlerRegistration rtn = wrapped.addHandler(type, handler);
-    registrations.add(rtn);
-    return rtn;
+  public <H> HandlerRegistration addHandler(Type<H> type, H handler) {
+    return real.addHandler(type, handler);
   }
 
-  @Override
-  public <H extends EventHandler> HandlerRegistration addHandlerToSource(
+  public <H extends EventHandler> com.google.gwt.event.shared.HandlerRegistration addHandlerToSource(
       GwtEvent.Type<H> type, Object source, H handler) {
-    HandlerRegistration rtn = wrapped.addHandlerToSource(type, source,
-        handler);
-    registrations.add(rtn);
-    return rtn;
+    return wrap(addHandlerToSource((Event.Type<H>) type, source, handler));
+  }
+
+  @Override
+  public <H> HandlerRegistration addHandlerToSource(Type<H> type, Object source, H handler) {
+    return real.addHandlerToSource(type, source, handler);
+  }
+
+  @Override
+  public void fireEvent(Event<?> event) {
+    real.fireEvent(event);
   }
 
   @Override
   public void fireEvent(GwtEvent<?> event) {
-    wrapped.fireEvent(event);
+    castFireEvent(event);
+  }
+
+  @Override
+  public void fireEventFromSource(Event<?> event, Object source) {
+    real.fireEventFromSource(event, source);
   }
 
   @Override
   public void fireEventFromSource(GwtEvent<?> event, Object source) {
-    wrapped.fireEventFromSource(event, source);
+    castFireEventFromSource(event, source);
+  }
+
+  public void removeHandlers() {
+    real.removeHandlers();
   }
 
   /**
-   * Remove all handlers that have been added through this wrapper.
+   * Visible for testing
    */
-  public void removeHandlers() {
-    for (HandlerRegistration r : registrations) {
-      r.removeHandler();
-    }
-    registrations.clear();
+  int getRegistrationSize() {
+    return real.getRegistrationSize();
   }
 }
diff --git a/user/src/com/google/gwt/event/shared/SimpleEventBus.java b/user/src/com/google/gwt/event/shared/SimpleEventBus.java
index 673d698..40b042d 100644
--- a/user/src/com/google/gwt/event/shared/SimpleEventBus.java
+++ b/user/src/com/google/gwt/event/shared/SimpleEventBus.java
@@ -15,309 +15,55 @@
  */
 package com.google.gwt.event.shared;
 
-import com.google.gwt.core.client.Scheduler.ScheduledCommand;
-import com.google.gwt.event.shared.GwtEvent.Type;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Map;
-import java.util.Set;
+import com.google.web.bindery.event.shared.Event;
+import com.google.web.bindery.event.shared.Event.Type;
+import com.google.web.bindery.event.shared.HandlerRegistration;
 
 /**
- * Basic implementation of {@link EventBus}.
+ * Wraps {com.google.web.bindery.event.shared.SimpleEventBus} for legacy
+ * compatibility.
  */
 public class SimpleEventBus extends EventBus {
-  private final boolean isReverseOrder;
+  private final com.google.web.bindery.event.shared.SimpleEventBus real =
+      new com.google.web.bindery.event.shared.SimpleEventBus();
 
-  private int firingDepth = 0;
-
-  /**
-   * Add and remove operations received during dispatch.
-   */
-  private List<ScheduledCommand> deferredDeltas;
-
-  /**
-   * Map of event type to map of event source to list of their handlers.
-   */
-  private final Map<GwtEvent.Type<?>, Map<Object, List<?>>> map = new HashMap<GwtEvent.Type<?>, Map<Object, List<?>>>();
-
-  public SimpleEventBus() {
-    this(false);
-  }
-
-  /**
-   * Allows creation of an instance that fires its handlers in the reverse of
-   * the order in which they were added, although filtered handlers all fire
-   * before unfiltered handlers.
-   * <p>
-   * 
-   * @deprecated This is a legacy feature, required by HandlerManager. Package
-   *             protected because it is a bad idea to rely upon the order of
-   *             event dispatch, and because fully supporting it (that is, not
-   *             segregating filtered and unfiltered handlers, a distinction not
-   *             used by HandlerManager) is not worth the effort.
-   */
-  @Deprecated
-  SimpleEventBus(boolean fireInReverseOrder) {
-    isReverseOrder = fireInReverseOrder;
+  public <H extends EventHandler> com.google.gwt.event.shared.HandlerRegistration addHandler(
+      GwtEvent.Type<H> type, H handler) {
+    return wrap(addHandler((Event.Type<H>) type, handler));
   }
 
   @Override
-  public <H extends EventHandler> HandlerRegistration addHandler(Type<H> type,
-      H handler) {
-    if (type == null) {
-      throw new NullPointerException("Cannot add a handler with a null type");
-    }
-    if (handler == null) {
-      throw new NullPointerException("Cannot add a null handler");
-    }
+  public <H> HandlerRegistration addHandler(Type<H> type, H handler) {
+    return real.addHandler(type, handler);
+  }
 
-    return doAdd(type, null, handler);
+  public <H extends EventHandler> com.google.gwt.event.shared.HandlerRegistration addHandlerToSource(
+      GwtEvent.Type<H> type, Object source, H handler) {
+    return wrap(addHandlerToSource((Event.Type<H>) type, source, handler));
   }
 
   @Override
-  public <H extends EventHandler> HandlerRegistration addHandlerToSource(
-      final GwtEvent.Type<H> type, final Object source, final H handler) {
-    if (type == null) {
-      throw new NullPointerException("Cannot add a handler with a null type");
-    }
-    if (source == null) {
-      throw new NullPointerException("Cannot add a handler with a null source");
-    }
-    if (handler == null) {
-      throw new NullPointerException("Cannot add a null handler");
-    }
+  public <H> HandlerRegistration addHandlerToSource(Type<H> type, Object source, H handler) {
+    return real.addHandlerToSource(type, source, handler);
+  }
 
-    return doAdd(type, source, handler);
+  @Override
+  public void fireEvent(Event<?> event) {
+    real.fireEvent(event);
   }
 
   @Override
   public void fireEvent(GwtEvent<?> event) {
-    if (event == null) {
-      throw new NullPointerException("Cannot fire null event");
-    }
-    doFire(event, null);
+    castFireEvent(event);
+  }
+
+  @Override
+  public void fireEventFromSource(Event<?> event, Object source) {
+    real.fireEventFromSource(event, source);
   }
 
   @Override
   public void fireEventFromSource(GwtEvent<?> event, Object source) {
-    if (event == null) {
-      throw new NullPointerException("Cannot fire null event");
-    }
-    if (source == null) {
-      throw new NullPointerException("Cannot fire from a null source");
-    }
-    doFire(event, source);
+    castFireEventFromSource(event, source);
   }
-
-  /**
-   * Package protected to support legacy features in HandlerManager.
-   */
-  <H extends EventHandler> void doRemove(
-      com.google.gwt.event.shared.GwtEvent.Type<H> type, Object source,
-      H handler) {
-    if (firingDepth > 0) {
-      enqueueRemove(type, source, handler);
-    } else {
-      doRemoveNow(type, source, handler);
-    }
-  }
-
-  /**
-   * Package protected to support legacy features in HandlerManager.
-   */
-  @Deprecated
-  <H extends EventHandler> H getHandler(GwtEvent.Type<H> type, int index) {
-    assert index < getHandlerCount(type) : "handlers for " + type.getClass()
-        + " have size: " + getHandlerCount(type)
-        + " so do not have a handler at index: " + index;
-
-    List<H> l = getHandlerList(type, null);
-    return l.get(index);
-  }
-
-  /**
-   * Package protected to support legacy features in HandlerManager.
-   */
-  @Deprecated
-  int getHandlerCount(GwtEvent.Type<?> eventKey) {
-    return getHandlerList(eventKey, null).size();
-  }
-
-  /**
-   * Package protected to support legacy features in HandlerManager.
-   */
-  @Deprecated
-  boolean isEventHandled(GwtEvent.Type<?> eventKey) {
-    return map.containsKey(eventKey);
-  }
-
-  private void defer(ScheduledCommand command) {
-    if (deferredDeltas == null) {
-      deferredDeltas = new ArrayList<ScheduledCommand>();
-    }
-    deferredDeltas.add(command);
-  }
-
-  private <H extends EventHandler> HandlerRegistration doAdd(
-      final GwtEvent.Type<H> type, final Object source, final H handler) {
-    if (firingDepth > 0) {
-      enqueueAdd(type, source, handler);
-    } else {
-      doAddNow(type, source, handler);
-    }
-
-    return new HandlerRegistration() {
-      public void removeHandler() {
-        doRemove(type, source, handler);
-      }
-    };
-  }
-
-  private <H extends EventHandler> void doAddNow(GwtEvent.Type<H> type,
-      Object source, H handler) {
-    List<H> l = ensureHandlerList(type, source);
-    l.add(handler);
-  }
-
-  private <H extends EventHandler> void doFire(GwtEvent<H> event, Object source) {
-    try {
-      firingDepth++;
-
-      if (source != null) {
-        event.setSource(source);
-      }
-
-      List<H> handlers = getDispatchList(event.getAssociatedType(), source);
-      Set<Throwable> causes = null;
-
-      ListIterator<H> it = isReverseOrder
-          ? handlers.listIterator(handlers.size()) : handlers.listIterator();
-      while (isReverseOrder ? it.hasPrevious() : it.hasNext()) {
-        H handler = isReverseOrder ? it.previous() : it.next();
-
-        try {
-          event.dispatch(handler);
-        } catch (Throwable e) {
-          if (causes == null) {
-            causes = new HashSet<Throwable>();
-          }
-          causes.add(e);
-        }
-      }
-
-      if (causes != null) {
-        throw new UmbrellaException(causes);
-      }
-    } finally {
-      firingDepth--;
-      if (firingDepth == 0) {
-        handleQueuedAddsAndRemoves();
-      }
-    }
-  }
-
-  private <H> void doRemoveNow(GwtEvent.Type<H> type, Object source, H handler) {
-    List<H> l = getHandlerList(type, source);
-
-    boolean removed = l.remove(handler);
-    assert removed : "redundant remove call";
-    if (removed && l.isEmpty()) {
-      prune(type, source);
-    }
-  }
-
-  private <H extends EventHandler> void enqueueAdd(final GwtEvent.Type<H> type,
-      final Object source, final H handler) {
-    defer(new ScheduledCommand() {
-      public void execute() {
-        doAddNow(type, source, handler);
-      }
-    });
-  }
-
-  private <H extends EventHandler> void enqueueRemove(
-      final GwtEvent.Type<H> type, final Object source, final H handler) {
-    defer(new ScheduledCommand() {
-      public void execute() {
-        doRemoveNow(type, source, handler);
-      }
-    });
-  }
-
-  private <H> List<H> ensureHandlerList(GwtEvent.Type<H> type, Object source) {
-    Map<Object, List<?>> sourceMap = map.get(type);
-    if (sourceMap == null) {
-      sourceMap = new HashMap<Object, List<?>>();
-      map.put(type, sourceMap);
-    }
-
-    // safe, we control the puts.
-    @SuppressWarnings("unchecked")
-    List<H> handlers = (List<H>) sourceMap.get(source);
-    if (handlers == null) {
-      handlers = new ArrayList<H>();
-      sourceMap.put(source, handlers);
-    }
-
-    return handlers;
-  }
-
-  private <H> List<H> getDispatchList(GwtEvent.Type<H> type, Object source) {
-    List<H> directHandlers = getHandlerList(type, source);
-    if (source == null) {
-      return directHandlers;
-    }
-
-    List<H> globalHandlers = getHandlerList(type, null);
-
-    List<H> rtn = new ArrayList<H>(directHandlers);
-    rtn.addAll(globalHandlers);
-    return rtn;
-  }
-
-  private <H> List<H> getHandlerList(GwtEvent.Type<H> type, Object source) {
-    Map<Object, List<?>> sourceMap = map.get(type);
-    if (sourceMap == null) {
-      return Collections.emptyList();
-    }
-
-    // safe, we control the puts.
-    @SuppressWarnings("unchecked")
-    List<H> handlers = (List<H>) sourceMap.get(source);
-    if (handlers == null) {
-      return Collections.emptyList();
-    }
-
-    return handlers;
-  }
-
-  private void handleQueuedAddsAndRemoves() {
-    if (deferredDeltas != null) {
-      try {
-        for (ScheduledCommand c : deferredDeltas) {
-          c.execute();
-        }
-      } finally {
-        deferredDeltas = null;
-      }
-    }
-  }
-
-  private void prune(GwtEvent.Type<?> type, Object source) {
-    Map<Object, List<?>> sourceMap = map.get(type);
-
-    List<?> pruned = sourceMap.remove(source);
-
-    assert pruned != null : "Can't prune what wasn't there";
-    assert pruned.isEmpty() : "Pruned unempty list!";
-
-    if (sourceMap.isEmpty()) {
-      map.remove(type);
-    }
-  }
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/event/shared/UmbrellaException.java b/user/src/com/google/gwt/event/shared/UmbrellaException.java
index 134af51..dd34695 100644
--- a/user/src/com/google/gwt/event/shared/UmbrellaException.java
+++ b/user/src/com/google/gwt/event/shared/UmbrellaException.java
@@ -15,43 +15,21 @@
  */
 package com.google.gwt.event.shared;
 
-import java.util.HashSet;
 import java.util.Set;
 
 /**
- * A {@link RuntimeException} that collects a {@link Set} of child
- * {@link Throwable}s together. Typically thrown after loop, with all of the
- * exceptions thrown during that loop, but delayed so that the loop finishes
- * executing.
+ * Wraps {com.google.web.bindery.event.shared.UmbrellaException} for legacy
+ * compatibility.
  */
-public class UmbrellaException extends RuntimeException {
-
-  /**
-   * The causes of the exception.
-   */
-  private Set<Throwable> causes;
-
-  /*
-   * The default constructor enables RPC support.
-   */
-  public UmbrellaException() {
-    this(new HashSet<Throwable>());
-  }
-  
+public class UmbrellaException extends com.google.web.bindery.event.shared.UmbrellaException {
   public UmbrellaException(Set<Throwable> causes) {
-    super(
-        "One or more exceptions caught, see full set in UmbrellaException#getCauses",
-        causes.size() == 0 ? null : causes.toArray(new Throwable[0])[0]);
-    this.causes = causes;
+    super(causes);
   }
 
   /**
-   * Get the set of exceptions that caused the failure.
-   * 
-   * @return the set of causes
+   * Required for GWT RPC serialization.
    */
-  public Set<Throwable> getCauses() {
-    return causes;
+  protected UmbrellaException() {
+    super();
   }
-
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/event/shared/testing/CountingEventBus.java b/user/src/com/google/gwt/event/shared/testing/CountingEventBus.java
index 3c1a121..b0aa76b 100644
--- a/user/src/com/google/gwt/event/shared/testing/CountingEventBus.java
+++ b/user/src/com/google/gwt/event/shared/testing/CountingEventBus.java
@@ -15,87 +15,68 @@
  */
 package com.google.gwt.event.shared.testing;
 
-import com.google.gwt.event.shared.EventBus;
 import com.google.gwt.event.shared.EventHandler;
 import com.google.gwt.event.shared.GwtEvent;
-import com.google.gwt.event.shared.GwtEvent.Type;
-import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwt.event.shared.SimpleEventBus;
-
-import java.util.HashMap;
-import java.util.Map;
+import com.google.web.bindery.event.shared.Event;
+import com.google.web.bindery.event.shared.Event.Type;
+import com.google.web.bindery.event.shared.HandlerRegistration;
 
 /**
- * Wraps an {@link EventBus} to keep a count of registered handlers. Handy for
- * tests.
+ * Legacy compatibility wrapper for
+ * {@link com.google.web.bindery.event.shared.testing.CountingEventBus}.
  */
-public class CountingEventBus extends EventBus {
-  private final Map<Type<?>, Integer> counts = new HashMap<GwtEvent.Type<?>, Integer>();
-  private final EventBus wrapped;
-  
+public class CountingEventBus extends com.google.gwt.event.shared.EventBus {
+  private final com.google.web.bindery.event.shared.testing.CountingEventBus real;
+
   public CountingEventBus() {
-    this(new SimpleEventBus());
+    real = new com.google.web.bindery.event.shared.testing.CountingEventBus();
+  }
+
+  public CountingEventBus(com.google.gwt.event.shared.EventBus wrapped) {
+    real = new com.google.web.bindery.event.shared.testing.CountingEventBus(wrapped);
+  }
+
+  public <H extends EventHandler> com.google.gwt.event.shared.HandlerRegistration addHandler(
+      GwtEvent.Type<H> type, H handler) {
+    return wrap(addHandler((Event.Type<H>) type, handler));
+  }
+
+  @Override
+  public <H> HandlerRegistration addHandler(Type<H> type, H handler) {
+    return real.addHandler(type, handler);
+  }
+
+  public <H extends EventHandler> com.google.gwt.event.shared.HandlerRegistration addHandlerToSource(
+      GwtEvent.Type<H> type, Object source, H handler) {
+    return wrap(addHandlerToSource((Event.Type<H>) type, source, handler));
   }
   
-  public CountingEventBus(EventBus wrapped) {
-    this.wrapped = wrapped;
+  @Override
+  public <H> HandlerRegistration addHandlerToSource(Type<H> type, Object source, H handler) {
+    return real.addHandlerToSource(type, source, handler);
   }
 
   @Override
-  public <H extends EventHandler> HandlerRegistration addHandler(Type<H> type,
-      H handler) {
-    increment(type);
-    final HandlerRegistration superReg = wrapped.addHandler(type, handler);
-    return makeReg(type, superReg);
-  }
-
-  @Override
-  public <H extends EventHandler> HandlerRegistration addHandlerToSource(
-      final Type<H> type, Object source, H handler) {
-    increment(type);
-    final HandlerRegistration superReg = wrapped.addHandlerToSource(type,
-        source, handler);
-    return makeReg(type, superReg);
+  public void fireEvent(Event<?> event) {
+    real.fireEvent(event);
   }
 
   @Override
   public void fireEvent(GwtEvent<?> event) {
-    wrapped.fireEvent(event);
+    castFireEvent(event);
+  }
+
+  @Override
+  public void fireEventFromSource(Event<?> event, Object source) {
+    real.fireEventFromSource(event, source);
   }
 
   @Override
   public void fireEventFromSource(GwtEvent<?> event, Object source) {
-    wrapped.fireEventFromSource(event, source);
+    castFireEventFromSource(event, source);
   }
 
-  public int getCount(Type<?> type) {
-    Integer count = counts.get(type);
-    return count == null ? 0 : count;
-  }
-
-  private void decrement(Type<?> type) {
-    Integer count = counts.get(type);
-    if (count == null) {
-      count = 0;
-    }
-    counts.put(type, count - 1);
-  }
-
-  private <H> void increment(final Type<H> type) {
-    Integer count = counts.get(type);
-    if (count == null) {
-      count = 0;
-    }
-    counts.put(type, count + 1);
-  }
-
-  private <H> HandlerRegistration makeReg(final Type<H> type,
-      final HandlerRegistration superReg) {
-    return new HandlerRegistration() {
-      public void removeHandler() {
-        decrement(type);
-        superReg.removeHandler();
-      }
-    };
+  public int getCount(GwtEvent.Type<?> type) {
+    return real.getCount(type);
   }
 }
diff --git a/user/src/com/google/gwt/requestfactory/client/DefaultRequestTransport.java b/user/src/com/google/gwt/requestfactory/client/DefaultRequestTransport.java
index 6cc5306..26060d6 100644
--- a/user/src/com/google/gwt/requestfactory/client/DefaultRequestTransport.java
+++ b/user/src/com/google/gwt/requestfactory/client/DefaultRequestTransport.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -34,7 +34,12 @@
 /**
  * An implementation of {@link RequestTransport} that uses a
  * {@link RequestBuilder}.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class DefaultRequestTransport implements RequestTransport {
   private static final String SERVER_ERROR = "Server Error";
 
@@ -57,7 +62,7 @@
 
   /**
    * Returns the current URL used by this transport.
-   * 
+   *
    * @return the URL as a String
    * @see #setRequestUrl(String)
    */
@@ -83,7 +88,7 @@
 
   /**
    * Override the default URL used by this transport.
-   * 
+   *
    * @param url a String URL
    * @see #getRequestUrl()
    */
@@ -93,7 +98,7 @@
 
   /**
    * Override to change the headers sent in the HTTP request.
-   * 
+   *
    * @param builder a {@link RequestBuilder} instance
    */
   protected void configureRequestBuilder(RequestBuilder builder) {
@@ -105,7 +110,7 @@
   /**
    * Constructs a {@link RequestBuilder} using the {@link RequestBuilder#POST}
    * method sent to the URL returned from {@link #getRequestUrl()}.
-   * 
+   *
    * @return a {@link RequestBuilder} instance
    */
   protected RequestBuilder createRequestBuilder() {
@@ -116,7 +121,7 @@
    * Creates a RequestCallback that maps the HTTP response onto the
    * {@link com.google.gwt.requestfactory.shared.RequestTransport.TransportReceiver
    * TransportReceiver} interface.
-   * 
+   *
    * @param receiver a {@link TransportReceiver}
    * @return a {@link RequestCallback} instance
    */
diff --git a/user/src/com/google/gwt/requestfactory/client/HasRequestContext.java b/user/src/com/google/gwt/requestfactory/client/HasRequestContext.java
index 0482875..5f13dd7 100644
--- a/user/src/com/google/gwt/requestfactory/client/HasRequestContext.java
+++ b/user/src/com/google/gwt/requestfactory/client/HasRequestContext.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -22,9 +22,14 @@
  * Editors used with {@link RequestFactoryEditorDriver} that implement this
  * interface will be provided with the {@link RequestContext} associated with
  * the current editing session.
- * 
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @param <T> the type of data being edited
  */
+@Deprecated
 public interface HasRequestContext<T> extends Editor<T> {
   /**
    * Called by {@link RequestFactoryEditorDriver} with the
@@ -32,7 +37,7 @@
    * {@link RequestFactoryEditorDriver#edit(Object, RequestContext) edit()} or
    * {@code null} if {@link RequestFactoryEditorDriver#display(Object)
    * display()} is called.
-   * 
+   *
    * @param the RequestContext associated with the current editing session which
    *          may be {@code null}
    */
diff --git a/user/src/com/google/gwt/requestfactory/client/RequestFactoryEditorDriver.java b/user/src/com/google/gwt/requestfactory/client/RequestFactoryEditorDriver.java
index 71cb681..dab47d3 100644
--- a/user/src/com/google/gwt/requestfactory/client/RequestFactoryEditorDriver.java
+++ b/user/src/com/google/gwt/requestfactory/client/RequestFactoryEditorDriver.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -26,33 +26,39 @@
  * The interface that links RequestFactory and the Editor framework together.
  * <p>
  * Instances of this interface are created with
- * 
+ *
  * <pre>
  * interface MyRFED extends RequestFactoryEditorDriver&lt;MyObjectProxy, MyObjectEditor> {}
  * MyRFED instance = GWT.create(MyRFED.class);
  * {
  * instance.initialize(.....);
  * myRequest.with(instance.getPaths());
- * 
+ *
  * // Fire the request, in the callback
  * instance.edit(retrievedRecord);
  * // Control when the request is sent
  * instance.flush().fire(new Receiver {...});
  * }
  * </pre>
- * 
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @param <P> the type of Proxy being edited
  * @param <E> the type of Editor that will edit the Record
+ *
  * @see HasRequestContext
  * @see {@link com.google.gwt.requestfactory.client.testing.MockRequestFactoryEditorDriver
  *      MockRequestFactoryEditorDriver}
  */
+@Deprecated
 public interface RequestFactoryEditorDriver<P, E extends Editor<? super P>>
     extends EditorDriver<RequestContext> {
   /**
    * Start driving the Editor and its sub-editors with data for display-only
    * mode.
-   * 
+   *
    * @param proxy a Proxy of type P
    */
   void display(P proxy);
@@ -62,7 +68,7 @@
    * {@link RequestContext} is required to provide context for the changes to
    * the proxy (see {@link RequestContext#edit}. Note that this driver will not
    * fire the request.
-   * 
+   *
    * @param proxy the proxy to be edited
    * @param request the request context that will accumulate edits and is
    *          returned form {@link #flush}
@@ -71,7 +77,7 @@
 
   /**
    * Update the object being edited with the current state of the Editor.
-   * 
+   *
    * @return the RequestContext passed into
    *         {@link #edit(Object, RequestContext)}
    * @throws IllegalStateException if {@link #edit(Object, RequestContext)} has
@@ -81,7 +87,7 @@
 
   /**
    * Returns a new array containing the request paths.
-   * 
+   *
    * @return an array of Strings
    */
   String[] getPaths();
@@ -89,11 +95,11 @@
   /**
    * Overload of {@link #initialize(RequestFactory, Editor)} to allow a modified
    * {@link EventBus} to be monitored for subscription services.
-   * 
+   *
    * @param eventBus the {@link EventBus}
    * @param requestFactory a {@link RequestFactory} instance
    * @param editor an {@link Editor} of type E
-   * 
+   *
    * @see com.google.gwt.editor.client.EditorDelegate#subscribe
    * @see com.google.gwt.event.shared.ResettableEventBus
    */
@@ -102,10 +108,10 @@
   /**
    * Initializes a driver with the editor it will run, and a RequestFactory to
    * use for subscription services.
-   * 
+   *
    * @param requestFactory a {@link RequestFactory} instance
    * @param editor an {@link Editor} of type E
-   * 
+   *
    * @see com.google.gwt.editor.client.EditorDelegate#subscribe
    */
   void initialize(RequestFactory requestFactory, E editor);
@@ -114,7 +120,7 @@
    * Initializes a driver that will not be able to support subscriptions. Calls
    * to {@link com.google.gwt.editor.client.EditorDelegate#subscribe()} will do
    * nothing.
-   * 
+   *
    * @param editor an {@link Editor} of type E
    */
   void initialize(E editor);
@@ -125,7 +131,7 @@
    * {@link com.google.gwt.editor.client.EditorError EditorError} objects whose
    * {@link com.google.gwt.editor.client.EditorError#getUserData()
    * getUserData()} method can be used to access the original Violation object.
-   * 
+   *
    * @param violations an Iterable over {@link Violation} instances
    * @return <code>true</code> if there were any unconsumed EditorErrors which
    *         can be retrieved from {@link #getErrors()}
diff --git a/user/src/com/google/gwt/requestfactory/client/RequestFactoryLogHandler.java b/user/src/com/google/gwt/requestfactory/client/RequestFactoryLogHandler.java
index d3f0b4f..9fee95a 100644
--- a/user/src/com/google/gwt/requestfactory/client/RequestFactoryLogHandler.java
+++ b/user/src/com/google/gwt/requestfactory/client/RequestFactoryLogHandler.java
@@ -1,19 +1,18 @@
 /*
  * Copyright 2010 Google Inc.
- * 
+ *
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- * 
+ *
  * http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-
 package com.google.gwt.requestfactory.client;
 
 import com.google.gwt.logging.client.JsonLogRecordClientUtil;
@@ -27,12 +26,22 @@
 
 /**
  * A Handler that does remote logging for applications using RequestFactory.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class RequestFactoryLogHandler extends RemoteLogHandlerBase {
-  
-  /** 
+
+  /**
    * Provides a logging request.
+   *
+   * <p><span style='color:red'>RequestFactory has moved to
+   * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
    */
+  @Deprecated
   public static interface LoggingRequestProvider {
     /**
      * Returns the logging request.
@@ -41,9 +50,9 @@
      */
     LoggingRequest getLoggingRequest();
   }
-  
+
   private LoggingRequestProvider requestProvider;
-  
+
   /**
    * Since records from this handler go accross the wire, it should only be
    * used for important messages, and it's Level will often be higher than the
@@ -52,7 +61,7 @@
    * name of the logger(s) which will be used to log acknowledgements of
    * activity going accross the wire. If we did not exclude these loggers, an
    * infinite loop would occur.
-   * 
+   *
    * @param requestProvider a {@link LoggingRequestProvider} instance
    * @param level a logging {@link Level}
    * @param ignoredLoggerNames a List of Strings
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/AbstractClientRequestFactory.java b/user/src/com/google/gwt/requestfactory/client/impl/AbstractClientRequestFactory.java
index c38fba9..6db7d3b 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/AbstractClientRequestFactory.java
+++ b/user/src/com/google/gwt/requestfactory/client/impl/AbstractClientRequestFactory.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -21,7 +21,12 @@
 
 /**
  * A RequestFactory that uses a {@link DefaultRequestTransport} by default.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public abstract class AbstractClientRequestFactory extends
     AbstractRequestFactory {
   @Override
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactoryEditorDriver.java b/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactoryEditorDriver.java
index d60cf8c..ae3c91f 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactoryEditorDriver.java
+++ b/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactoryEditorDriver.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -40,16 +40,26 @@
 
 /**
  * Contains utility methods for top-level driver implementations.
- * 
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @param <R> the type being edited
  * @param <E> the type of Editor
  */
+@Deprecated
 public abstract class AbstractRequestFactoryEditorDriver<R, E extends Editor<R>>
     extends BaseEditorDriver<R, E> implements RequestFactoryEditorDriver<R, E> {
 
   /**
    * Adapts a RequestFactory Violation object to the SimpleViolation interface.
+   *
+   * <p><span style='color:red'>RequestFactory has moved to
+   * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
    */
+  @Deprecated
   static class SimpleViolationAdapter extends SimpleViolation {
     private final Violation v;
 
@@ -79,7 +89,12 @@
   /**
    * Provides a source of SimpleViolation objects based on RequestFactory's
    * simplified Violation interface.
+   *
+   * <p><span style='color:red'>RequestFactory has moved to
+   * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
    */
+  @Deprecated
   static class ViolationIterable implements Iterable<SimpleViolation> {
 
     private final Iterable<Violation> violations;
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/PathCollector.java b/user/src/com/google/gwt/requestfactory/client/impl/PathCollector.java
index 01a5fdf..83d0967 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/PathCollector.java
+++ b/user/src/com/google/gwt/requestfactory/client/impl/PathCollector.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -26,9 +26,13 @@
 
 /**
  * Collects all non-value-type paths in an editor hierarchy for use with
- * {@link com.google.gwt.requestfactory.client.RequestFactoryEditorDriver#getPaths()}
- * .
+ * {@link com.google.gwt.requestfactory.client.RequestFactoryEditorDriver#getPaths()}.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 class PathCollector extends EditorVisitor {
   /**
    * Use a set in the case of aliased editors, so we don't repeat path entries.
@@ -62,4 +66,4 @@
     }
     return true;
   }
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryEditorDelegate.java b/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryEditorDelegate.java
index bc40e1b..4073ca7 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryEditorDelegate.java
+++ b/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryEditorDelegate.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -35,10 +35,15 @@
 /**
  * Base class for generated EditorDelegates using a RequestFactory as the
  * backend.
- * 
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @param <P> the type of Proxy
  * @param <E> the type of Editor
  */
+@Deprecated
 public abstract class RequestFactoryEditorDelegate<P, E extends Editor<P>>
     extends AbstractEditorDelegate<P, E> {
   private class SubscriptionHandler implements
diff --git a/user/src/com/google/gwt/requestfactory/client/package-info.java b/user/src/com/google/gwt/requestfactory/client/package-info.java
index 4ca199c..2a58d05 100644
--- a/user/src/com/google/gwt/requestfactory/client/package-info.java
+++ b/user/src/com/google/gwt/requestfactory/client/package-info.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -15,9 +15,14 @@
  */
 
 /**
- * A package for manging client-server requests.
+ * A package for managing client-server requests.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  *
  * @since GWT 2.1
  */
+@Deprecated
 @com.google.gwt.util.PreventSpuriousRebuilds
 package com.google.gwt.requestfactory.client;
diff --git a/user/src/com/google/gwt/requestfactory/client/testing/MockRequestFactoryEditorDriver.java b/user/src/com/google/gwt/requestfactory/client/testing/MockRequestFactoryEditorDriver.java
index f0e4d89..58d6652 100644
--- a/user/src/com/google/gwt/requestfactory/client/testing/MockRequestFactoryEditorDriver.java
+++ b/user/src/com/google/gwt/requestfactory/client/testing/MockRequestFactoryEditorDriver.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -32,10 +32,15 @@
 /**
  * A no-op implementation of {@link RequestFactoryEditorDriver} that records its
  * inputs.
- * 
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @param <P> the Proxy type being edited
  * @param <E> the Editor type
  */
+@Deprecated
 public class MockRequestFactoryEditorDriver<P, E extends Editor<P>> implements
     RequestFactoryEditorDriver<P, E> {
   private static final String[] EMPTY_STRING = new String[0];
diff --git a/user/src/com/google/gwt/requestfactory/client/testing/package-info.java b/user/src/com/google/gwt/requestfactory/client/testing/package-info.java
index eaa68a7..21aa40a 100644
--- a/user/src/com/google/gwt/requestfactory/client/testing/package-info.java
+++ b/user/src/com/google/gwt/requestfactory/client/testing/package-info.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -17,7 +17,12 @@
 /**
  * Classes used for testing the request factory service.
  *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @since GWT 2.1
  */
+@Deprecated
 @com.google.gwt.util.PreventSpuriousRebuilds
 package com.google.gwt.requestfactory.client.testing;
diff --git a/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryEditorDriverGenerator.java b/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryEditorDriverGenerator.java
index 9926e0d..2598ba4 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryEditorDriverGenerator.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryEditorDriverGenerator.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -28,7 +28,12 @@
 
 /**
  * Generates implementations of RFEDs.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class RequestFactoryEditorDriverGenerator extends
     AbstractEditorDriverGenerator {
 
diff --git a/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java b/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java
index 5c1d109..1384ec7 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -69,7 +69,12 @@
  * Generates implementations of
  * {@link com.google.gwt.requestfactory.shared.RequestFactory RequestFactory}
  * and its nested interfaces.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class RequestFactoryGenerator extends Generator {
 
   /**
diff --git a/user/src/com/google/gwt/requestfactory/rebind/model/AcceptsModelVisitor.java b/user/src/com/google/gwt/requestfactory/rebind/model/AcceptsModelVisitor.java
index e3fb75c..4519e45 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/model/AcceptsModelVisitor.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/model/AcceptsModelVisitor.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -17,7 +17,12 @@
 
 /**
  * A common interface for model types.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public interface AcceptsModelVisitor {
   void accept(ModelVisitor visitor);
 }
diff --git a/user/src/com/google/gwt/requestfactory/rebind/model/ContextMethod.java b/user/src/com/google/gwt/requestfactory/rebind/model/ContextMethod.java
index 367f5b7..3eb376b 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/model/ContextMethod.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/model/ContextMethod.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -25,12 +25,22 @@
 
 /**
  * Represents a service endpoint.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class ContextMethod implements AcceptsModelVisitor {
 
   /**
    * Builds a {@link ContextMethod}.
+   *
+   * <p><span style='color:red'>RequestFactory has moved to
+   * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
    */
+  @Deprecated
   public static class Builder {
     private ContextMethod toReturn = new ContextMethod();
 
diff --git a/user/src/com/google/gwt/requestfactory/rebind/model/EntityProxyModel.java b/user/src/com/google/gwt/requestfactory/rebind/model/EntityProxyModel.java
index ffcae99..5b3d789 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/model/EntityProxyModel.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/model/EntityProxyModel.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -20,11 +20,21 @@
 
 /**
  * Represents an EntityProxy subtype.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class EntityProxyModel implements AcceptsModelVisitor {
   /**
    * Builds {@link EntityProxyModel}.
+   *
+   * <p><span style='color:red'>RequestFactory has moved to
+   * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
    */
+  @Deprecated
   public static class Builder {
     private EntityProxyModel toReturn = new EntityProxyModel();
 
@@ -69,7 +79,12 @@
   /**
    * The kind of proxy. This is an enum in case more proxy types are defined in
    * the future.
+   *
+   * <p><span style='color:red'>RequestFactory has moved to
+   * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
    */
+  @Deprecated
   public enum Type {
     ENTITY, VALUE
   }
diff --git a/user/src/com/google/gwt/requestfactory/rebind/model/ModelVisitor.java b/user/src/com/google/gwt/requestfactory/rebind/model/ModelVisitor.java
index a380da8..9c174ef 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/model/ModelVisitor.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/model/ModelVisitor.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -17,7 +17,12 @@
 
 /**
  * Implements traversal across a RequestFactory model.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class ModelVisitor {
   public void endVisit(ContextMethod x) {
   }
diff --git a/user/src/com/google/gwt/requestfactory/rebind/model/RequestFactoryModel.java b/user/src/com/google/gwt/requestfactory/rebind/model/RequestFactoryModel.java
index 142fdfa..413bf80 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/model/RequestFactoryModel.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/model/RequestFactoryModel.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -52,7 +52,12 @@
 
 /**
  * Represents a RequestFactory interface declaration.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class RequestFactoryModel implements AcceptsModelVisitor {
   static String badContextReturnType(JMethod method,
       JClassType requestInterface, JClassType instanceRequestInterface) {
diff --git a/user/src/com/google/gwt/requestfactory/rebind/model/RequestMethod.java b/user/src/com/google/gwt/requestfactory/rebind/model/RequestMethod.java
index 867764c..42f6d29 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/model/RequestMethod.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/model/RequestMethod.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -27,12 +27,22 @@
  * Represents a method declaration that causes data to be transported. This can
  * be a method declared in a RequestContext or a getter or setter on an
  * EntityProxy.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class RequestMethod implements AcceptsModelVisitor {
 
   /**
-   * Builds a {@link ContextMethod}.
+   * Builds a {@link RequestMethod}.
+   *
+   * <p><span style='color:red'>RequestFactory has moved to
+   * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
    */
+  @Deprecated
   public static class Builder {
     private RequestMethod toReturn = new RequestMethod();
 
@@ -106,7 +116,12 @@
 
   /**
    * Indicates the type of collection that a Request will return.
+   *
+   * <p><span style='color:red'>RequestFactory has moved to
+   * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
    */
+  @Deprecated
   public enum CollectionType {
     LIST, SET, MAP
   }
@@ -141,7 +156,7 @@
   /**
    * If the method returns a collection, this method will return the element
    * type.
-   * 
+   *
    * @return
    */
   public JClassType getCollectionElementType() {
diff --git a/user/src/com/google/gwt/requestfactory/server/DeadEntityException.java b/user/src/com/google/gwt/requestfactory/server/DeadEntityException.java
index 535239e..476c391 100644
--- a/user/src/com/google/gwt/requestfactory/server/DeadEntityException.java
+++ b/user/src/com/google/gwt/requestfactory/server/DeadEntityException.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -18,11 +18,16 @@
 /**
  * Indicates the user attempted to perform an operation on an irretrievable
  * entity.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 class DeadEntityException extends RuntimeException {
   /**
    * Contructs a new {@link DeadEntityException} with a given message.
-   * 
+   *
    * @param message a message String
    */
   public DeadEntityException(String message) {
diff --git a/user/src/com/google/gwt/requestfactory/server/DefaultExceptionHandler.java b/user/src/com/google/gwt/requestfactory/server/DefaultExceptionHandler.java
index 77b901b..87e1883 100644
--- a/user/src/com/google/gwt/requestfactory/server/DefaultExceptionHandler.java
+++ b/user/src/com/google/gwt/requestfactory/server/DefaultExceptionHandler.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -20,10 +20,15 @@
 /**
  * Default implementation for handling exceptions thrown while processing a
  * request. Suppresses stack traces and the exception class name.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class DefaultExceptionHandler implements ExceptionHandler {
   public ServerFailure createServerFailure(Throwable throwable) {
     return new ServerFailure("Server Error: "
         + (throwable == null ? null : throwable.getMessage()), null, null, true);
   }
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/requestfactory/server/ExceptionHandler.java b/user/src/com/google/gwt/requestfactory/server/ExceptionHandler.java
index 8735633..bcc8454 100644
--- a/user/src/com/google/gwt/requestfactory/server/ExceptionHandler.java
+++ b/user/src/com/google/gwt/requestfactory/server/ExceptionHandler.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -19,14 +19,19 @@
 
 /**
  * Handles an exception produced while processing a request.
- * 
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @see DefaultExceptionHandler
  */
+@Deprecated
 public interface ExceptionHandler {
   /**
    * Generates a {@link ServerFailure} based on the information contained in the
    * received {@code exception}.
-   * 
+   *
    * @param throwable a Throwable instance
    * @return a {@link ServerFailure} instance
    * @see DefaultExceptionHandler
diff --git a/user/src/com/google/gwt/requestfactory/server/LocatorServiceLayer.java b/user/src/com/google/gwt/requestfactory/server/LocatorServiceLayer.java
index 221f286..81815ff 100644
--- a/user/src/com/google/gwt/requestfactory/server/LocatorServiceLayer.java
+++ b/user/src/com/google/gwt/requestfactory/server/LocatorServiceLayer.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -31,7 +31,12 @@
 /**
  * Adds support to the ServiceLayer chain for using {@link Locator} and
  * {@link ServiceLocator} helper objects.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 final class LocatorServiceLayer extends ServiceLayerDecorator {
 
   @Override
diff --git a/user/src/com/google/gwt/requestfactory/server/Logging.java b/user/src/com/google/gwt/requestfactory/server/Logging.java
index cbcb1b0..e13298a 100644
--- a/user/src/com/google/gwt/requestfactory/server/Logging.java
+++ b/user/src/com/google/gwt/requestfactory/server/Logging.java
@@ -1,19 +1,18 @@
 /*
  * Copyright 2010 Google Inc.
- * 
+ *
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- * 
+ *
  * http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-
 package com.google.gwt.requestfactory.server;
 
 import com.google.gwt.logging.server.RemoteLoggingServiceUtil;
@@ -26,7 +25,12 @@
 /**
  * Server side object that handles log messages sent by
  * {@link com.google.gwt.requestfactory.client.RequestFactoryLogHandler}.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class Logging {
 
   private static StackTraceDeobfuscator deobfuscator = new StackTraceDeobfuscator(
@@ -34,7 +38,7 @@
 
   /**
    * Logs a message.
-   * 
+   *
    * @param serializedLogRecordString a json serialized LogRecord, as provided by
    * {@link com.google.gwt.logging.client.JsonLogRecordClientUtil.logRecordAsJsonObject(LogRecord)}
    * @throws RemoteLoggingException if logging fails
@@ -58,7 +62,7 @@
   /**
    * This function is only for server side use which is why it's not in the
    * LoggingRequest interface.
-   * 
+   *
    * @param dir a directory, specified as a String
    */
   public static void setSymbolMapsDirectory(String dir) {
@@ -71,7 +75,7 @@
 
   /**
    * Returns the id of this instance.
-   * 
+   *
    * @return a String id
    * @see #setId(String)
    */
@@ -81,7 +85,7 @@
 
   /**
    * Returns the version of this instance.
-   * 
+   *
    * @return an Integer version number
    * @see #setVersion(Integer)
    */
@@ -91,7 +95,7 @@
 
   /**
    * Sets the id on this instance.
-   * 
+   *
    * @param id a String id
    * @see #getId()
    */
@@ -101,7 +105,7 @@
 
   /**
    * Sets the version of this instance.
-   * 
+   *
    * @param version an Integer version number
    * @see #getVersion()
    */
diff --git a/user/src/com/google/gwt/requestfactory/server/ReflectiveServiceLayer.java b/user/src/com/google/gwt/requestfactory/server/ReflectiveServiceLayer.java
index 9445f54..25f4c76 100644
--- a/user/src/com/google/gwt/requestfactory/server/ReflectiveServiceLayer.java
+++ b/user/src/com/google/gwt/requestfactory/server/ReflectiveServiceLayer.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -43,7 +43,12 @@
 
 /**
  * Implements all methods that interact with domain objects.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 final class ReflectiveServiceLayer extends ServiceLayerDecorator {
   /*
    * NB: All calls that ReflectiveServiceLayer makes to public APIs inherited
diff --git a/user/src/com/google/gwt/requestfactory/server/ReportableException.java b/user/src/com/google/gwt/requestfactory/server/ReportableException.java
index 292b452..ba3da8c 100644
--- a/user/src/com/google/gwt/requestfactory/server/ReportableException.java
+++ b/user/src/com/google/gwt/requestfactory/server/ReportableException.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -17,7 +17,12 @@
 
 /**
  * Encapsulates exceptions that should be thrown back to the client.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 @SuppressWarnings("serial")
 class ReportableException extends RuntimeException {
   public ReportableException(String msg) {
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java b/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java
index f558697..8837c48 100644
--- a/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java
+++ b/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -64,7 +64,7 @@
  * JVM.
  * <p>
  * This class is amenable to being used as a unit test:
- * 
+ *
  * <pre>
  * public void testRequestFactory() {
  *   Logger logger = Logger.getLogger("");
@@ -76,18 +76,28 @@
  * </pre>
  * This class also has a {@code main} method and can be used as a build-time
  * tool:
- * 
+ *
  * <pre>
  * java -cp gwt-servlet.jar:your-code.jar \
  *   com.google.gwt.requestfactory.server.RequestFactoryInterfaceValidator \
  *   com.example.MyRequestFactory
  * </pre>
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class RequestFactoryInterfaceValidator {
   /**
    * An implementation of {@link Loader} that uses a {@link ClassLoader} to
    * retrieve the class files.
+   *
+   * <p><span style='color:red'>RequestFactory has moved to
+   * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
    */
+  @Deprecated
   public static class ClassLoaderLoader implements Loader {
     private final ClassLoader loader;
 
@@ -106,13 +116,18 @@
 
   /**
    * Abstracts the mechanism by which class files are loaded.
-   * 
+   *
+   * <p><span style='color:red'>RequestFactory has moved to
+   * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
+   *
    * @see ClassLoaderLoader
    */
+  @Deprecated
   public interface Loader {
     /**
      * Returns true if the specified resource can be loaded.
-     * 
+     *
      * @param resource a resource name (e.g. <code>com/example/Foo.class</code>)
      */
     boolean exists(String resource);
@@ -120,7 +135,7 @@
     /**
      * Returns an InputStream to access the specified resource, or
      * <code>null</code> if no such resource exists.
-     * 
+     *
      * @param resource a resource name (e.g. <code>com/example/Foo.class</code>)
      */
     InputStream getResourceAsStream(String resource);
@@ -130,7 +145,12 @@
    * Improves error messages by providing context for the user.
    * <p>
    * Visible for testing.
+   *
+   * <p><span style='color:red'>RequestFactory has moved to
+   * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
    */
+  @Deprecated
   static class ErrorContext {
     private final Logger logger;
     private final ErrorContext parent;
@@ -225,7 +245,12 @@
   /**
    * Used internally as a placeholder for types that cannot be mapped to a
    * domain object.
+   *
+   * <p><span style='color:red'>RequestFactory has moved to
+   * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
    */
+  @Deprecated
   interface MissingDomainType {
   }
 
@@ -721,7 +746,7 @@
    * equivalent domain method</li>
    * <li>All referenced proxy types are valid</li>
    * </ul>
-   * 
+   *
    * @param binaryName the binary name (e.g. {@link Class#getName()}) of the
    *          EntityProxy subtype
    */
@@ -733,7 +758,7 @@
    * Determine if the specified type implements a proxy interface and apply the
    * appropriate validations. This can be used as a general-purpose entry method
    * when writing unit tests.
-   * 
+   *
    * @param binaryName the binary name (e.g. {@link Class#getName()}) of the
    *          EntityProxy or ValueProxy subtype
    */
@@ -772,7 +797,7 @@
    * equivalent domain method</li>
    * <li>All referenced EntityProxy types are valid</li>
    * </ul>
-   * 
+   *
    * @param binaryName the binary name (e.g. {@link Class#getName()}) of the
    *          RequestContext subtype
    * @see #validateEntityProxy(String)
@@ -830,7 +855,7 @@
    * <li> <code>binaryName</code> implements RequestFactory</li>
    * <li>All referenced RequestContext types are valid</li>
    * </ul>
-   * 
+   *
    * @param binaryName the binary name (e.g. {@link Class#getName()}) of the
    *          RequestContext subtype
    * @see #validateRequestContext(String)
@@ -880,7 +905,7 @@
    * equivalent domain method</li>
    * <li>All referenced proxy types are valid</li>
    * </ul>
-   * 
+   *
    * @param binaryName the binary name (e.g. {@link Class#getName()}) of the
    *          EntityProxy subtype
    */
@@ -1521,7 +1546,7 @@
   /**
    * Load the classfile for the given binary name and apply the provided
    * visitor.
-   * 
+   *
    * @return <code>true</code> if the visitor was successfully visited
    */
   private boolean visit(ErrorContext logger, String internalName,
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java b/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
index cfac4b4..68a4e5a 100644
--- a/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
+++ b/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -29,8 +29,13 @@
 import javax.servlet.http.HttpServletResponse;
 
 /**
- * Handles GWT RequestFactory JSON requests. 
+ * Handles GWT RequestFactory JSON requests.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 @SuppressWarnings("serial")
 public class RequestFactoryServlet extends HttpServlet {
 
@@ -48,7 +53,7 @@
 
   /**
    * Returns the thread-local {@link HttpServletRequest}.
-   * 
+   *
    * @return an {@link HttpServletRequest} instance
    */
   public static HttpServletRequest getThreadLocalRequest() {
@@ -57,7 +62,7 @@
 
   /**
    * Returns the thread-local {@link HttpServletResponse}.
-   * 
+   *
    * @return an {@link HttpServletResponse} instance
    */
   public static HttpServletResponse getThreadLocalResponse() {
@@ -77,7 +82,7 @@
   /**
    * Use this constructor in subclasses to provide a custom
    * {@link ExceptionHandler}.
-   * 
+   *
    * @param exceptionHandler an {@link ExceptionHandler} instance
    * @param serviceDecorators an array of ServiceLayerDecorators that change how
    *          the RequestFactory request processor interact with the domain
@@ -92,7 +97,7 @@
 
   /**
    * Processes a POST to the server.
-   * 
+   *
    * @param request an {@link HttpServletRequest} instance
    * @param response an {@link HttpServletResponse} instance
    * @throws IOException if an internal I/O error occurs
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestState.java b/user/src/com/google/gwt/requestfactory/server/RequestState.java
index 520e6d7..2a29430 100644
--- a/user/src/com/google/gwt/requestfactory/server/RequestState.java
+++ b/user/src/com/google/gwt/requestfactory/server/RequestState.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -43,7 +43,12 @@
 /**
  * Encapsulates all state relating to the processing of a single request so that
  * the SimpleRequestProcessor can be stateless.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 class RequestState implements EntityCodex.EntitySource {
   final IdToEntityMap beans = new IdToEntityMap();
   private final Map<Object, SimpleProxyId<?>> domainObjectsToId;
@@ -292,4 +297,4 @@
     }
     return toReturn;
   }
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/requestfactory/server/Resolver.java b/user/src/com/google/gwt/requestfactory/server/Resolver.java
index 5762f28..1b2ca38 100644
--- a/user/src/com/google/gwt/requestfactory/server/Resolver.java
+++ b/user/src/com/google/gwt/requestfactory/server/Resolver.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -43,9 +43,14 @@
  * Responsible for converting between domain and client entities. This class has
  * a small amount of temporary state used to handle graph cycles and assignment
  * of synthetic ids.
- * 
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @see RequestState#getResolver()
  */
+@Deprecated
 class Resolver {
   /**
    * A parameterized type with a single parameter.
@@ -183,7 +188,7 @@
 
   /**
    * Given a domain object, return a value that can be encoded by the client.
-   * 
+   *
    * @param domainValue the domain object to be converted into a client-side
    *          value
    * @param assignableTo the type in the client to which the resolved value
@@ -199,7 +204,7 @@
 
   /**
    * Convert a client-side value into a domain value.
-   * 
+   *
    * @param maybeEntityProxy the client object to resolve
    * @param detectDeadEntities if <code>true</code> this method will throw a
    *          ReportableException containing a {@link DeadEntityException} if an
@@ -447,4 +452,4 @@
     throw new ReportableException("Unsupported domain type "
         + returnClass.getCanonicalName());
   }
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/requestfactory/server/ResolverServiceLayer.java b/user/src/com/google/gwt/requestfactory/server/ResolverServiceLayer.java
index c0f2f72..e142ead 100644
--- a/user/src/com/google/gwt/requestfactory/server/ResolverServiceLayer.java
+++ b/user/src/com/google/gwt/requestfactory/server/ResolverServiceLayer.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -33,7 +33,12 @@
 
 /**
  * Implements all of the resolution methods in ServiceLayer.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 final class ResolverServiceLayer extends ServiceLayerDecorator {
 
   private static final Logger log = Logger.getLogger(ServiceLayer.class.getName());
diff --git a/user/src/com/google/gwt/requestfactory/server/ServiceLayer.java b/user/src/com/google/gwt/requestfactory/server/ServiceLayer.java
index 753fe1d..300d24e 100644
--- a/user/src/com/google/gwt/requestfactory/server/ServiceLayer.java
+++ b/user/src/com/google/gwt/requestfactory/server/ServiceLayer.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -35,7 +35,12 @@
  * logic can be decorated by extending an {@link ServiceLayerDecorator}.
  * <p>
  * This API is subject to change in future releases.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public abstract class ServiceLayer {
   /*
    * NB: This type cannot be directly extended by the user since it has a
@@ -53,7 +58,7 @@
   /**
    * Create a RequestFactory ServiceLayer that is optionally modified by the
    * given decorators.
-   * 
+   *
    * @param decorators the decorators that will modify the behavior of the core
    *          service layer implementation
    * @return a ServiceLayer instance
@@ -101,7 +106,7 @@
 
   /**
    * Create an instance of the requested domain type.
-   * 
+   *
    * @param <T> the requested domain type
    * @param clazz the requested domain type
    * @return an instance of the requested domain type
@@ -110,7 +115,7 @@
 
   /**
    * Create an instance of the requested {@link Locator} type.
-   * 
+   *
    * @param <T> the requested Locator type
    * @param clazz the requested Locator type
    * @return an instance of the requested Locator type
@@ -120,7 +125,7 @@
   /**
    * Create an instance of a service object that can be used as the target for
    * the given method invocation.
-   * 
+   *
    * @param contextMethod a method defined in a RequestContext
    * @param domainMethod the method that the service object must implement
    * @return an instance of the requested service object
@@ -138,7 +143,7 @@
 
   /**
    * Determine the method to invoke when retrieving the given property.
-   * 
+   *
    * @param domainType a domain entity type
    * @param property the name of the property to be retrieved
    * @return the Method that should be invoked to retrieve the property or
@@ -154,7 +159,7 @@
    * <p>
    * The values returned from this method may be passed to
    * {@link #loadDomainObject(Class, Object)} in the future.
-   * 
+   *
    * @param domainObject a domain object
    * @return the persistent id of the domain object or {@code null} if the
    *         object is not persistent
@@ -165,7 +170,7 @@
    * Returns the type of object the domain type's {@code findFoo()} or
    * {@link com.google.gwt.requestfactory.shared.Locator#getId(Object)
    * Locator.getId()} expects to receive.
-   * 
+   *
    * @param domainType a domain entity type
    * @return the type of the persistent id value used to represent the domain
    *         type
@@ -174,7 +179,7 @@
 
   /**
    * Retrieve the named property from the domain object.
-   * 
+   *
    * @param domainObject the domain object being examined
    * @param property the property name
    * @return the value of the property
@@ -189,7 +194,7 @@
 
   /**
    * Determine the method to invoke when setting the given property.
-   * 
+   *
    * @param domainType a domain entity type
    * @param property the name of the property to be set
    * @return the Method that should be invoked to set the property or
@@ -202,7 +207,7 @@
    * persisted. The value returned from this method must be a simple type (e.g.
    * Integer, String) or a domain type for which a mapping to an EntityProxy or
    * Value proxy exists.
-   * 
+   *
    * @param domainObject a domain object
    * @return the version of the domain object or {@code null} if the object is
    *         not persistent
@@ -212,7 +217,7 @@
   /**
    * Invoke a domain service method. The underlying eventually calls
    * {@link Method#invoke(Object, Object...)}.
-   * 
+   *
    * @param domainMethod the method to invoke
    * @param args the arguments to pass to the method
    * @return the value returned from the method invocation
@@ -222,7 +227,7 @@
   /**
    * Returns {@code true} if the given domain object is still live (i.e. not
    * deleted) in the backing store.
-   * 
+   *
    * @param domainObject a domain entity
    * @return {@code true} if {@code domainObject} could be retrieved at a later
    *         point in time
@@ -232,7 +237,7 @@
   /**
    * Load an object from the backing store. This method may return {@code null}
    * to indicate that the requested object is no longer available.
-   * 
+   *
    * @param <T> the type of object to load
    * @param clazz the type of object to load
    * @param domainId an id previously returned from {@link #getId(Object)}
@@ -247,7 +252,7 @@
    * <p>
    * The default implementation of this method will delegate to
    * {@link #loadDomainObject(Class, Object)}.
-   * 
+   *
    * @param classes type type of each object to load
    * @param domainIds the ids previously returned from {@link #getId(Object)}
    * @return the requested objects, elements of which may be {@code null} if the
@@ -259,7 +264,7 @@
    * Determines if the invocation of a domain method requires a
    * {@link ServiceLocator} as the 0th parameter when passed into
    * {@link #invoke(Method, Object...)}.
-   * 
+   *
    * @param contextMethod a method defined in a RequestContext
    * @param domainMethod a domain method
    * @return {@code true} if a ServiceLocator is required
@@ -270,7 +275,7 @@
    * Given a type token previously returned from
    * {@link #resolveTypeToken(Class)}, return the Class literal associated with
    * the token.
-   * 
+   *
    * @param typeToken a string token
    * @return the type represented by the token
    */
@@ -280,7 +285,7 @@
    * Determine the type used by the client code to represent a given domain
    * type. If multiple proxy types have been mapped to the same domain type, the
    * {@code clientType} parameter is used to ensure assignability.
-   * 
+   *
    * @param domainClass the server-side type to be transported to the client
    * @param clientType the type to which the returned type must be assignable
    * @param required if {@code true} and no mapping is available, throw an
@@ -295,7 +300,7 @@
   /**
    * Determine the domain (server-side) type that the given client type is
    * mapped to.
-   * 
+   *
    * @param clientType a client-side type
    * @return the domain type that {@code clientType} represents
    */
@@ -305,7 +310,7 @@
    * Return the domain service method associated with a RequestContext method
    * declaration. The {@code requestContextMethod} will have been previously
    * resolved by {@link #resolveRequestContextMethod(String, String)}.
-   * 
+   *
    * @param requestContextMethod a RequestContext method declaration.
    * @return the domain service method that should be invoked
    */
@@ -314,7 +319,7 @@
   /**
    * Return the type of {@link Locator} that should be used to access the given
    * domain type.
-   * 
+   *
    * @param domainType a domain (server-side) type
    * @return the type of Locator to use, or {@code null} if the type conforms to
    *         the RequestFactory entity protocol
@@ -323,7 +328,7 @@
 
   /**
    * Find a RequestContext method declaration by name.
-   * 
+   *
    * @param requestContextClass the fully-qualified binary name of the
    *          RequestContext
    * @param methodName the name of the service method declared within the
@@ -347,7 +352,7 @@
    * {@link ServiceLocator} that should be used when invoking the domain method.
    * This method will only be called if {@link #requiresServiceLocator(Method)}
    * returned {@code true} for the associated domain method.
-   * 
+   *
    * @param contextMethod a RequestContext method declaration
    * @param domainMethod the domain method that will be invoked
    * @return the type of ServiceLocator to use
@@ -357,7 +362,7 @@
 
   /**
    * Return a string used to represent the given type in the wire protocol.
-   * 
+   *
    * @param proxyType a client-side EntityProxy or ValueProxy type
    * @return the type token used to represent the proxy type
    */
@@ -365,7 +370,7 @@
 
   /**
    * Sets a property on a domain object.
-   * 
+   *
    * @param domainObject the domain object to operate on
    * @param property the name of the property to set
    * @param expectedType the type of the property
@@ -377,7 +382,7 @@
   /**
    * Invoke a JSR 303 validator on the given domain object. If no validator is
    * available, this method is a no-op.
-   * 
+   *
    * @param <T> the type of data being validated
    * @param domainObject the domain objcet to validate
    * @return the violations associated with the domain object
diff --git a/user/src/com/google/gwt/requestfactory/server/ServiceLayerCache.java b/user/src/com/google/gwt/requestfactory/server/ServiceLayerCache.java
index 201fc62..097345a 100644
--- a/user/src/com/google/gwt/requestfactory/server/ServiceLayerCache.java
+++ b/user/src/com/google/gwt/requestfactory/server/ServiceLayerCache.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -31,7 +31,12 @@
  * A cache for idempotent methods in {@link ServiceLayer}. The caching is
  * separate from {@link ReflectiveServiceLayer} so that the cache can be applied
  * to any decorators injected by the user.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 class ServiceLayerCache extends ServiceLayerDecorator {
 
   /**
diff --git a/user/src/com/google/gwt/requestfactory/server/ServiceLayerDecorator.java b/user/src/com/google/gwt/requestfactory/server/ServiceLayerDecorator.java
index d93f359..9dfe5dd 100644
--- a/user/src/com/google/gwt/requestfactory/server/ServiceLayerDecorator.java
+++ b/user/src/com/google/gwt/requestfactory/server/ServiceLayerDecorator.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -36,7 +36,12 @@
  * {@link ServiceLayer#create(ServiceLayerDecorator...)}. The methods defined in
  * this type will automatically delegate to the next decorator or the root
  * service object after being processed by{@code create()}.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class ServiceLayerDecorator extends ServiceLayer {
   private static final Logger log = Logger.getLogger(ServiceLayer.class.getName());
 
@@ -187,7 +192,7 @@
    * should be used to provide diagnostic information that will help the
    * end-developer track down problems when that data would expose
    * implementation details of the server to the client.
-   * 
+   *
    * @param e a throwable with more data, may be {@code null}
    * @param message a printf-style format string
    * @param args arguments for the message
@@ -205,7 +210,7 @@
    * should use the instance provided by {@code getTop()} when calling public
    * methods on the ServiceLayer API to allow higher-level decorators to
    * override behaviors built into lower-level decorators.
-   * 
+   *
    * @return the ServiceLayer returned by
    *         {@link #create(ServiceLayerDecorator...)}
    */
@@ -216,7 +221,7 @@
   /**
    * Report an exception thrown by code that is under the control of the
    * end-developer.
-   * 
+   *
    * @param an {@link InvocationTargetException} thrown by an invocation of
    *          user-provided code
    * @throws ReportableException this method never returns normally
@@ -229,7 +234,7 @@
   /**
    * Return a message to the client. This method should not include any data
    * that was not sent to the server by the client to avoid leaking data.
-   * 
+   *
    * @param msg a printf-style format string
    * @param args arguments for the message
    * @throws ReportableException this method never returns normally
diff --git a/user/src/com/google/gwt/requestfactory/server/SignatureAdapter.java b/user/src/com/google/gwt/requestfactory/server/SignatureAdapter.java
index ebd6299..b8951c2 100644
--- a/user/src/com/google/gwt/requestfactory/server/SignatureAdapter.java
+++ b/user/src/com/google/gwt/requestfactory/server/SignatureAdapter.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -21,7 +21,12 @@
  * An empty implementation of SignatureVisitor, used by
  * {@link RequestFactoryInterfaceValidator}. This is a copy of the dev package's
  * EmptySignatureVisitor.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 class SignatureAdapter implements SignatureVisitor {
 
   private static final SignatureAdapter ignore = new SignatureAdapter();
@@ -82,4 +87,4 @@
 
   public void visitTypeVariable(String name) {
   }
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java b/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java
index 5eeb6b8..21934f1 100644
--- a/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java
+++ b/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -64,12 +64,22 @@
 /**
  * Processes request payloads from a RequestFactory client. This implementation
  * is stateless. A single instance may be reused and is thread-safe.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class SimpleRequestProcessor {
   /**
    * This parameterization is so long, it improves readability to have a
    * specific type.
+   *
+   * <p><span style='color:red'>RequestFactory has moved to
+   * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
    */
+  @Deprecated
   @SuppressWarnings("serial")
   static class IdToEntityMap extends
       HashMap<SimpleProxyId<?>, AutoBean<? extends BaseProxy>> {
@@ -113,7 +123,7 @@
 
   /**
    * Process a payload sent by a RequestFactory client.
-   * 
+   *
    * @param payload the payload sent by the client
    * @return a payload to return to the client
    */
diff --git a/user/src/com/google/gwt/requestfactory/server/UnexpectedException.java b/user/src/com/google/gwt/requestfactory/server/UnexpectedException.java
index f6cac79..9b1788b 100644
--- a/user/src/com/google/gwt/requestfactory/server/UnexpectedException.java
+++ b/user/src/com/google/gwt/requestfactory/server/UnexpectedException.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -18,7 +18,12 @@
 /**
  * Encapsulates exceptions that indicate something went wrong in RequestFactory
  * code.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 class UnexpectedException extends RuntimeException {
   public UnexpectedException(String msg, Throwable cause) {
     super(msg, cause);
diff --git a/user/src/com/google/gwt/requestfactory/server/impl/FindService.java b/user/src/com/google/gwt/requestfactory/server/impl/FindService.java
index 8f467e1..8e337eb 100644
--- a/user/src/com/google/gwt/requestfactory/server/impl/FindService.java
+++ b/user/src/com/google/gwt/requestfactory/server/impl/FindService.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -17,12 +17,17 @@
 
 /**
  * Server side service to support a generic find method.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class FindService {
 
   /**
    * For now, a simple implementation of find will work.
-   * 
+   *
    * @param entityInstance an entity instance
    * @return the passed-in entity instance
    */
diff --git a/user/src/com/google/gwt/requestfactory/server/package-info.java b/user/src/com/google/gwt/requestfactory/server/package-info.java
index 6d9ddab..70cf54c 100644
--- a/user/src/com/google/gwt/requestfactory/server/package-info.java
+++ b/user/src/com/google/gwt/requestfactory/server/package-info.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -17,13 +17,17 @@
 /**
  * Server side classes for mediating between the client side and the persistent
  * datastore.
- * 
+ *
  * This package contains classes that can receive client side read and write
  * requests in the JSON format, perform the necessary operations on the
  * persistent datastore, and return the results in JSON format.
- * 
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @since GWT 2.1
  */
+@Deprecated
 @com.google.gwt.util.PreventSpuriousRebuilds
 package com.google.gwt.requestfactory.server;
-
diff --git a/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestContext.java b/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestContext.java
index 2f74d22..01220ee 100644
--- a/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestContext.java
+++ b/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestContext.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -39,7 +39,12 @@
 
 /**
  * An in-process implementation of RequestContext
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 class InProcessRequestContext extends AbstractRequestContext {
   class RequestContextHandler implements InvocationHandler {
     public Object invoke(Object proxy, Method method, final Object[] args)
diff --git a/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestFactory.java b/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestFactory.java
index 836fb1a..ffc64fe 100644
--- a/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestFactory.java
+++ b/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestFactory.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -41,7 +41,12 @@
 
 /**
  * A JRE-compatible implementation of RequestFactory.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 class InProcessRequestFactory extends AbstractRequestFactory {
   @Category(value = {
       EntityProxyCategory.class, ValueProxyCategory.class,
diff --git a/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestTransport.java b/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestTransport.java
index b215b4f..4cd4f39 100644
--- a/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestTransport.java
+++ b/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestTransport.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -23,7 +23,7 @@
  * A RequesTransports that calls a {@link SimpleRequestProcessor}. This test can
  * be used for end-to-end tests of RequestFactory service methods in non-GWT
  * test suites.
- * 
+ *
  * <pre>
  * ServiceLayer serviceLayer = ServiceLayer.create();
  * SimpleRequestProcessor processor = new SimpleRequestProcessor(serviceLayer);
@@ -31,13 +31,18 @@
  * MyRequestFactory f = RequestFactoryMagic.create(MyRequestFactory.class);
  * f.initialize(eventBus, new InProcessRequestTransport(processor));
  * </pre>
- * 
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @see RequestFactoryMagic
  * @see com.google.gwt.requestfactory.server.ServiceLayer#create(com.google.gwt.requestfactory.server.ServiceLayerDecorator...)
  *      ServiceLayer.create()
  * @see com.google.gwt.event.shared.SimpleEventBus SimpleEventBus
  * @see SimpleRequestProcessor
  */
+@Deprecated
 public class InProcessRequestTransport implements RequestTransport {
   private static final boolean DUMP_PAYLOAD = Boolean.getBoolean("gwt.rpc.dumpPayload");
   private final SimpleRequestProcessor processor;
diff --git a/user/src/com/google/gwt/requestfactory/server/testing/RequestFactoryMagic.java b/user/src/com/google/gwt/requestfactory/server/testing/RequestFactoryMagic.java
index 3d1286d..706eebf 100644
--- a/user/src/com/google/gwt/requestfactory/server/testing/RequestFactoryMagic.java
+++ b/user/src/com/google/gwt/requestfactory/server/testing/RequestFactoryMagic.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -22,7 +22,14 @@
 
 /**
  * Create JRE-compatible instances of a RequestFactory interface.
+ *
+ * @deprecated Replaced by {@link com.google.web.bindery.vm.RequestFactorySource}
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class RequestFactoryMagic {
   /**
    * Create an instance of a RequestFactory. The returned RequestFactory must be
@@ -31,7 +38,7 @@
    * RequestTransport} via the
    * {@link RequestFactory#initialize(com.google.gwt.event.shared.EventBus, com.google.gwt.requestfactory.shared.RequestTransport)
    * initialize(EventBus, RequestTransport} method.
-   * 
+   *
    * @param <T> the RequestFactory type
    * @param requestFactory the RequestFactory type
    * @return an instance of the RequestFactory type
diff --git a/user/src/com/google/gwt/requestfactory/server/testing/package-info.java b/user/src/com/google/gwt/requestfactory/server/testing/package-info.java
index 23f9f88..7b3b710 100644
--- a/user/src/com/google/gwt/requestfactory/server/testing/package-info.java
+++ b/user/src/com/google/gwt/requestfactory/server/testing/package-info.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -17,7 +17,12 @@
 /**
  * Classes used for testing the request factory service.
  *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @since GWT 2.1.1
  */
+@Deprecated
 @com.google.gwt.util.PreventSpuriousRebuilds
 package com.google.gwt.requestfactory.server.testing;
diff --git a/user/src/com/google/gwt/requestfactory/shared/BaseProxy.java b/user/src/com/google/gwt/requestfactory/shared/BaseProxy.java
index 3764fc0..3944fa4 100644
--- a/user/src/com/google/gwt/requestfactory/shared/BaseProxy.java
+++ b/user/src/com/google/gwt/requestfactory/shared/BaseProxy.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -19,9 +19,14 @@
  * The root type from which all client-side proxy objects are derived. Users
  * should not extend the BaseProxy type directly, but use either
  * {@link EntityProxy} or {@link ValueProxy}.
- * 
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @see EntityProxy
  * @see ValueProxy
  */
+@Deprecated
 public interface BaseProxy {
 }
diff --git a/user/src/com/google/gwt/requestfactory/shared/DefaultProxyStore.java b/user/src/com/google/gwt/requestfactory/shared/DefaultProxyStore.java
index 26d9eef..632d034 100644
--- a/user/src/com/google/gwt/requestfactory/shared/DefaultProxyStore.java
+++ b/user/src/com/google/gwt/requestfactory/shared/DefaultProxyStore.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -27,7 +27,12 @@
 /**
  * An in-memory ProxyStore store that can encode its state as a JSON object
  * literal.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class DefaultProxyStore implements ProxyStore {
   /**
    * Provide a little bit of future-proofing to at least detect an encoded
@@ -52,7 +57,7 @@
   /**
    * Construct a DefaultProxyStore using the a value returned from
    * {@link #encode()}.
-   * 
+   *
    * @param payload a String previously returned from {@link #encode()}
    * @throws IllegalArgumentException if the payload cannot be parsed
    */
diff --git a/user/src/com/google/gwt/requestfactory/shared/EntityProxy.java b/user/src/com/google/gwt/requestfactory/shared/EntityProxy.java
index 4bb312d..9e73822 100644
--- a/user/src/com/google/gwt/requestfactory/shared/EntityProxy.java
+++ b/user/src/com/google/gwt/requestfactory/shared/EntityProxy.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -17,7 +17,12 @@
 
 /**
  * A proxy for a server-side domain object.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public interface EntityProxy extends BaseProxy {
   /**
    * Returns the {@link EntityProxyId} that identifies a particular instance of
@@ -30,7 +35,7 @@
    * Subtypes should override to declare they return a stable id of their own
    * type, to allow type safe use of the request objects returned by
    * {@link RequestFactory#find(EntityProxyId)}.
-   * 
+   *
    * @return an {@link EntityProxyId} instance
    */
   EntityProxyId<?> stableId();
diff --git a/user/src/com/google/gwt/requestfactory/shared/EntityProxyChange.java b/user/src/com/google/gwt/requestfactory/shared/EntityProxyChange.java
index 1c59370..64e08b9 100644
--- a/user/src/com/google/gwt/requestfactory/shared/EntityProxyChange.java
+++ b/user/src/com/google/gwt/requestfactory/shared/EntityProxyChange.java
@@ -31,8 +31,13 @@
  * <p>
  * TODO: use ProxyId rather than an empty proxy
  *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @param <P> the type of the proxy
  */
+@Deprecated
 public class EntityProxyChange<P extends EntityProxy> extends
     GwtEvent<EntityProxyChange.Handler<P>> {
 
@@ -40,7 +45,12 @@
    * Implemented by methods that handle EntityProxyChange events.
    *
    * @param <P> the proxy type
+   *
+   * <p><span style='color:red'>RequestFactory has moved to
+   * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
    */
+  @Deprecated
   public interface Handler<P extends EntityProxy> extends EventHandler {
     /**
      * Called when an {@link EntityProxyChange} event is fired.
diff --git a/user/src/com/google/gwt/requestfactory/shared/EntityProxyId.java b/user/src/com/google/gwt/requestfactory/shared/EntityProxyId.java
index a64e390..e56818f 100644
--- a/user/src/com/google/gwt/requestfactory/shared/EntityProxyId.java
+++ b/user/src/com/google/gwt/requestfactory/shared/EntityProxyId.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -21,15 +21,20 @@
  * <p>
  * In particular, an {@link EntityProxy} foo that is yet to be persisted and a
  * copy of foo after being persisted have equal {@link EntityProxyId}.
- * 
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @param <P> the entity type
  */
+@Deprecated
 @ProxyFor(Object.class)
 public interface EntityProxyId<P extends EntityProxy> {
 
   /**
    * Returns the class of the proxy identified.
-   * 
+   *
    * @return a Class object of type P
    */
   Class<P> getProxyClass();
diff --git a/user/src/com/google/gwt/requestfactory/shared/InstanceRequest.java b/user/src/com/google/gwt/requestfactory/shared/InstanceRequest.java
index 27e5055..a13be4d 100644
--- a/user/src/com/google/gwt/requestfactory/shared/InstanceRequest.java
+++ b/user/src/com/google/gwt/requestfactory/shared/InstanceRequest.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -19,15 +19,20 @@
  * Used to call instance methods. Note that this does not extend {@link Request}
  * &mdash; rather it vends one. There is no way to get an instance method's
  * {@link Request#fire} without assigning an instance by calling {@link #using}.
- * 
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @param <P> the instance type of BaseProxy
  * @param <T> the type eventually returned by the method invocation
  */
+@Deprecated
 public interface InstanceRequest<P extends BaseProxy, T> {
 
   /**
    * Provide the instance on which the request will be invoked.
-   * 
+   *
    * @param instanceObject an {@link BaseProxy} instance of type P
    * @return an instance of {@link Request}&lt;T&gt;
    */
diff --git a/user/src/com/google/gwt/requestfactory/shared/JsonRpcContent.java b/user/src/com/google/gwt/requestfactory/shared/JsonRpcContent.java
index 7fe081e..0dd5e05 100644
--- a/user/src/com/google/gwt/requestfactory/shared/JsonRpcContent.java
+++ b/user/src/com/google/gwt/requestfactory/shared/JsonRpcContent.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -25,8 +25,13 @@
  * declaration to indicate that a particular parameter is used as the
  * {@code request} portion of the JSON-RPC request. This is analogous to the
  * payload body in a REST-style request.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.PARAMETER)
+@Deprecated
 public @interface JsonRpcContent {
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/JsonRpcProxy.java b/user/src/com/google/gwt/requestfactory/shared/JsonRpcProxy.java
index 5cb7300..27f9222 100644
--- a/user/src/com/google/gwt/requestfactory/shared/JsonRpcProxy.java
+++ b/user/src/com/google/gwt/requestfactory/shared/JsonRpcProxy.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -18,6 +18,11 @@
 /**
  * <b>Experimental API, subject to change</b> Used instead of the
  * {@link ProxyFor} annotation.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public @interface JsonRpcProxy {
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/JsonRpcService.java b/user/src/com/google/gwt/requestfactory/shared/JsonRpcService.java
index e147b72..3b28a23 100644
--- a/user/src/com/google/gwt/requestfactory/shared/JsonRpcService.java
+++ b/user/src/com/google/gwt/requestfactory/shared/JsonRpcService.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -23,8 +23,13 @@
 /**
  * <b>Experimental API, subject to change</b> Indicates that a RequestContext
  * should be encoded as a JSON-RPC request.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.TYPE)
+@Deprecated
 public @interface JsonRpcService {
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/JsonRpcWireName.java b/user/src/com/google/gwt/requestfactory/shared/JsonRpcWireName.java
index 36449c4..9f506a6 100644
--- a/user/src/com/google/gwt/requestfactory/shared/JsonRpcWireName.java
+++ b/user/src/com/google/gwt/requestfactory/shared/JsonRpcWireName.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -21,10 +21,15 @@
 /**
  * <b>Experimental API, subject to change</b> Provides the method name for a
  * JSON-RPC invocation.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 @Retention(RetentionPolicy.RUNTIME)
 public @interface JsonRpcWireName {
   String value();
 
   String version() default "";
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/Locator.java b/user/src/com/google/gwt/requestfactory/shared/Locator.java
index 3516984..c031813 100644
--- a/user/src/com/google/gwt/requestfactory/shared/Locator.java
+++ b/user/src/com/google/gwt/requestfactory/shared/Locator.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -25,16 +25,22 @@
  * Locator subtypes must be default instantiable (i.e. public static types with
  * a no-arg constructor). Instances of Locators may be retained and reused by
  * the RequestFactory service layer.
- * 
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @param <T> the type of domain object the Locator will operate on
  * @param <I> the type of object the Locator expects to use as an id for the
  *          domain object
+ *
  * @see ProxyFor#locator()
  */
+@Deprecated
 public abstract class Locator<T, I> {
   /**
    * Create a new instance of the requested type.
-   * 
+   *
    * @param clazz the type of object to create
    * @return the new instance of the domain type
    */
@@ -43,7 +49,7 @@
   /**
    * Retrieve an object. May return {@code null} to indicate that the requested
    * object could not be found.
-   * 
+   *
    * @param clazz the type of object to retrieve
    * @param id an id previously returned from {@link #getId(Object)}
    * @return the requested object or {@code null} if it could not be found
@@ -59,7 +65,7 @@
    * Returns a domain object to be used as the id for the given object. This
    * method may return {@code null} if the object has not been persisted or
    * should be treated as irretrievable.
-   * 
+   *
    * @param domainObject the object to obtain an id for
    * @return the object's id or {@code null}
    */
@@ -74,7 +80,7 @@
    * Returns a domain object to be used as the version for the given object.
    * This method may return {@code null} if the object has not been persisted or
    * should be treated as irretrievable.
-   * 
+   *
    * @param domainObject the object to obtain an id for
    * @return the object's version or {@code null}
    */
diff --git a/user/src/com/google/gwt/requestfactory/shared/LoggingRequest.java b/user/src/com/google/gwt/requestfactory/shared/LoggingRequest.java
index 776d9f4..5ba468a 100644
--- a/user/src/com/google/gwt/requestfactory/shared/LoggingRequest.java
+++ b/user/src/com/google/gwt/requestfactory/shared/LoggingRequest.java
@@ -1,19 +1,18 @@
 /*
  * Copyright 2010 Google Inc.
- * 
+ *
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- * 
+ *
  * http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-
 package com.google.gwt.requestfactory.shared;
 
 import com.google.gwt.requestfactory.server.Logging;
@@ -21,7 +20,12 @@
 /**
  * "API Generated" request selector interface implemented by objects that give
  * client access to the methods of {@link Logging}.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 @Service(Logging.class)
 public interface LoggingRequest extends RequestContext {
 
diff --git a/user/src/com/google/gwt/requestfactory/shared/ProxyFor.java b/user/src/com/google/gwt/requestfactory/shared/ProxyFor.java
index 38f6df6..2c8d7c7 100644
--- a/user/src/com/google/gwt/requestfactory/shared/ProxyFor.java
+++ b/user/src/com/google/gwt/requestfactory/shared/ProxyFor.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -23,9 +23,14 @@
 /**
  * Annotation on EntityProxy and ValueProxy classes specifying the domain
  * (server-side) object type.
- * 
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @see ProxyForName
  */
+@Deprecated
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.TYPE)
 public @interface ProxyFor {
diff --git a/user/src/com/google/gwt/requestfactory/shared/ProxyForName.java b/user/src/com/google/gwt/requestfactory/shared/ProxyForName.java
index 5eb5594..843e791 100644
--- a/user/src/com/google/gwt/requestfactory/shared/ProxyForName.java
+++ b/user/src/com/google/gwt/requestfactory/shared/ProxyForName.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -24,7 +24,12 @@
  * Annotation on EntityProxy classes specifying the domain (server-side) object
  * type. This annotation can be used in place of {@link ProxyFor} if the domain
  * object is not available to the GWT compiler or DevMode runtime.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.TYPE)
 public @interface ProxyForName {
diff --git a/user/src/com/google/gwt/requestfactory/shared/ProxySerializer.java b/user/src/com/google/gwt/requestfactory/shared/ProxySerializer.java
index 3cfcafd..a55f2f7 100644
--- a/user/src/com/google/gwt/requestfactory/shared/ProxySerializer.java
+++ b/user/src/com/google/gwt/requestfactory/shared/ProxySerializer.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -25,11 +25,11 @@
  * are not stable.
  * <p>
  * To create a self-contained message that encapsulates a proxy:
- * 
+ *
  * <pre>
  * RequestFactory myFactory = ...;
  * MyFooProxy someProxy = ...;
- * 
+ *
  * DefaultProxyStore store = new DefaultProxyStore();
  * ProxySerializer ser = myFactory.getSerializer(store);
  * // More than one proxy could be serialized
@@ -37,26 +37,31 @@
  * // Create the flattened representation
  * String payload = store.encode();
  * </pre>
- * 
+ *
  * To recreate the object:
- * 
+ *
  * <pre>
  * ProxyStore store = new DefaultProxyStore(payload);
  * ProxySerializer ser = myFactory.getSerializer(store);
  * MyFooProxy someProxy = ser.deserialize(MyFooProxy.class, key);
  * </pre>
- * 
+ *
  * If two objects refer to different EntityProxy instances that have the same
  * stableId(), the last mutable proxy encountered will be preferred, otherwise
  * the first immutable proxy will be used.
- * 
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @see DefaultProxyStore
  */
+@Deprecated
 public interface ProxySerializer {
   /**
    * Recreate a proxy instance that was previously passed to
    * {@link #serialize(BaseProxy)}.
-   * 
+   *
    * @param <T> the type of proxy object to create
    * @param proxyType the type of proxy object to create
    * @param key a value previously returned from {@link #serialize(BaseProxy)}
@@ -68,7 +73,7 @@
   /**
    * Recreate a {@link EntityProxy} instance that was previously passed to
    * {@link #serialize(BaseProxy)}.
-   * 
+   *
    * @param <T> the type of proxy object to create
    * @param id the {@link EntityProxyId} of the desired entity
    * @return a new, immutable instance of the proxy or {@code null} if the data
@@ -78,7 +83,7 @@
 
   /**
    * Store a proxy into the backing store.
-   * 
+   *
    * @param proxy the proxy to store
    * @return a key value that can be passed to
    *         {@link #deserialize(Class, String)}
diff --git a/user/src/com/google/gwt/requestfactory/shared/ProxyStore.java b/user/src/com/google/gwt/requestfactory/shared/ProxyStore.java
index 412b070..0ea58ce 100644
--- a/user/src/com/google/gwt/requestfactory/shared/ProxyStore.java
+++ b/user/src/com/google/gwt/requestfactory/shared/ProxyStore.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -22,14 +22,19 @@
  * persistence mechanism. The ProxyStore does not need to be able to interpret
  * the data sent to it by the ProxySerializer; it is merely a wrapper around a
  * persistence mechanism.
- * 
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @see DefaultProxyStore
  */
+@Deprecated
 public interface ProxyStore {
   /**
    * Called by {@link ProxySerializer} to retrieve a value previously provided
    * to {@link #put(String, Splittable)}.
-   * 
+   *
    * @param key the key
    * @return the associated value or {@code null} if {@code key} is unknown
    */
@@ -44,10 +49,10 @@
 
   /**
    * Called by {@link ProxySerializer} to store a value.
-   * 
+   *
    * @param key a key value that will be passed to {@link #get(String)}
    * @param value the data to store
    * @see Splittable#getPayload()
    */
   void put(String key, Splittable value);
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/Receiver.java b/user/src/com/google/gwt/requestfactory/shared/Receiver.java
index a1a4fc6..fa44d8d 100644
--- a/user/src/com/google/gwt/requestfactory/shared/Receiver.java
+++ b/user/src/com/google/gwt/requestfactory/shared/Receiver.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -20,15 +20,20 @@
 /**
  * Callback object for {@link Request#fire(Receiver)} and
  * {@link RequestContext#fire(Receiver)}.
- * 
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @param <V> value type
  */
+@Deprecated
 public abstract class Receiver<V> {
   /**
    * Receives general failure notifications. The default implementation looks at
    * {@link ServerFailure#isFatal()}, and throws a runtime exception with the
    * failure object's error message if it is true.
-   * 
+   *
    * @param error a {@link ServerFailure} instance
    */
   public void onFailure(ServerFailure error) {
@@ -39,7 +44,7 @@
 
   /**
    * Called when a Request has been successfully executed on the server.
-   * 
+   *
    * @param response a response of type V
    */
   public abstract void onSuccess(V response);
@@ -48,7 +53,7 @@
    * Called if an object sent to the server could not be validated. The default
    * implementation calls {@link #onFailure(ServerFailure)} if <code>errors
    * </code> is not empty.
-   * 
+   *
    * @param errors a Set of {@link Violation} instances
    */
   public void onViolation(Set<Violation> errors) {
diff --git a/user/src/com/google/gwt/requestfactory/shared/Request.java b/user/src/com/google/gwt/requestfactory/shared/Request.java
index d8190d0..cee9753 100644
--- a/user/src/com/google/gwt/requestfactory/shared/Request.java
+++ b/user/src/com/google/gwt/requestfactory/shared/Request.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -17,9 +17,14 @@
 
 /**
  * Implemented by the request objects created by this factory.
- * 
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @param <T> The return type of objects in the corresponding response.
  */
+@Deprecated
 public interface Request<T> {
 
   /**
diff --git a/user/src/com/google/gwt/requestfactory/shared/RequestContext.java b/user/src/com/google/gwt/requestfactory/shared/RequestContext.java
index c5ad7af..ceaf7d9 100644
--- a/user/src/com/google/gwt/requestfactory/shared/RequestContext.java
+++ b/user/src/com/google/gwt/requestfactory/shared/RequestContext.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -17,13 +17,18 @@
 
 /**
  * The base interface for RequestFactory service endpoints.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public interface RequestContext {
   /**
    * Returns a new mutable proxy that this request can carry to the server,
    * perhaps to be persisted. If the object is succesfully persisted, a PERSIST
    * event will be posted including the EntityProxyId of this proxy.
-   * 
+   *
    * @param clazz a Class object of type T
    * @return an {@link BaseProxy} instance of type T
    */
@@ -33,7 +38,7 @@
    * Returns a mutable version of the proxy, whose mutations will accumulate in
    * this context. Proxies reached via getters on this mutable proxy will also
    * be mutable.
-   * 
+   *
    * @param object an instance of type T
    * @return an {@link EntityProxy} or {@link ValueProxy} instance of type T
    */
@@ -51,7 +56,7 @@
 
   /**
    * For receiving errors or validation failures only.
-   * 
+   *
    * @param receiver a {@link Receiver} instance
    * @throws IllegalArgumentException if <code>receiver</code> is
    *           <code>null</code>
@@ -63,7 +68,7 @@
    * context. Note that vacuous changes &mdash; e.g. foo.setName(foo.getName()
    * &mdash; will not trip the changed flag. Similarly, "unmaking" a change will
    * clear the isChanged flag
-   * 
+   *
    * <pre>
    * String name = bar.getName();
    * bar.setName("something else");
@@ -71,7 +76,7 @@
    * bar.setName(name);
    * assertFalse(context.isChanged());
    * </pre>
-   * 
+   *
    * @return {@code true} if any changes have been made
    */
   boolean isChanged();
diff --git a/user/src/com/google/gwt/requestfactory/shared/RequestFactory.java b/user/src/com/google/gwt/requestfactory/shared/RequestFactory.java
index 16d116c..ea2fca5 100644
--- a/user/src/com/google/gwt/requestfactory/shared/RequestFactory.java
+++ b/user/src/com/google/gwt/requestfactory/shared/RequestFactory.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -39,9 +39,14 @@
  * effect, treating it as an instance of the supertype. Returning abstract
  * supertypes of value types is not supported (e.g. Object, Enum, Number).
  * </p>
- * 
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @see com.google.gwt.requestfactory.server.testing.InProcessRequestTransport
  */
+@Deprecated
 public interface RequestFactory {
   /**
    * The JSON content type String.
@@ -50,7 +55,7 @@
 
   /**
    * Return a request to find a fresh instance of the referenced proxy.
-   * 
+   *
    * @param proxyId an {@link EntityProxyId} instance of type P
    * @return a {@link Request} object
    */
@@ -59,7 +64,7 @@
   /**
    * Returns the event bus this factory's events are posted on, which was set
    * via {@link #initialize}.
-   * 
+   *
    * @return the {@link EventBus} associated with this instance
    */
   EventBus getEventBus();
@@ -68,7 +73,7 @@
    * Get a {@link com.google.gwt.user.client.History} compatible token that
    * represents the given class. It can be processed by
    * {@link #getProxyClass(String)}
-   * 
+   *
    * @param clazz a Class object for an {@link EntityProxy} subclass
    * @return a {@link com.google.gwt.user.client.History} compatible token
    */
@@ -87,7 +92,7 @@
    * operation. In other words, the "future" history token returned for an
    * as-yet-unpersisted EntityProxy is only valid for the duration of the
    * RequestFactory's lifespan.
-   * 
+   *
    * @param proxy an {@link EntityProxyId} instance
    * @return a {@link com.google.gwt.user.client.History} compatible token
    */
@@ -98,7 +103,7 @@
    * type of this token, via {@link RequestContext#create}. The token may
    * represent either a proxy instance (see {@link #getHistoryToken}) or a proxy
    * class (see {@link #getProxyClass}).
-   * 
+   *
    * @param historyToken a String token
    * @return a Class object for an {@link EntityProxy} subclass
    */
@@ -107,7 +112,7 @@
   /**
    * Return the appropriate {@link EntityProxyId} using a string returned from
    * {@link #getHistoryToken(EntityProxyId)}.
-   * 
+   *
    * @param historyToken a String token
    * @return an {@link EntityProxyId}
    */
@@ -116,7 +121,7 @@
   /**
    * Returns a ProxySerializer that can encode and decode the various
    * EntityProxy and ValueProxy types reachable from the RequestFactory.
-   * 
+   *
    * @param store a helper object for the ProxySerializer to provide low-level
    *          storage access
    * @return a new ProxySerializer
@@ -127,14 +132,14 @@
   /**
    * Start this request factory with a
    * {@link com.google.gwt.requestfactory.client.DefaultRequestTransport}.
-   * 
+   *
    * @param eventBus an {@link EventBus}
    */
   void initialize(EventBus eventBus);
 
   /**
    * Start this request factory with a user-provided transport.
-   * 
+   *
    * @param eventBus an {@link EventBus}
    * @param transport a {@link RequestTransport} instance
    */
diff --git a/user/src/com/google/gwt/requestfactory/shared/RequestTransport.java b/user/src/com/google/gwt/requestfactory/shared/RequestTransport.java
index b3947cb..a12118a 100644
--- a/user/src/com/google/gwt/requestfactory/shared/RequestTransport.java
+++ b/user/src/com/google/gwt/requestfactory/shared/RequestTransport.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -18,24 +18,34 @@
 /**
  * Abstracts the mechanism by which a RequestFactory instance transmits its
  * payload to the backend.
- * 
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @see com.google.gwt.requestfactory.client.DefaultRequestTransport
  */
+@Deprecated
 public interface RequestTransport {
   /**
    * A callback interface.
+   *
+   * <p><span style='color:red'>RequestFactory has moved to
+   * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
    */
+  @Deprecated
   public interface TransportReceiver {
     /**
      * Called when the transmission succeeds.
-     * 
+     *
      * @param payload the String payload
      */
     void onTransportSuccess(String payload);
 
     /**
      * Called to report a transmission failure as a ServerFailure.
-     * 
+     *
      * @param message the String error message
      */
     void onTransportFailure(ServerFailure failure);
@@ -43,7 +53,7 @@
 
   /**
    * Called by the RequestFactory implementation.
-   * 
+   *
    * @param payload the String payload
    * @param receiver a {@link TransportReceiver} instance
    */
diff --git a/user/src/com/google/gwt/requestfactory/shared/ServerFailure.java b/user/src/com/google/gwt/requestfactory/shared/ServerFailure.java
index 91f2fa8..ba8016a 100644
--- a/user/src/com/google/gwt/requestfactory/shared/ServerFailure.java
+++ b/user/src/com/google/gwt/requestfactory/shared/ServerFailure.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -21,7 +21,12 @@
  * This error reporting mechanism is adequate at best. When RequestFactory is
  * extended to handle polymorphic types, this class will likely be replaced with
  * something more expressive.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class ServerFailure {
   private final String message;
   private final String stackTraceString;
@@ -44,7 +49,7 @@
 
   /**
    * Constructs a ServerFailure object.
-   * 
+   *
    * @param message a String containing the failure message
    * @param exceptionType a String containing the exception type
    * @param stackTraceString a String containing the stack trace
@@ -59,7 +64,7 @@
 
   /**
    * Return the exception type.
-   * 
+   *
    * @return the exception type as a String
    */
   public String getExceptionType() {
@@ -68,7 +73,7 @@
 
   /**
    * Return the failure message.
-   * 
+   *
    * @return the message as a String
    */
   public String getMessage() {
@@ -77,7 +82,7 @@
 
   /**
    * Return the failure stack trace.
-   * 
+   *
    * @return the stack trace as a String
    */
   public String getStackTraceString() {
@@ -87,7 +92,7 @@
   /**
    * Return true if this is a fatal error. The default implementation of
    * {@link Receiver#onFailure} throws a runtime exception for fatal failures.
-   * 
+   *
    * @return whether this is a fatal failure
    */
   public boolean isFatal() {
diff --git a/user/src/com/google/gwt/requestfactory/shared/Service.java b/user/src/com/google/gwt/requestfactory/shared/Service.java
index de14983..c88701c 100644
--- a/user/src/com/google/gwt/requestfactory/shared/Service.java
+++ b/user/src/com/google/gwt/requestfactory/shared/Service.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -23,9 +23,14 @@
 /**
  * Annotation on Request classes specifying the server side implementations that
  * back them.
- * 
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @see ServiceName
  */
+@Deprecated
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.TYPE)
 public @interface Service {
diff --git a/user/src/com/google/gwt/requestfactory/shared/ServiceLocator.java b/user/src/com/google/gwt/requestfactory/shared/ServiceLocator.java
index dfa841d..a6449da 100644
--- a/user/src/com/google/gwt/requestfactory/shared/ServiceLocator.java
+++ b/user/src/com/google/gwt/requestfactory/shared/ServiceLocator.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -23,13 +23,18 @@
  * ServiceLocator subtypes must be default instantiable (i.e. public static
  * types with a no-arg constructor). Instances of ServiceLocators may be
  * retained and reused by the RequestFactory service layer.
- * 
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @see Service#locator()
  */
+@Deprecated
 public interface ServiceLocator {
   /**
    * Returns an instance of the service object.
-   * 
+   *
    * @param clazz the requested type of service object
    * @return an instance of the service object
    */
diff --git a/user/src/com/google/gwt/requestfactory/shared/ServiceName.java b/user/src/com/google/gwt/requestfactory/shared/ServiceName.java
index 464c032..1f26404 100644
--- a/user/src/com/google/gwt/requestfactory/shared/ServiceName.java
+++ b/user/src/com/google/gwt/requestfactory/shared/ServiceName.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -24,7 +24,12 @@
  * Annotation on Request classes specifying the server side implementations that
  * back them.This annotation can be used in place of {@link Service} if the
  * service type is not available to the GWT compiler or DevMode runtime.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.TYPE)
 public @interface ServiceName {
diff --git a/user/src/com/google/gwt/requestfactory/shared/ValueProxy.java b/user/src/com/google/gwt/requestfactory/shared/ValueProxy.java
index 2882b3e..052f672 100644
--- a/user/src/com/google/gwt/requestfactory/shared/ValueProxy.java
+++ b/user/src/com/google/gwt/requestfactory/shared/ValueProxy.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -18,6 +18,11 @@
 /**
  * An analog to EntityProxy for domain types that do not have an identity
  * concept.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public interface ValueProxy extends BaseProxy {
 }
diff --git a/user/src/com/google/gwt/requestfactory/shared/Violation.java b/user/src/com/google/gwt/requestfactory/shared/Violation.java
index 23466b0..fab366d 100644
--- a/user/src/com/google/gwt/requestfactory/shared/Violation.java
+++ b/user/src/com/google/gwt/requestfactory/shared/Violation.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -18,19 +18,24 @@
 /**
  * A lightweight representation of a
  * {@link javax.validation.ConstraintViolation}.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public interface Violation {
   /**
    * If the ConstraintViolation occurred while validating a object, this method
    * will return a BaseProxy that contains the invalid values.
-   * 
+   *
    * @return the BaseProxy that caused the ConstraintViolation
    */
   BaseProxy getInvalidProxy();
 
   /**
    * Returns the message associated with this {@link Violation}.
-   * 
+   *
    * @return a String message
    */
   String getMessage();
@@ -39,7 +44,7 @@
    * If the ConstraintViolation occurred while validating a value object that
    * originated from the server, this method will return a BaseProxy that
    * contains the original values.
-   * 
+   *
    * @return the BaseProxy originally sent by the server or {@code null} if the
    *         BaseProxy was created on the client.
    */
@@ -47,7 +52,7 @@
 
   /**
    * Returns the path associated with this {@link Violation}.
-   * 
+   *
    * @return a String path
    */
   String getPath();
@@ -55,7 +60,7 @@
   /**
    * Returns the proxy id associated with this {@link Violation} if the object
    * associated with the violation is an {@link EntityProxy}.
-   * 
+   *
    * @return an {@link EntityProxyId} instance or {@code null} if the object is
    *         a ValueProxy.
    */
diff --git a/user/src/com/google/gwt/requestfactory/shared/WriteOperation.java b/user/src/com/google/gwt/requestfactory/shared/WriteOperation.java
index 89b5d9c..a10fea0 100644
--- a/user/src/com/google/gwt/requestfactory/shared/WriteOperation.java
+++ b/user/src/com/google/gwt/requestfactory/shared/WriteOperation.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -25,7 +25,12 @@
  * <li>A DELETE event is fired after a proxy that was deleted on the client is
  * deleted on the server as well.
  * </ul>
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public enum WriteOperation {
   PERSIST("PERSIST"), UPDATE("UPDATE"), DELETE("DELETE");
 
@@ -40,10 +45,10 @@
   /**
    * Returns the unobfuscated name of the event associated with this
    * {@link WriteOperation}.
-   * 
+   *
    * @return one of "PERSIST", "UPDATE", or "DELETE"
    */
   public String getUnObfuscatedEnumName() {
     return this.unObfuscatedEnumName;
   }
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequest.java b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequest.java
index 8524524..c68b081 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequest.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequest.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -32,9 +32,14 @@
 /**
  * Abstract implementation of {@link Request}. Each request stores a
  * {@link DeltaValueStoreJsonImpl}.
- * 
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @param <T> return type
  */
+@Deprecated
 public abstract class AbstractRequest<T> implements Request<T>,
     InstanceRequest<BaseProxy, T> {
 
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java
index 3d53b91..c906ac8 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -66,14 +66,24 @@
 
 /**
  * Base implementations for RequestContext services.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public abstract class AbstractRequestContext implements RequestContext,
     EntityCodex.EntitySource {
   /**
    * Allows the payload dialect to be injected into the AbstractRequestContext
    * without the caller needing to be concerned with how the implementation
    * object is instantiated.
+   *
+   * <p><span style='color:red'>RequestFactory has moved to
+   * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
    */
+  @Deprecated
   public enum Dialect {
     STANDARD {
       @Override
@@ -90,6 +100,12 @@
     abstract DialectImpl create(AbstractRequestContext context);
   }
 
+  /*
+   * <p><span style='color:red'>RequestFactory has moved to
+   * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
+   */
+  @Deprecated
   interface DialectImpl {
 
     void addInvocation(AbstractRequest<?> request);
@@ -515,7 +531,7 @@
   public boolean isChanged() {
     /*
      * NB: Don't use the presence of ephemeral objects for this test.
-     * 
+     *
      * Diff the objects until one is found to be different. It's not just a
      * simple flag-check because of the possibility of "unmaking" a change, per
      * the JavaDoc.
@@ -730,7 +746,7 @@
 
   /**
    * Create a new EntityProxy from a snapshot in the return payload.
-   * 
+   *
    * @param id the EntityProxyId of the object
    * @param returnRecord the JSON map containing property/value pairs
    * @param operations the WriteOperation eventns to broadcast over the EventBus
@@ -808,7 +824,7 @@
 
   /**
    * Get-or-create method for synthetic ids.
-   * 
+   *
    * @see #syntheticIds
    */
   private <Q extends BaseProxy> SimpleProxyId<Q> allocateSyntheticId(
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestFactory.java b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestFactory.java
index 3c26cf4..b52a4e6 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestFactory.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestFactory.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -31,7 +31,12 @@
 
 /**
  * Base type for generated RF interfaces.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public abstract class AbstractRequestFactory extends IdFactory implements
     RequestFactory {
   private static final int MAX_VERSION_ENTRIES = 10000;
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/BaseProxyCategory.java b/user/src/com/google/gwt/requestfactory/shared/impl/BaseProxyCategory.java
index 9782088..34b5eb4 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/BaseProxyCategory.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/BaseProxyCategory.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -24,7 +24,12 @@
 
 /**
  * Contains behaviors common to all proxy instances.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class BaseProxyCategory {
   /**
    * Sniff all return values and ensure that if the current bean is a mutable
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/Constants.java b/user/src/com/google/gwt/requestfactory/shared/impl/Constants.java
index ec0cba6..64f29eb 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/Constants.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/Constants.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -17,7 +17,12 @@
 
 /**
  * Contains a variety of AutoBean tag constants to prevent typos.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public interface Constants {
   String DOMAIN_OBJECT = "domainObject";
   String IN_RESPONSE = "inResponse";
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/EntityCodex.java b/user/src/com/google/gwt/requestfactory/shared/impl/EntityCodex.java
index 4304885..c89dab8 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/EntityCodex.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/EntityCodex.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -32,11 +32,21 @@
 
 /**
  * Analogous to {@link ValueCodex}, but for object types.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class EntityCodex {
   /**
    * Abstracts the process by which EntityProxies are created.
+   *
+   * <p><span style='color:red'>RequestFactory has moved to
+   * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
    */
+  @Deprecated
   public interface EntitySource {
     /**
      * Expects an encoded
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/EntityProxyCategory.java b/user/src/com/google/gwt/requestfactory/shared/impl/EntityProxyCategory.java
index 26ed7f2..6492817 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/EntityProxyCategory.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/EntityProxyCategory.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -24,7 +24,12 @@
 
 /**
  * Contains static implementation of EntityProxy-specific methods.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class EntityProxyCategory {
 
   /**
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/FindRequest.java b/user/src/com/google/gwt/requestfactory/shared/impl/FindRequest.java
index 7888fb1..94e3b08 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/FindRequest.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/FindRequest.java
@@ -1,19 +1,18 @@
 /*
  * Copyright 2010 Google Inc.
- * 
+ *
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- * 
+ *
  * http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-
 package com.google.gwt.requestfactory.shared.impl;
 
 import com.google.gwt.requestfactory.server.impl.FindService;
@@ -25,11 +24,20 @@
 
 /**
  * Request selector interface for implementing a find method.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 @Service(FindService.class)
 public interface FindRequest extends RequestContext {
   /**
    * Use the implicit lookup in passing EntityProxy types to service methods.
+   *
+   * <p><span style='color:red'>RequestFactory has moved to
+   * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
    */
   Request<EntityProxy> find(EntityProxyId<?> proxy);
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/IdFactory.java b/user/src/com/google/gwt/requestfactory/shared/impl/IdFactory.java
index aa3254d..fde538b 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/IdFactory.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/IdFactory.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -24,7 +24,12 @@
 
 /**
  * Handles common code for creating SimpleProxyIds.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public abstract class IdFactory {
   /**
    * Maps ephemeral history tokens to an id object. This canonicalizing mapping
@@ -246,4 +251,4 @@
     return toReturn;
   }
 
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/IdUtil.java b/user/src/com/google/gwt/requestfactory/shared/impl/IdUtil.java
index fd71302..874b786 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/IdUtil.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/IdUtil.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -17,7 +17,12 @@
 
 /**
  * Common functions for slicing and dicing EntityProxy ids.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 class IdUtil {
   private static final String ANY_SEPARATOR_PATTERN = "@[012]@";
   private static final String EPHEMERAL_SEPARATOR = "@1@";
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/MessageFactoryHolder.java b/user/src/com/google/gwt/requestfactory/shared/impl/MessageFactoryHolder.java
index a36a6ae..7775118 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/MessageFactoryHolder.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/MessageFactoryHolder.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -20,7 +20,12 @@
 
 /**
  * This class has a super-source version with a client-only implementation.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public interface MessageFactoryHolder {
   MessageFactory FACTORY = AutoBeanFactoryMagic.create(MessageFactory.class);
 }
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/Poser.java b/user/src/com/google/gwt/requestfactory/shared/impl/Poser.java
index 0c16b50..2144916 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/Poser.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/Poser.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -18,9 +18,14 @@
 /**
  * Used to lock down mutable, non-proxy, value objects when their owning proxy
  * is frozen.
- * 
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @param <T> the type of simple value the Poser is standing in for
  */
+@Deprecated
 public interface Poser<T> {
   T getPosedValue();
 
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/ProxySerializerImpl.java b/user/src/com/google/gwt/requestfactory/shared/impl/ProxySerializerImpl.java
index 1e53bbb..2562795 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/ProxySerializerImpl.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/ProxySerializerImpl.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -38,7 +38,12 @@
 
 /**
  * The default implementation of ProxySerializer.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 class ProxySerializerImpl extends AbstractRequestContext implements
     ProxySerializer {
 
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/RequestData.java b/user/src/com/google/gwt/requestfactory/shared/impl/RequestData.java
index db66e2c..0da33ff 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/RequestData.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/RequestData.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -23,7 +23,12 @@
 /**
  * A class that encapsulates the parameters and method name to be invoked on the
  * server.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class RequestData {
   private final Class<?> elementType;
   private final String operation;
@@ -111,7 +116,7 @@
 
   /**
    * Represents the {@code request} object in a JSON-RPC request.
-   * 
+   *
    * @see com.google.gwt.requestfactory.shared.JsonRpcContent
    */
   public void setRequestContent(Object requestContent) {
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/SimpleEntityProxyId.java b/user/src/com/google/gwt/requestfactory/shared/impl/SimpleEntityProxyId.java
index 027a1d6..50366b1 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/SimpleEntityProxyId.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/SimpleEntityProxyId.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -21,9 +21,14 @@
 /**
  * Extends SimpleProxyId with the correct parameterization to implement
  * EntityProxyId.
- * 
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @param <P> the type of EntityProxy object the id describes
  */
+@Deprecated
 public class SimpleEntityProxyId<P extends EntityProxy> extends
     SimpleProxyId<P> implements EntityProxyId<P> {
 
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/SimpleProxyId.java b/user/src/com/google/gwt/requestfactory/shared/impl/SimpleProxyId.java
index dce3a15..fb13712 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/SimpleProxyId.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/SimpleProxyId.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -23,9 +23,14 @@
  * EntityProxy as far as metadata maintenance is concerned. There is a specific
  * subtype {@link SimpleEntityProxyId} which implements the requisite public
  * interface for EntityProxy types.
- * 
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @param <P> the type of BaseProxy object the id describes
  */
+@Deprecated
 public class SimpleProxyId<P extends BaseProxy> {
   /**
    * A placeholder value for {@link #clientId} to indicate the id was not
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/TypeLibrary.java b/user/src/com/google/gwt/requestfactory/shared/impl/TypeLibrary.java
index 7234528..b80bec3 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/TypeLibrary.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/TypeLibrary.java
@@ -27,7 +27,12 @@
 /**
  * Utility methods for querying, encoding, and decoding typed
  * payload data.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class TypeLibrary {
 
   public static final Collection<Class<?>> VALUE_TYPES;
@@ -60,5 +65,5 @@
 
   public static boolean isValueType(Class<?> type) {
     return VALUE_TYPES.contains(type);
-  }  
+  }
 }
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/ValueProxyCategory.java b/user/src/com/google/gwt/requestfactory/shared/impl/ValueProxyCategory.java
index 5550921..c858f8e 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/ValueProxyCategory.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/ValueProxyCategory.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -23,7 +23,12 @@
 
 /**
  * Contains static implementation of ValueProxy-specific methods.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public class ValueProxyCategory {
 
   /**
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/posers/DatePoser.java b/user/src/com/google/gwt/requestfactory/shared/impl/posers/DatePoser.java
index 403ebc3..559de8d 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/posers/DatePoser.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/posers/DatePoser.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -21,8 +21,12 @@
 
 /**
  * A sometimes-mutable implementation of {@link Date}.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
-@SuppressWarnings("deprecation")
+@Deprecated
 public class DatePoser extends Date implements Poser<Date> {
   private boolean frozen;
 
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/IdMessage.java b/user/src/com/google/gwt/requestfactory/shared/messages/IdMessage.java
index 2b8919b..9e4a8de 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/IdMessage.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/IdMessage.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -19,11 +19,21 @@
 
 /**
  * Used as a base type for messages that are about a particular id.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public interface IdMessage {
   /**
    * Describes the longevity of the id.
+   *
+   * <p><span style='color:red'>RequestFactory has moved to
+   * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+   * removed in a future version of GWT.</span></p>
    */
+  @Deprecated
   public enum Strength {
     /**
      * The id is indefinitely persistent and can be freely reused by the client
@@ -81,4 +91,4 @@
 
   @PropertyName(TYPE_TOKEN)
   void setTypeToken(String value);
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/InvocationMessage.java b/user/src/com/google/gwt/requestfactory/shared/messages/InvocationMessage.java
index 0bd07e2..62491d3 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/InvocationMessage.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/InvocationMessage.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -23,7 +23,12 @@
 
 /**
  * Describes a method invocation.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public interface InvocationMessage {
   String OPERATIONS = "O";
   String PARAMETERS = "P";
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/JsonRpcRequest.java b/user/src/com/google/gwt/requestfactory/shared/messages/JsonRpcRequest.java
index 063f315..711a199 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/JsonRpcRequest.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/JsonRpcRequest.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -22,7 +22,12 @@
 
 /**
  * A JSON-RPC request payload.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public interface JsonRpcRequest {
   String getApiVersion();
 
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/MessageFactory.java b/user/src/com/google/gwt/requestfactory/shared/messages/MessageFactory.java
index c38216e..278d96f 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/MessageFactory.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/MessageFactory.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -20,14 +20,19 @@
 
 /**
  * The factory for creating RequestFactory wire messages.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public interface MessageFactory extends AutoBeanFactory {
   AutoBean<ServerFailureMessage> failure();
 
   AutoBean<IdMessage> id();
 
   AutoBean<InvocationMessage> invocation();
-  
+
   AutoBean<JsonRpcRequest> jsonRpcRequest();
 
   AutoBean<OperationMessage> operation();
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/OperationMessage.java b/user/src/com/google/gwt/requestfactory/shared/messages/OperationMessage.java
index c57c934..870af5f 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/OperationMessage.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/OperationMessage.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -23,7 +23,12 @@
 
 /**
  * Represents an operation to be carried out on a single entity on the server.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public interface OperationMessage extends IdMessage, VersionedMessage {
   String OPERATION = "O";
   String PROPERTY_MAP = "P";
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/RequestMessage.java b/user/src/com/google/gwt/requestfactory/shared/messages/RequestMessage.java
index 660f50c..2079898 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/RequestMessage.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/RequestMessage.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -21,7 +21,12 @@
 
 /**
  * The message sent from the client to the server.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public interface RequestMessage extends VersionedMessage {
   String INVOCATION = "I";
   String OPERATIONS = "O";
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/ResponseMessage.java b/user/src/com/google/gwt/requestfactory/shared/messages/ResponseMessage.java
index 2555ea5..4628400 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/ResponseMessage.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/ResponseMessage.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -22,7 +22,12 @@
 
 /**
  * The result of fulfilling a request on the server.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public interface ResponseMessage extends VersionedMessage {
   String GENERAL_FAILURE = "F";
   String INVOCATION_RESULTS = "I";
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/ServerFailureMessage.java b/user/src/com/google/gwt/requestfactory/shared/messages/ServerFailureMessage.java
index 27a5007..89f780d 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/ServerFailureMessage.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/ServerFailureMessage.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -19,7 +19,12 @@
 
 /**
  * Encapsulates a ServerFailure object.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public interface ServerFailureMessage {
   String EXCEPTION_TYPE = "X";
   String MESSAGE = "M";
@@ -34,7 +39,7 @@
 
   @PropertyName(STACK_TRACE)
   String getStackTrace();
-  
+
   @PropertyName(FATAL)
   boolean isFatal();
 
@@ -46,7 +51,7 @@
 
   @PropertyName(MESSAGE)
   void setMessage(String message);
-  
+
   @PropertyName(STACK_TRACE)
   void setStackTrace(String stackTrace);
 }
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/VersionedMessage.java b/user/src/com/google/gwt/requestfactory/shared/messages/VersionedMessage.java
index e216e37..6a11990 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/VersionedMessage.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/VersionedMessage.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -19,7 +19,12 @@
 
 /**
  * Describes a message that contains version information.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public interface VersionedMessage {
   String VERSION = "V";
 
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/ViolationMessage.java b/user/src/com/google/gwt/requestfactory/shared/messages/ViolationMessage.java
index b891ac0..a864a6f 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/ViolationMessage.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/ViolationMessage.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -19,7 +19,12 @@
 
 /**
  * Represents a ConstraintViolation.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 public interface ViolationMessage extends IdMessage {
   String MESSAGE = "M";
   String PATH = "P";
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/package-info.java b/user/src/com/google/gwt/requestfactory/shared/messages/package-info.java
index b396dfc..c318718 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/package-info.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/package-info.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -16,9 +16,13 @@
 
 /**
  * Contains classes that define the RequestFactory wire format.
- * 
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @since GWT 2.1.1
  */
+@Deprecated
 @com.google.gwt.util.PreventSpuriousRebuilds
 package com.google.gwt.requestfactory.shared.messages;
-
diff --git a/user/src/com/google/gwt/requestfactory/shared/package-info.java b/user/src/com/google/gwt/requestfactory/shared/package-info.java
index 3d5c70e..977284c 100644
--- a/user/src/com/google/gwt/requestfactory/shared/package-info.java
+++ b/user/src/com/google/gwt/requestfactory/shared/package-info.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -15,9 +15,15 @@
  */
 
 /**
- * Shared classes used on both the client and the server side for transmitting data between the sever and the client in JSON format.
+ * Shared classes used on both the client and the server side for
+ * transmitting data between the sever and the client in JSON format.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  *
  * @since GWT 2.1
  */
+@Deprecated
 @com.google.gwt.util.PreventSpuriousRebuilds
 package com.google.gwt.requestfactory.shared;
diff --git a/user/src/com/google/gwt/requestfactory/ui/client/EntityProxyKeyProvider.java b/user/src/com/google/gwt/requestfactory/ui/client/EntityProxyKeyProvider.java
index 84d715f..f42a6ab 100644
--- a/user/src/com/google/gwt/requestfactory/ui/client/EntityProxyKeyProvider.java
+++ b/user/src/com/google/gwt/requestfactory/ui/client/EntityProxyKeyProvider.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -22,13 +22,18 @@
  * An {@link EntityProxy}-aware key provider, handy for use with
  * {@link com.google.gwt.view.client.SelectionModel} and various
  * cell widgets.
- * 
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
+ * @param <P> the proxy type
+ *
  * @see com.google.gwt.user.cellview.client.CellBrowser
  * @see com.google.gwt.user.cellview.client.CellList
  * @see com.google.gwt.user.cellview.client.CellTable
- * 
- * @param <P> the proxy type
  */
+@Deprecated
 public class EntityProxyKeyProvider<P extends EntityProxy> implements
     ProvidesKey<P> {
   /**
diff --git a/user/src/com/google/gwt/requestfactory/ui/client/ProxyRenderer.java b/user/src/com/google/gwt/requestfactory/ui/client/ProxyRenderer.java
index 2ddf49c..8390acf 100644
--- a/user/src/com/google/gwt/requestfactory/ui/client/ProxyRenderer.java
+++ b/user/src/com/google/gwt/requestfactory/ui/client/ProxyRenderer.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -20,9 +20,14 @@
 /**
  * Renders a proxy object, and reports the properties it requires to do that
  * rendering.
- * 
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
+ *
  * @param <R> the type to render
  */
+@Deprecated
 public abstract class ProxyRenderer<R> extends
     AbstractRenderer<R> {
 
diff --git a/user/src/com/google/gwt/requestfactory/ui/client/package-info.java b/user/src/com/google/gwt/requestfactory/ui/client/package-info.java
index 30e5f71..6d1c658 100644
--- a/user/src/com/google/gwt/requestfactory/ui/client/package-info.java
+++ b/user/src/com/google/gwt/requestfactory/ui/client/package-info.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -16,6 +16,11 @@
 
 /**
  * Classes used by the request factory to manage proxies, user logins, and authentication.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>.  This package will be
+ * removed in a future version of GWT.</span></p>
  */
+@Deprecated
 @com.google.gwt.util.PreventSpuriousRebuilds
 package com.google.gwt.requestfactory.ui.client;
diff --git a/user/src/com/google/gwt/user/client/ui/Widget.java b/user/src/com/google/gwt/user/client/ui/Widget.java
index 7bc1ba3..932aaee 100644
--- a/user/src/com/google/gwt/user/client/ui/Widget.java
+++ b/user/src/com/google/gwt/user/client/ui/Widget.java
@@ -23,7 +23,6 @@
 import com.google.gwt.event.shared.EventHandler;
 import com.google.gwt.event.shared.GwtEvent;
 import com.google.gwt.event.shared.HandlerManager;
-import com.google.gwt.event.shared.GwtEvent.Type;
 import com.google.gwt.event.shared.HandlerRegistration;
 import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.Event;
@@ -246,8 +245,8 @@
    *
    * @return the {@link HandlerManager} you want to use
    */
-  protected com.google.gwt.event.shared.HandlerManager createHandlerManager() {
-    return new com.google.gwt.event.shared.HandlerManager(this);
+  protected HandlerManager createHandlerManager() {
+    return new HandlerManager(this);
   }
 
   /**
@@ -289,7 +288,7 @@
    * @param type the event type
    * @return the number of registered handlers
    */
-  protected int getHandlerCount(Type<?> type) {
+  protected int getHandlerCount(GwtEvent.Type<?> type) {
     return handlerManager == null ? 0 : handlerManager.getHandlerCount(type);
   }
 
@@ -412,12 +411,12 @@
    *
    * @return the handler manager
    * */
-  com.google.gwt.event.shared.HandlerManager ensureHandlers() {
+  HandlerManager ensureHandlers() {
     return handlerManager == null ? handlerManager = createHandlerManager()
         : handlerManager;
   }
 
-  com.google.gwt.event.shared.HandlerManager getHandlerManager() {
+  HandlerManager getHandlerManager() {
     return handlerManager;
   }
 
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/event/Event.gwt.xml b/user/src/com/google/web/bindery/event/Event.gwt.xml
new file mode 100644
index 0000000..8bafd18
--- /dev/null
+++ b/user/src/com/google/web/bindery/event/Event.gwt.xml
@@ -0,0 +1,18 @@
+<!--
+  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.
+-->
+<module>
+  <source path="shared" />
+</module>
diff --git a/user/src/com/google/web/bindery/event/shared/Event.java b/user/src/com/google/web/bindery/event/shared/Event.java
new file mode 100644
index 0000000..487771f
--- /dev/null
+++ b/user/src/com/google/web/bindery/event/shared/Event.java
@@ -0,0 +1,125 @@
+/*
+ * 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.event.shared;
+
+/**
+ * Base Event object.
+ * 
+ * @param <H> interface implemented by handlers of this kind of event
+ */
+public abstract class Event<H> {
+  /**
+   * Type class used to register events with an {@link EventBus}.
+   * 
+   * @param <H> handler type
+   */
+  public static class Type<H> {
+    private static int nextHashCode;
+    private final int index;
+
+    /**
+     * Constructor.
+     */
+    public Type() {
+      index = ++nextHashCode;
+    }
+
+    @Override
+    public final int hashCode() {
+      return index;
+    }
+
+    @Override
+    public String toString() {
+      return "Event type";
+    }
+  }
+
+  private Object source;
+
+  /**
+   * Constructor.
+   */
+  protected Event() {
+  }
+
+  /**
+   * Returns the {@link Type} used to register this event, allowing an
+   * {@link EventBus} to find handlers of the appropriate class.
+   * 
+   * @return the type
+   */
+  public abstract Type<H> getAssociatedType();
+
+  /**
+   * Returns the source for this event. The type and meaning of the source is
+   * arbitrary, and is most useful as a secondary key for handler registration.
+   * (See {@link EventBus#addHandlerToSource}, which allows a handler to
+   * register for events of a particular type, tied to a particular source.)
+   * <p>
+   * Note that the source is actually set at dispatch time, e.g. via
+   * {@link EventBus#fireEventFromSource(Event, Object)}.
+   * 
+   * @return object representing the source of this event
+   */
+  public Object getSource() {
+    return source;
+  }
+
+  /**
+   * This is a method used primarily for debugging. It gives a string
+   * representation of the event details. This does not override the toString
+   * method because the compiler cannot always optimize toString out correctly.
+   * Event types should override as desired.
+   * 
+   * @return a string representing the event's specifics.
+   */
+  public String toDebugString() {
+    String name = this.getClass().getName();
+    name = name.substring(name.lastIndexOf(".") + 1);
+    return "event: " + name + ":";
+  }
+
+  /**
+   * The toString() for abstract event is overridden to avoid accidently
+   * including class literals in the the compiled output. Use {@link Event}
+   * #toDebugString to get more information about the event.
+   */
+  @Override
+  public String toString() {
+    return "An event type";
+  }
+
+  /**
+   * Implemented by subclasses to to invoke their handlers in a type safe
+   * manner. Intended to be called by {@link EventBus#fireEvent(Event)} or
+   * {@link EventBus#fireEventFromSource(Event, Object)}.
+   * 
+   * @param handler handler
+   */
+  protected abstract void dispatch(H handler);
+
+  /**
+   * Set the source that triggered this event. Intended to be called by the
+   * {@link EventBus} during dispatch.
+   * 
+   * @param source the source of this event.
+   * @see EventBus#fireEventFromSource(Event, Object)
+   */
+  protected void setSource(Object source) {
+    this.source = source;
+  }
+}
diff --git a/user/src/com/google/web/bindery/event/shared/EventBus.java b/user/src/com/google/web/bindery/event/shared/EventBus.java
new file mode 100644
index 0000000..91ed3dc
--- /dev/null
+++ b/user/src/com/google/web/bindery/event/shared/EventBus.java
@@ -0,0 +1,91 @@
+/*
+ * 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.event.shared;
+
+import com.google.web.bindery.event.shared.Event.Type;
+
+/**
+ * Dispatches {@link Event}s to interested parties. Eases decoupling by allowing
+ * objects to interact without having direct dependencies upon one another, and
+ * without requiring event sources to deal with maintaining handler lists. There
+ * will typically be one EventBus per application, broadcasting events that may
+ * be of general interest.
+ * 
+ * @see SimpleEventBus
+ * @see ResettableEventBus
+ * @see com.google.web.bindery.event.shared.testing.CountingEventBus
+ */
+public abstract class EventBus {
+
+  /**
+   * Adds an unfiltered handler to receive events of this type from all sources.
+   * <p>
+   * It is rare to call this method directly. More typically an {@link Event}
+   * subclass will provide a static <code>register</code> method, or a widget
+   * will accept handlers directly.
+   * 
+   * @param <H> The type of handler
+   * @param type the event type associated with this handler
+   * @param handler the handler
+   * @return the handler registration, can be stored in order to remove the
+   *         handler later
+   */
+  public abstract <H> HandlerRegistration addHandler(Type<H> type, H handler);
+
+  /**
+   * Adds a handler to receive events of this type from the given source.
+   * <p>
+   * It is rare to call this method directly. More typically a {@link Event}
+   * subclass will provide a static <code>register</code> method, or a widget
+   * will accept handlers directly.
+   * 
+   * @param <H> The type of handler
+   * @param type the event type associated with this handler
+   * @param source the source associated with this handler
+   * @param handler the handler
+   * @return the handler registration, can be stored in order to remove the
+   *         handler later
+   */
+  public abstract <H> HandlerRegistration addHandlerToSource(Type<H> type, Object source, H handler);
+
+  /**
+   * Fires the event from no source. Only unfiltered handlers will receive it.
+   * <p>
+   * Any exceptions thrown by handlers will be bundled into a
+   * {@link UmbrellaException} and then re-thrown after all handlers have
+   * completed. An exception thrown by a handler will not prevent other handlers
+   * from executing.
+   * 
+   * @throws UmbrellaException wrapping exceptions thrown by handlers
+   *
+   * @param event the event to fire
+   */
+  public abstract void fireEvent(Event<?> event);
+
+  /**
+   * Fires the given event to the handlers listening to the event's type.
+   * <p>
+   * Any exceptions thrown by handlers will be bundled into a
+   * {@link UmbrellaException} and then re-thrown after all handlers have
+   * completed. An exception thrown by a handler will not prevent other handlers
+   * from executing.
+   * 
+   * @throws UmbrellaException wrapping exceptions thrown by handlers
+   * 
+   * @param event the event to fire
+   */
+  public abstract void fireEventFromSource(Event<?> event, Object source);
+}
diff --git a/user/src/com/google/web/bindery/event/shared/HandlerRegistration.java b/user/src/com/google/web/bindery/event/shared/HandlerRegistration.java
new file mode 100644
index 0000000..ce34e39
--- /dev/null
+++ b/user/src/com/google/web/bindery/event/shared/HandlerRegistration.java
@@ -0,0 +1,41 @@
+/*
+ * 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.event.shared;
+
+/**
+ * Registration objects returned when an event handler is bound (e.g. via
+ * {@link EventBus#addHandler}), used to deregister.
+ * <p>
+ * A tip: to make a handler deregister itself try something like the following:
+ * <code><pre>new MyHandler() {
+ *  HandlerRegistration reg = MyEvent.register(eventBus, this);
+ * 
+ *  public void onMyThing(MyEvent event) {
+ *    {@literal /}* do your thing *{@literal /}
+ *    reg.removeHandler();
+ *  }
+ * };
+ * </pre></code>
+ */
+public interface HandlerRegistration {
+
+  /**
+   * Deregisters the handler associated with this registration object if the
+   * handler is still attached to the event source. If the handler is no longer
+   * attached to the event source, this is a no-op.
+   */
+  void removeHandler();
+}
diff --git a/user/src/com/google/web/bindery/event/shared/ResettableEventBus.java b/user/src/com/google/web/bindery/event/shared/ResettableEventBus.java
new file mode 100644
index 0000000..a1b56ce
--- /dev/null
+++ b/user/src/com/google/web/bindery/event/shared/ResettableEventBus.java
@@ -0,0 +1,98 @@
+/*
+ * 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.event.shared;
+
+import com.google.web.bindery.event.shared.Event.Type;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * Wraps an EventBus to hold on to any HandlerRegistrations, so that they can
+ * easily all be cleared at once.
+ */
+public class ResettableEventBus extends EventBus {
+  private final EventBus wrapped;
+  private final Set<HandlerRegistration> registrations = new HashSet<HandlerRegistration>();
+
+  public ResettableEventBus(EventBus wrappedBus) {
+    this.wrapped = wrappedBus;
+  }
+
+  @Override
+  public <H> HandlerRegistration addHandler(Type<H> type, H handler) {
+    HandlerRegistration rtn = wrapped.addHandler(type, handler);
+    return doRegisterHandler(rtn);
+  }
+
+  @Override
+  public <H> HandlerRegistration addHandlerToSource(Event.Type<H> type, Object source, H handler) {
+    HandlerRegistration rtn = wrapped.addHandlerToSource(type, source, handler);
+    return doRegisterHandler(rtn);
+  }
+
+  @Override
+  public void fireEvent(Event<?> event) {
+    wrapped.fireEvent(event);
+  }
+
+  @Override
+  public void fireEventFromSource(Event<?> event, Object source) {
+    wrapped.fireEventFromSource(event, source);
+  }
+
+  /**
+   * Remove all handlers that have been added through this wrapper.
+   */
+  public void removeHandlers() {
+    Iterator<HandlerRegistration> it = registrations.iterator();
+    while (it.hasNext()) {
+      HandlerRegistration r = it.next();
+
+      /*
+       * must remove before we call removeHandler. Might have come from nested
+       * ResettableEventBus
+       */
+      it.remove();
+
+      r.removeHandler();
+    }
+  }
+
+  /**
+   *  Visible for testing
+   */
+  protected int getRegistrationSize() {
+    return registrations.size();
+  }
+
+  private HandlerRegistration doRegisterHandler(final HandlerRegistration registration) {
+    registrations.add(registration);
+    return new HandlerRegistration() {
+      public void removeHandler() {
+        doUnregisterHandler(registration);
+      }
+    };
+  }
+
+  private void doUnregisterHandler(HandlerRegistration registration) {
+    if (registrations.contains(registration)) {
+      registration.removeHandler();
+      registrations.remove(registration);
+    }
+  }
+}
diff --git a/user/src/com/google/web/bindery/event/shared/SimpleEventBus.java b/user/src/com/google/web/bindery/event/shared/SimpleEventBus.java
new file mode 100644
index 0000000..14a1f3c
--- /dev/null
+++ b/user/src/com/google/web/bindery/event/shared/SimpleEventBus.java
@@ -0,0 +1,310 @@
+/*
+ * 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.event.shared;
+
+import com.google.web.bindery.event.shared.Event.Type;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Basic implementation of {@link EventBus}.
+ */
+public class SimpleEventBus extends EventBus {
+  private interface Command {
+    void execute();
+  }
+
+  private final boolean isReverseOrder;
+
+  private int firingDepth = 0;
+
+  /**
+   * Add and remove operations received during dispatch.
+   */
+  private List<Command> deferredDeltas;
+
+  /**
+   * Map of event type to map of event source to list of their handlers.
+   */
+  private final Map<Event.Type<?>, Map<Object, List<?>>> map =
+      new HashMap<Event.Type<?>, Map<Object, List<?>>>();
+
+  public SimpleEventBus() {
+    this(false);
+  }
+
+  /**
+   * Allows creation of an instance that fires its handlers in the reverse of
+   * the order in which they were added, although filtered handlers all fire
+   * before unfiltered handlers.
+   * <p>
+   * 
+   * @deprecated This is a legacy feature, required by GWT's old HandlerManager.
+   *             Reverse order is not honored for handlers tied to a specific
+   *             event source (via {@link #addHandlerToSource}.
+   */
+  @Deprecated
+  protected SimpleEventBus(boolean fireInReverseOrder) {
+    isReverseOrder = fireInReverseOrder;
+  }
+
+  @Override
+  public <H> HandlerRegistration addHandler(Type<H> type, H handler) {
+    return doAdd(type, null, handler);
+  }
+
+  @Override
+  public <H> HandlerRegistration addHandlerToSource(final Event.Type<H> type, final Object source,
+      final H handler) {
+    if (source == null) {
+      throw new NullPointerException("Cannot add a handler with a null source");
+    }
+
+    return doAdd(type, source, handler);
+  }
+
+  @Override
+  public void fireEvent(Event<?> event) {
+    doFire(event, null);
+  }
+
+  @Override
+  public void fireEventFromSource(Event<?> event, Object source) {
+    if (source == null) {
+      throw new NullPointerException("Cannot fire from a null source");
+    }
+    doFire(event, source);
+  }
+
+  /**
+   * @deprecated required by legacy features in GWT's old HandlerManager
+   */
+  @Deprecated
+  protected <H> void doRemove(Event.Type<H> type, Object source, H handler) {
+    if (firingDepth > 0) {
+      enqueueRemove(type, source, handler);
+    } else {
+      doRemoveNow(type, source, handler);
+    }
+  }
+
+  /**
+   * @deprecated required by legacy features in GWT's old HandlerManager
+   */
+  @Deprecated
+  protected <H> H getHandler(Event.Type<H> type, int index) {
+    assert index < getHandlerCount(type) : "handlers for " + type.getClass() + " have size: "
+        + getHandlerCount(type) + " so do not have a handler at index: " + index;
+
+    List<H> l = getHandlerList(type, null);
+    return l.get(index);
+  }
+
+  /**
+   * @deprecated required by legacy features in GWT's old HandlerManager
+   */
+  @Deprecated
+  protected int getHandlerCount(Event.Type<?> eventKey) {
+    return getHandlerList(eventKey, null).size();
+  }
+
+  /**
+   * @deprecated required by legacy features in GWT's old HandlerManager
+   */
+  @Deprecated
+  protected boolean isEventHandled(Event.Type<?> eventKey) {
+    return map.containsKey(eventKey);
+  }
+
+  private void defer(Command command) {
+    if (deferredDeltas == null) {
+      deferredDeltas = new ArrayList<Command>();
+    }
+    deferredDeltas.add(command);
+  }
+
+  private <H> HandlerRegistration doAdd(final Event.Type<H> type, final Object source,
+      final H handler) {
+    if (type == null) {
+      throw new NullPointerException("Cannot add a handler with a null type");
+    }
+    if (handler == null) {
+      throw new NullPointerException("Cannot add a null handler");
+    }
+
+    if (firingDepth > 0) {
+      enqueueAdd(type, source, handler);
+    } else {
+      doAddNow(type, source, handler);
+    }
+
+    return new HandlerRegistration() {
+      public void removeHandler() {
+        doRemove(type, source, handler);
+      }
+    };
+  }
+
+  private <H> void doAddNow(Event.Type<H> type, Object source, H handler) {
+    List<H> l = ensureHandlerList(type, source);
+    l.add(handler);
+  }
+
+  private <H> void doFire(Event<H> event, Object source) {
+    if (event == null) {
+      throw new NullPointerException("Cannot fire null event");
+    }
+    try {
+      firingDepth++;
+
+      if (source != null) {
+        event.setSource(source);
+      }
+
+      List<H> handlers = getDispatchList(event.getAssociatedType(), source);
+      Set<Throwable> causes = null;
+
+      ListIterator<H> it =
+          isReverseOrder ? handlers.listIterator(handlers.size()) : handlers.listIterator();
+      while (isReverseOrder ? it.hasPrevious() : it.hasNext()) {
+        H handler = isReverseOrder ? it.previous() : it.next();
+
+        try {
+          event.dispatch(handler);
+        } catch (Throwable e) {
+          if (causes == null) {
+            causes = new HashSet<Throwable>();
+          }
+          causes.add(e);
+        }
+      }
+
+      if (causes != null) {
+        throw new UmbrellaException(causes);
+      }
+    } finally {
+      firingDepth--;
+      if (firingDepth == 0) {
+        handleQueuedAddsAndRemoves();
+      }
+    }
+  }
+
+  private <H> void doRemoveNow(Event.Type<H> type, Object source, H handler) {
+    List<H> l = getHandlerList(type, source);
+
+    boolean removed = l.remove(handler);
+    assert removed : "redundant remove call";
+    if (removed && l.isEmpty()) {
+      prune(type, source);
+    }
+  }
+
+  private <H> void enqueueAdd(final Event.Type<H> type, final Object source, final H handler) {
+    defer(new Command() {
+      public void execute() {
+        doAddNow(type, source, handler);
+      }
+    });
+  }
+
+  private <H> void enqueueRemove(final Event.Type<H> type, final Object source, final H handler) {
+    defer(new Command() {
+      public void execute() {
+        doRemoveNow(type, source, handler);
+      }
+    });
+  }
+
+  private <H> List<H> ensureHandlerList(Event.Type<H> type, Object source) {
+    Map<Object, List<?>> sourceMap = map.get(type);
+    if (sourceMap == null) {
+      sourceMap = new HashMap<Object, List<?>>();
+      map.put(type, sourceMap);
+    }
+
+    // safe, we control the puts.
+    @SuppressWarnings("unchecked")
+    List<H> handlers = (List<H>) sourceMap.get(source);
+    if (handlers == null) {
+      handlers = new ArrayList<H>();
+      sourceMap.put(source, handlers);
+    }
+
+    return handlers;
+  }
+
+  private <H> List<H> getDispatchList(Event.Type<H> type, Object source) {
+    List<H> directHandlers = getHandlerList(type, source);
+    if (source == null) {
+      return directHandlers;
+    }
+
+    List<H> globalHandlers = getHandlerList(type, null);
+
+    List<H> rtn = new ArrayList<H>(directHandlers);
+    rtn.addAll(globalHandlers);
+    return rtn;
+  }
+
+  private <H> List<H> getHandlerList(Event.Type<H> type, Object source) {
+    Map<Object, List<?>> sourceMap = map.get(type);
+    if (sourceMap == null) {
+      return Collections.emptyList();
+    }
+
+    // safe, we control the puts.
+    @SuppressWarnings("unchecked")
+    List<H> handlers = (List<H>) sourceMap.get(source);
+    if (handlers == null) {
+      return Collections.emptyList();
+    }
+
+    return handlers;
+  }
+
+  private void handleQueuedAddsAndRemoves() {
+    if (deferredDeltas != null) {
+      try {
+        for (Command c : deferredDeltas) {
+          c.execute();
+        }
+      } finally {
+        deferredDeltas = null;
+      }
+    }
+  }
+
+  private void prune(Event.Type<?> type, Object source) {
+    Map<Object, List<?>> sourceMap = map.get(type);
+
+    List<?> pruned = sourceMap.remove(source);
+
+    assert pruned != null : "Can't prune what wasn't there";
+    assert pruned.isEmpty() : "Pruned unempty list!";
+
+    if (sourceMap.isEmpty()) {
+      map.remove(type);
+    }
+  }
+}
\ No newline at end of file
diff --git a/user/src/com/google/web/bindery/event/shared/UmbrellaException.java b/user/src/com/google/web/bindery/event/shared/UmbrellaException.java
new file mode 100644
index 0000000..3ed254b
--- /dev/null
+++ b/user/src/com/google/web/bindery/event/shared/UmbrellaException.java
@@ -0,0 +1,58 @@
+/*
+ * 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.event.shared;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * A {@link RuntimeException} that collects a {@link Set} of child
+ * {@link Throwable}s together. Typically thrown after a loop, with all of the
+ * exceptions thrown during that loop, but delayed so that the loop finishes
+ * executing.
+ */
+public class UmbrellaException extends RuntimeException {
+
+  private static final String MSG =
+      "One or more exceptions caught, see full set in UmbrellaException#getCauses";
+  /**
+   * The causes of the exception.
+   */
+  private Set<Throwable> causes;
+
+  public UmbrellaException(Set<Throwable> causes) {
+    super(MSG, causes.size() == 0 ? null : causes.toArray(new Throwable[0])[0]);
+    this.causes = causes;
+  }
+
+  /**
+   * Required for GWT RPC serialization.
+   */
+  protected UmbrellaException() {
+    // Can't delegate to the other constructor or GWT RPC gets cranky
+    super(MSG);
+    this.causes = Collections.<Throwable> emptySet();
+  }
+
+  /**
+   * Get the set of exceptions that caused the failure.
+   * 
+   * @return the set of causes
+   */
+  public Set<Throwable> getCauses() {
+    return causes;
+  }
+}
diff --git a/user/src/com/google/web/bindery/event/shared/testing/CountingEventBus.java b/user/src/com/google/web/bindery/event/shared/testing/CountingEventBus.java
new file mode 100644
index 0000000..4b657a4
--- /dev/null
+++ b/user/src/com/google/web/bindery/event/shared/testing/CountingEventBus.java
@@ -0,0 +1,88 @@
+/*
+ * 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.event.shared.testing;
+
+import com.google.web.bindery.event.shared.Event;
+import com.google.web.bindery.event.shared.Event.Type;
+import com.google.web.bindery.event.shared.EventBus;
+import com.google.web.bindery.event.shared.HandlerRegistration;
+import com.google.web.bindery.event.shared.SimpleEventBus;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Wraps an {@link EventBus} to keep a count of registered handlers. Handy for
+ * tests.
+ */
+public class CountingEventBus extends EventBus {
+  private final Map<Type<?>, Integer> counts = new HashMap<Event.Type<?>, Integer>();
+  private final EventBus wrapped;
+
+  public CountingEventBus() {
+    this(new SimpleEventBus());
+  }
+
+  public CountingEventBus(EventBus wrapped) {
+    this.wrapped = wrapped;
+  }
+
+  @Override
+  public <H> HandlerRegistration addHandler(Type<H> type, H handler) {
+    increment(type);
+    final HandlerRegistration superReg = wrapped.addHandler(type, handler);
+    return makeReg(type, superReg);
+  }
+
+  @Override
+  public <H> HandlerRegistration addHandlerToSource(final Type<H> type, Object source, H handler) {
+    increment(type);
+    final HandlerRegistration superReg = wrapped.addHandlerToSource(type, source, handler);
+    return makeReg(type, superReg);
+  }
+
+  @Override
+  public void fireEvent(Event<?> event) {
+    wrapped.fireEvent(event);
+  }
+
+  @Override
+  public void fireEventFromSource(Event<?> event, Object source) {
+    wrapped.fireEventFromSource(event, source);
+  }
+
+  public int getCount(Type<?> type) {
+    Integer count = counts.get(type);
+    return count == null ? 0 : count;
+  }
+
+  private void decrement(Type<?> type) {
+    counts.put(type, getCount(type) - 1);
+  }
+
+  private <H> void increment(final Type<H> type) {
+    counts.put(type, getCount(type) + 1);
+  }
+
+  private <H> HandlerRegistration makeReg(final Type<H> type, final HandlerRegistration superReg) {
+    return new HandlerRegistration() {
+      public void removeHandler() {
+        decrement(type);
+        superReg.removeHandler();
+      }
+    };
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/RequestFactory.gwt.xml b/user/src/com/google/web/bindery/requestfactory/RequestFactory.gwt.xml
new file mode 100644
index 0000000..f55cc8d
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/RequestFactory.gwt.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 2.0.1//EN" "http://google-web-toolkit.googlecode.com/svn/tags/2.0.1/distro-source/core/src/gwt-module.dtd">
+<!--
+  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.
+-->
+<module>
+  <inherits name='com.google.gwt.core.Core'/>
+  <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'/>
+
+  <source path="gwt/client"/>
+  <source path="gwt/ui/client"/>
+  <source path="shared"/>
+  <super-source path="super" />
+  <generate-with
+    class="com.google.web.bindery.requestfactory.gwt.rebind.RequestFactoryEditorDriverGenerator">
+    <when-type-assignable
+      class="com.google.web.bindery.requestfactory.gwt.client.RequestFactoryEditorDriver" />
+  </generate-with>
+  <generate-with class="com.google.web.bindery.requestfactory.gwt.rebind.RequestFactoryGenerator">
+    <when-type-assignable class='com.google.web.bindery.requestfactory.shared.RequestFactory'/>
+  </generate-with>
+</module>
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/client/DefaultRequestTransport.java b/user/src/com/google/web/bindery/requestfactory/gwt/client/DefaultRequestTransport.java
new file mode 100644
index 0000000..12203a2
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/client/DefaultRequestTransport.java
@@ -0,0 +1,146 @@
+/*
+ * 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.requestfactory.gwt.client;
+
+import static com.google.gwt.user.client.rpc.RpcRequestBuilder.STRONG_NAME_HEADER;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.http.client.Request;
+import com.google.gwt.http.client.RequestBuilder;
+import com.google.gwt.http.client.RequestCallback;
+import com.google.gwt.http.client.RequestException;
+import com.google.gwt.http.client.Response;
+import com.google.gwt.user.client.Window.Location;
+import com.google.web.bindery.requestfactory.shared.RequestFactory;
+import com.google.web.bindery.requestfactory.shared.RequestTransport;
+import com.google.web.bindery.requestfactory.shared.ServerFailure;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * An implementation of {@link RequestTransport} that uses a
+ * {@link RequestBuilder}.
+ */
+public class DefaultRequestTransport implements RequestTransport {
+  private static final String SERVER_ERROR = "Server Error";
+
+  /**
+   * The default URL for a DefaultRequestTransport is
+   * <code>{@link GWT#getHostPageBaseURL()} + {@value #URL}</code> which may be
+   * overridden by calling {@link #setRequestUrl(String)}.
+   */
+  public static final String URL = "gwtRequest";
+
+  /*
+   * A separate logger for wire activity, which does not get logged by the
+   * remote log handler, so we avoid infinite loops. All log messages that could
+   * happen every time a request is made from the server should be logged to
+   * this logger.
+   */
+  private static final Logger wireLogger = Logger.getLogger("WireActivityLogger");
+
+  private String requestUrl = GWT.getHostPageBaseURL() + URL;
+
+  /**
+   * Returns the current URL used by this transport.
+   * 
+   * @return the URL as a String
+   * @see #setRequestUrl(String)
+   */
+  public String getRequestUrl() {
+    return requestUrl;
+  }
+
+  public void send(String payload, TransportReceiver receiver) {
+    RequestBuilder builder = createRequestBuilder();
+    configureRequestBuilder(builder);
+
+    builder.setRequestData(payload);
+    builder.setCallback(createRequestCallback(receiver));
+
+    try {
+      wireLogger.finest("Sending fire request");
+      builder.send();
+    } catch (RequestException e) {
+      wireLogger.log(Level.SEVERE, SERVER_ERROR + " (" + e.getMessage() + ")",
+          e);
+    }
+  }
+
+  /**
+   * Override the default URL used by this transport.
+   * 
+   * @param url a String URL
+   * @see #getRequestUrl()
+   */
+  public void setRequestUrl(String url) {
+    this.requestUrl = url;
+  }
+
+  /**
+   * Override to change the headers sent in the HTTP request.
+   * 
+   * @param builder a {@link RequestBuilder} instance
+   */
+  protected void configureRequestBuilder(RequestBuilder builder) {
+    builder.setHeader("Content-Type", RequestFactory.JSON_CONTENT_TYPE_UTF8);
+    builder.setHeader("pageurl", Location.getHref());
+    builder.setHeader(STRONG_NAME_HEADER, GWT.getPermutationStrongName());
+  }
+
+  /**
+   * Constructs a {@link RequestBuilder} using the {@link RequestBuilder#POST}
+   * method sent to the URL returned from {@link #getRequestUrl()}.
+   * 
+   * @return a {@link RequestBuilder} instance
+   */
+  protected RequestBuilder createRequestBuilder() {
+    return new RequestBuilder(RequestBuilder.POST, getRequestUrl());
+  }
+
+  /**
+   * Creates a RequestCallback that maps the HTTP response onto the
+   * {@link com.google.web.bindery.requestfactory.shared.RequestTransport.TransportReceiver
+   * TransportReceiver} interface.
+   * 
+   * @param receiver a {@link com.google.web.bindery.requestfactory.shared.RequestTransport.TransportReceiver TransportReceiver}
+   * @return a {@link RequestCallback} instance
+   */
+  protected RequestCallback createRequestCallback(
+      final TransportReceiver receiver) {
+    return new RequestCallback() {
+
+      public void onError(Request request, Throwable exception) {
+        wireLogger.log(Level.SEVERE, SERVER_ERROR, exception);
+        receiver.onTransportFailure(new ServerFailure(exception.getMessage()));
+      }
+
+      public void onResponseReceived(Request request, Response response) {
+        wireLogger.finest("Response received");
+        if (Response.SC_OK == response.getStatusCode()) {
+          String text = response.getText();
+          receiver.onTransportSuccess(text);
+        } else {
+          String message = SERVER_ERROR + " " + response.getStatusCode() + " "
+              + response.getText();
+          wireLogger.severe(message);
+          receiver.onTransportFailure(new ServerFailure(message));
+        }
+      }
+    };
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/client/HasRequestContext.java b/user/src/com/google/web/bindery/requestfactory/gwt/client/HasRequestContext.java
new file mode 100644
index 0000000..6ce2413
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/client/HasRequestContext.java
@@ -0,0 +1,40 @@
+/*
+ * 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.requestfactory.gwt.client;
+
+import com.google.gwt.editor.client.Editor;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+
+/**
+ * Editors used with {@link RequestFactoryEditorDriver} that implement this
+ * interface will be provided with the {@link RequestContext} associated with
+ * the current editing session.
+ * 
+ * @param <T> the type of data being edited
+ */
+public interface HasRequestContext<T> extends Editor<T> {
+  /**
+   * Called by {@link RequestFactoryEditorDriver} with the
+   * {@link RequestContext} passed into
+   * {@link RequestFactoryEditorDriver#edit(Object, RequestContext) edit()} or
+   * {@code null} if {@link RequestFactoryEditorDriver#display(Object)
+   * display()} is called.
+   * 
+   * @param ctx the RequestContext associated with the current editing session
+   *          which may be {@code null}
+   */
+  void setRequestContext(RequestContext ctx);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryEditorDriver.java b/user/src/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryEditorDriver.java
new file mode 100644
index 0000000..e5535e2
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryEditorDriver.java
@@ -0,0 +1,134 @@
+/*
+ * 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.requestfactory.gwt.client;
+
+import com.google.gwt.editor.client.Editor;
+import com.google.gwt.editor.client.EditorDriver;
+import com.google.web.bindery.event.shared.EventBus;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.RequestFactory;
+import com.google.web.bindery.requestfactory.shared.Violation;
+
+/**
+ * The interface that links RequestFactory and the Editor framework together.
+ * <p>
+ * Instances of this interface are created with
+ * 
+ * <pre>
+ * interface MyRFED extends RequestFactoryEditorDriver&lt;MyObjectProxy, MyObjectEditor> {}
+ * MyRFED instance = GWT.create(MyRFED.class);
+ * {
+ * instance.initialize(.....);
+ * myRequest.with(instance.getPaths());
+ * 
+ * // Fire the request, in the callback
+ * instance.edit(retrievedRecord);
+ * // Control when the request is sent
+ * instance.flush().fire(new Receiver {...});
+ * }
+ * </pre>
+ * 
+ * @param <P> the type of Proxy being edited
+ * @param <E> the type of Editor that will edit the Record
+ * @see HasRequestContext
+ * @see com.google.web.bindery.requestfactory.gwt.client.testing.MockRequestFactoryEditorDriver
+ *      MockRequestFactoryEditorDriver
+ */
+public interface RequestFactoryEditorDriver<P, E extends Editor<? super P>>
+    extends EditorDriver<RequestContext> {
+  /**
+   * Start driving the Editor and its sub-editors with data for display-only
+   * mode.
+   * 
+   * @param proxy a Proxy of type P
+   */
+  void display(P proxy);
+
+  /**
+   * Start driving the Editor and its sub-editors with data. A
+   * {@link RequestContext} is required to provide context for the changes to
+   * the proxy (see {@link RequestContext#edit}. Note that this driver will not
+   * fire the request.
+   * 
+   * @param proxy the proxy to be edited
+   * @param request the request context that will accumulate edits and is
+   *          returned form {@link #flush}
+   */
+  void edit(P proxy, RequestContext request);
+
+  /**
+   * Update the object being edited with the current state of the Editor.
+   * 
+   * @return the RequestContext passed into
+   *         {@link #edit(Object, RequestContext)}
+   * @throws IllegalStateException if {@link #edit(Object, RequestContext)} has
+   *           not been called
+   */
+  RequestContext flush();
+
+  /**
+   * Returns a new array containing the request paths.
+   * 
+   * @return an array of Strings
+   */
+  String[] getPaths();
+
+  /**
+   * Initializes a driver that will not be able to support subscriptions. Calls
+   * to {@link com.google.gwt.editor.client.EditorDelegate#subscribe()} will do
+   * nothing.
+   * 
+   * @param editor an {@link Editor} of type E
+   */
+  void initialize(E editor);
+
+  /**
+   * Overload of {@link #initialize(RequestFactory, Editor)} to allow a modified
+   * {@link EventBus} to be monitored for subscription services.
+   * 
+   * @param eventBus the {@link EventBus}
+   * @param requestFactory a {@link RequestFactory} instance
+   * @param editor an {@link Editor} of type E
+   * 
+   * @see com.google.gwt.editor.client.EditorDelegate#subscribe
+   * @see com.google.gwt.event.shared.ResettableEventBus
+   */
+  void initialize(EventBus eventBus, RequestFactory requestFactory, E editor);
+
+  /**
+   * Initializes a driver with the editor it will run, and a RequestFactory to
+   * use for subscription services.
+   * 
+   * @param requestFactory a {@link RequestFactory} instance
+   * @param editor an {@link Editor} of type E
+   * 
+   * @see com.google.gwt.editor.client.EditorDelegate#subscribe
+   */
+  void initialize(RequestFactory requestFactory, E editor);
+
+  /**
+   * Show Violations returned from an attempt to submit a request. The
+   * violations will be converted into
+   * {@link com.google.gwt.editor.client.EditorError EditorError} objects whose
+   * {@link com.google.gwt.editor.client.EditorError#getUserData()
+   * getUserData()} method can be used to access the original Violation object.
+   * 
+   * @param violations an Iterable over {@link Violation} instances
+   * @return <code>true</code> if there were any unconsumed EditorErrors which
+   *         can be retrieved from {@link #getErrors()}
+   */
+  boolean setViolations(Iterable<Violation> violations);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryLogHandler.java b/user/src/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryLogHandler.java
new file mode 100644
index 0000000..47d67a8
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryLogHandler.java
@@ -0,0 +1,81 @@
+/*
+ * 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.requestfactory.gwt.client;
+
+import com.google.gwt.logging.client.JsonLogRecordClientUtil;
+import com.google.gwt.logging.client.RemoteLogHandlerBase;
+import com.google.web.bindery.requestfactory.shared.LoggingRequest;
+import com.google.web.bindery.requestfactory.shared.Receiver;
+
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+
+/**
+ * A Handler that does remote logging for applications using RequestFactory.
+ */
+public class RequestFactoryLogHandler extends RemoteLogHandlerBase {
+
+  /**
+   * Provides a logging request.
+   */
+  public static interface LoggingRequestProvider {
+    /**
+     * Returns the logging request.
+     *
+     * @return a {@link LoggingRequest} instance
+     */
+    LoggingRequest getLoggingRequest();
+  }
+
+  private LoggingRequestProvider requestProvider;
+
+  /**
+   * Since records from this handler go accross the wire, it should only be
+   * used for important messages, and it's Level will often be higher than the
+   * Level being used app-wide. This handler also takes string which it will
+   * use to exclude the messages from some loggers. This usually includes the
+   * name of the logger(s) which will be used to log acknowledgements of
+   * activity going accross the wire. If we did not exclude these loggers, an
+   * infinite loop would occur.
+   *
+   * @param requestProvider a {@link LoggingRequestProvider} instance
+   * @param level a logging {@link Level}
+   * @param ignoredLoggerNames a List of Strings
+   */
+  public RequestFactoryLogHandler(LoggingRequestProvider requestProvider,
+      Level level, List<String> ignoredLoggerNames) {
+    super(ignoredLoggerNames);
+    this.requestProvider = requestProvider;
+    setLevel(level);
+  }
+
+  @Override
+  public void publish(LogRecord record) {
+    if (!isLoggable(record)) {
+      return;
+    }
+    String json = JsonLogRecordClientUtil.logRecordAsJson(record);
+    requestProvider.getLoggingRequest().logMessage(json).fire(
+        new Receiver<Void>() {
+          @Override
+          public void onSuccess(Void response) {
+            // Do nothing
+          }
+        });
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/client/impl/AbstractClientRequestFactory.java b/user/src/com/google/web/bindery/requestfactory/gwt/client/impl/AbstractClientRequestFactory.java
new file mode 100644
index 0000000..b122674
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/client/impl/AbstractClientRequestFactory.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.
+ */
+package com.google.web.bindery.requestfactory.gwt.client.impl;
+
+import com.google.web.bindery.event.shared.EventBus;
+import com.google.web.bindery.requestfactory.gwt.client.DefaultRequestTransport;
+import com.google.web.bindery.requestfactory.shared.impl.AbstractRequestFactory;
+
+/**
+ * A RequestFactory that uses a {@link DefaultRequestTransport} by default.
+ */
+public abstract class AbstractClientRequestFactory extends
+    AbstractRequestFactory {
+  @Override
+  public void initialize(EventBus eventBus) {
+    initialize(eventBus, new DefaultRequestTransport());
+  }
+}
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
new file mode 100644
index 0000000..94f71ba
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/client/impl/AbstractRequestFactoryEditorDriver.java
@@ -0,0 +1,235 @@
+/*
+ * 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.requestfactory.gwt.client.impl;
+
+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;
+import com.google.gwt.editor.client.impl.AbstractEditorDelegate;
+import com.google.gwt.editor.client.impl.BaseEditorDriver;
+import com.google.gwt.editor.client.impl.DelegateMap;
+import com.google.gwt.editor.client.impl.DelegateMap.KeyMethod;
+import com.google.gwt.editor.client.impl.SimpleViolation;
+import com.google.web.bindery.event.shared.EventBus;
+import com.google.web.bindery.requestfactory.gwt.client.HasRequestContext;
+import com.google.web.bindery.requestfactory.gwt.client.RequestFactoryEditorDriver;
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.RequestFactory;
+import com.google.web.bindery.requestfactory.shared.ValueProxy;
+import com.google.web.bindery.requestfactory.shared.Violation;
+import com.google.web.bindery.requestfactory.shared.impl.Constants;
+
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Contains utility methods for top-level driver implementations.
+ * 
+ * @param <R> the type being edited
+ * @param <E> the type of Editor
+ */
+public abstract class AbstractRequestFactoryEditorDriver<R, E extends Editor<R>>
+    extends BaseEditorDriver<R, E> implements RequestFactoryEditorDriver<R, E> {
+
+  /**
+   * Adapts a RequestFactory Violation object to the SimpleViolation interface.
+   */
+  static class SimpleViolationAdapter extends SimpleViolation {
+    private final Violation v;
+
+    /**
+     * @param v
+     */
+    private SimpleViolationAdapter(Violation v) {
+      this.v = v;
+    }
+
+    @Override
+    public Object getKey() {
+      return v.getOriginalProxy();
+    }
+
+    @Override
+    public String getMessage() {
+      return v.getMessage();
+    }
+
+    @Override
+    public String getPath() {
+      return v.getPath();
+    }
+
+    @Override
+    public Object getUserDataObject() {
+      return v;
+    }
+  }
+  /**
+   * Provides a source of SimpleViolation objects based on RequestFactory's
+   * simplified Violation interface.
+   */
+  static class ViolationIterable implements Iterable<SimpleViolation> {
+
+    private final Iterable<Violation> violations;
+
+    public ViolationIterable(Iterable<Violation> violations) {
+      this.violations = violations;
+    }
+
+    public Iterator<SimpleViolation> iterator() {
+      final Iterator<Violation> source = violations.iterator();
+      return new Iterator<SimpleViolation>() {
+        public boolean hasNext() {
+          return source.hasNext();
+        }
+
+        public SimpleViolation next() {
+          return new SimpleViolationAdapter(source.next());
+        }
+
+        public void remove() {
+          source.remove();
+        }
+      };
+    }
+  }
+
+  /**
+   * Since the ValueProxy is being mutated in-place, we need a way to stabilize
+   * its hashcode for future equality checks.
+   */
+  private static class ValueProxyHolder {
+    private final ValueProxy proxy;
+
+    public ValueProxyHolder(ValueProxy proxy) {
+      this.proxy = proxy;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      return proxy.equals(((ValueProxyHolder) o).proxy);
+    }
+
+    @Override
+    public int hashCode() {
+      return proxy.getClass().hashCode();
+    }
+  }
+
+  private static final DelegateMap.KeyMethod PROXY_ID_KEY = new DelegateMap.KeyMethod() {
+    public Object key(Object object) {
+      if (object instanceof EntityProxy) {
+        return ((EntityProxy) object).stableId();
+      } else if (object instanceof ValueProxy) {
+        AutoBean<?> bean = AutoBeanUtils.getAutoBean(object);
+        // Possibly replace an editable ValueProxy with its immutable base
+        AutoBean<?> parent = bean.getTag(Constants.PARENT_OBJECT);
+        if (parent != null) {
+          object = parent.as();
+        }
+        return new ValueProxyHolder((ValueProxy) object);
+      }
+      return null;
+    }
+  };
+
+  private EventBus eventBus;
+  private List<String> paths;
+  private RequestFactory factory;
+  private RequestContext saveRequest;
+
+  public void display(R object) {
+    edit(object, null);
+  }
+
+  public void edit(R object, final RequestContext saveRequest) {
+    this.saveRequest = saveRequest;
+    // Provide the delegate and maybe the editor with the RequestContext
+    accept(new EditorVisitor() {
+      @Override
+      public <T> void endVisit(EditorContext<T> ctx) {
+        RequestFactoryEditorDelegate<?, ?> delegate = (RequestFactoryEditorDelegate<?, ?>) ctx.getEditorDelegate();
+        if (delegate != null) {
+          delegate.setRequestContext(saveRequest);
+        }
+        Editor<T> editor = ctx.getEditor();
+        if (editor instanceof HasRequestContext<?>) {
+          ((HasRequestContext<T>) editor).setRequestContext(saveRequest);
+        }
+      }
+    });
+    doEdit(object);
+  }
+
+  public RequestContext flush() {
+    checkSaveRequest();
+    doFlush();
+    return saveRequest;
+  }
+
+  public String[] getPaths() {
+    return paths.toArray(new String[paths.size()]);
+  }
+
+  public void initialize(E editor) {
+    doInitialize(null, null, editor);
+  }
+
+  public void initialize(EventBus eventBus, RequestFactory requestFactory,
+      E editor) {
+    assert eventBus != null : "eventBus must not be null";
+    assert requestFactory != null : "requestFactory must not be null";
+    doInitialize(eventBus, requestFactory, editor);
+  }
+
+  public void initialize(RequestFactory requestFactory, E editor) {
+    initialize(requestFactory.getEventBus(), requestFactory, editor);
+  }
+
+  public boolean setViolations(Iterable<Violation> violations) {
+    return doSetViolations(new ViolationIterable(violations));
+  }
+
+  protected void checkSaveRequest() {
+    if (saveRequest == null) {
+      throw new IllegalStateException("edit() was called with a null Request");
+    }
+  }
+
+  @Override
+  protected void configureDelegate(AbstractEditorDelegate<R, E> rootDelegate) {
+    ((RequestFactoryEditorDelegate<R, E>) rootDelegate).initialize(eventBus,
+        factory, "", getEditor());
+  }
+
+  protected void doInitialize(EventBus eventBus, RequestFactory requestFactory,
+      E editor) {
+    this.eventBus = eventBus;
+    this.factory = requestFactory;
+    super.doInitialize(editor);
+    PathCollector c = new PathCollector();
+    accept(c);
+    this.paths = c.getPaths();
+  }
+
+  @Override
+  protected KeyMethod getViolationKeyMethod() {
+    return PROXY_ID_KEY;
+  }
+}
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
new file mode 100644
index 0000000..ab4f8aa
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/client/impl/PathCollector.java
@@ -0,0 +1,65 @@
+/*
+ * 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.requestfactory.gwt.client.impl;
+
+import com.google.web.bindery.autobean.shared.ValueCodex;
+import com.google.gwt.editor.client.EditorContext;
+import com.google.gwt.editor.client.EditorVisitor;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Collects all non-value-type paths in an editor hierarchy for use with
+ * {@link com.google.web.bindery.requestfactory.gwt.client.RequestFactoryEditorDriver#getPaths()}.
+ */
+class PathCollector extends EditorVisitor {
+  /**
+   * Use a set in the case of aliased editors, so we don't repeat path entries.
+   */
+  private final Set<String> paths = new LinkedHashSet<String>();
+
+  public List<String> getPaths() {
+    return new ArrayList<String>(paths);
+  }
+
+  @Override
+  public <T> boolean visit(EditorContext<T> ctx) {
+    String path = ctx.getAbsolutePath();
+    if (path.length() > 0) {
+      if (ValueCodex.canDecode(ctx.getEditedType())) {
+        /*
+         * If there's an @Path("foo.bar.valueField") annotation, we want to
+         * collect the containing "foo.bar" path.
+         */
+        int dotPosition = path.lastIndexOf('.');
+        if (dotPosition > 0) {
+          String parentPath = path.substring(0, dotPosition);
+          paths.add(parentPath);
+        }
+      } else {
+        // Always collect @Path("foo.bar.baz") field, when baz isn't a value
+        paths.add(path);
+      }
+    }
+    if (ctx.asCompositeEditor() != null) {
+      ctx.traverseSyntheticCompositeEditor(this);
+    }
+    return true;
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/client/impl/RequestFactoryEditorDelegate.java b/user/src/com/google/web/bindery/requestfactory/gwt/client/impl/RequestFactoryEditorDelegate.java
new file mode 100644
index 0000000..0cf247a
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/client/impl/RequestFactoryEditorDelegate.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.requestfactory.gwt.client.impl;
+
+import com.google.gwt.editor.client.Editor;
+import com.google.gwt.editor.client.impl.AbstractEditorDelegate;
+import com.google.gwt.editor.client.impl.Refresher;
+// This import is not an accident, details in subscribe() implementation
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.web.bindery.event.shared.EventBus;
+import com.google.web.bindery.requestfactory.shared.BaseProxy;
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.EntityProxyChange;
+import com.google.web.bindery.requestfactory.shared.EntityProxyId;
+import com.google.web.bindery.requestfactory.shared.Receiver;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.RequestFactory;
+import com.google.web.bindery.requestfactory.shared.WriteOperation;
+import com.google.web.bindery.requestfactory.shared.impl.AbstractRequestContext;
+
+import java.util.List;
+
+/**
+ * Base class for generated EditorDelegates using a RequestFactory as the
+ * backend.
+ * 
+ * @param <P> the type of Proxy
+ * @param <E> the type of Editor
+ */
+public abstract class RequestFactoryEditorDelegate<P, E extends Editor<P>>
+    extends AbstractEditorDelegate<P, E> {
+  private class SubscriptionHandler implements
+      EntityProxyChange.Handler<EntityProxy> {
+
+    public void onProxyChange(EntityProxyChange<EntityProxy> event) {
+      if (event.getWriteOperation().equals(WriteOperation.UPDATE)
+          && event.getProxyId().equals(((EntityProxy) getObject()).stableId())) {
+        PathCollector collector = new PathCollector();
+        accept(collector);
+        EntityProxyId<?> id = event.getProxyId();
+        doFind(collector.getPaths(), id);
+      }
+    }
+
+    @SuppressWarnings(value = {"rawtypes", "unchecked"})
+    private void doFind(List<String> paths, EntityProxyId id) {
+      factory.find(id).with(paths.toArray(new String[paths.size()])).fire(
+          new SubscriptionReceiver());
+    }
+  }
+
+  private class SubscriptionReceiver extends Receiver<EntityProxy> {
+    @Override
+    public void onSuccess(EntityProxy response) {
+      @SuppressWarnings("unchecked")
+      P cast = (P) response;
+      setObject(cast);
+      accept(new Refresher());
+    }
+  }
+
+  protected EventBus eventBus;
+  protected RequestFactory factory;
+  protected RequestContext request;
+
+  public void setRequestContext(RequestContext request) {
+    this.request = request;
+  }
+
+  @Override
+  public HandlerRegistration subscribe() {
+    if (factory == null) {
+      /*
+       * They called the no-subscriptions version of
+       * RequestFactoryEditorDriver#initialize
+       */
+      return null;
+    }
+
+    if (!(getObject() instanceof EntityProxy)) {
+      /*
+       * This is kind of weird. The user is asking for notifications on a
+       * String, which means there's a HasEditorDelegate<String> in play and not
+       * the usual LeafValueEditor<String>.
+       */
+      return null;
+    }
+
+    // Can't just use getObject().getClass() because it's not the proxy type
+    EntityProxyId<?> stableId = ((EntityProxy) getObject()).stableId();
+    @SuppressWarnings("unchecked")
+    Class<EntityProxy> clazz = (Class<EntityProxy>) stableId.getProxyClass();
+
+    /*
+     * Convert to the old gwt HandlerRegistration type required by the
+     * EditorDelegate interface. This can get cleaned up when Editor moves to
+     * com.google.web.bindery.
+     */
+    final com.google.web.bindery.event.shared.HandlerRegistration toReturn =
+        EntityProxyChange.<EntityProxy> registerForProxyType(eventBus, clazz,
+            new SubscriptionHandler());
+    return new HandlerRegistration() {
+      public void removeHandler() {
+        toReturn.removeHandler();
+      }
+    };
+  }
+
+  @Override
+  protected <R, S extends Editor<R>> void addSubDelegate(
+      AbstractEditorDelegate<R, S> subDelegate, String path, S subEditor) {
+    RequestFactoryEditorDelegate<R, S> d = (RequestFactoryEditorDelegate<R, S>) subDelegate;
+    d.initialize(eventBus, factory, path, subEditor);
+  }
+
+  @Override
+  protected <T> T ensureMutable(T object) {
+    if (request == null) {
+      // Read-only mode
+      return object;
+    }
+    if (object instanceof BaseProxy) {
+      @SuppressWarnings("unchecked")
+      T toReturn = (T) request.edit((BaseProxy) object);
+      return toReturn;
+    }
+    return object;
+  }
+
+  protected void initialize(EventBus eventBus, RequestFactory factory,
+      String pathSoFar, E editor) {
+    this.eventBus = eventBus;
+    this.factory = factory;
+    super.initialize(pathSoFar, editor);
+  }
+
+  /**
+   * Must call four-arg version instead.
+   */
+  @Override
+  protected void initialize(String pathSoFar, E editor) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  protected boolean shouldFlush() {
+    if (request == null) {
+      return false;
+    }
+    if (request instanceof AbstractRequestContext) {
+      return !((AbstractRequestContext) request).isLocked();
+    }
+    return true;
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/client/package-info.java b/user/src/com/google/web/bindery/requestfactory/gwt/client/package-info.java
new file mode 100644
index 0000000..6413a73
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/client/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+/**
+ * A package for managing client-server requests.
+ *
+ * @since GWT 2.1
+ */
+@com.google.gwt.util.PreventSpuriousRebuilds
+package com.google.web.bindery.requestfactory.gwt.client;
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/client/testing/MockRequestFactoryEditorDriver.java b/user/src/com/google/web/bindery/requestfactory/gwt/client/testing/MockRequestFactoryEditorDriver.java
new file mode 100644
index 0000000..5cc2223
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/client/testing/MockRequestFactoryEditorDriver.java
@@ -0,0 +1,172 @@
+/*
+ * 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.requestfactory.gwt.client.testing;
+
+import com.google.gwt.editor.client.Editor;
+import com.google.gwt.editor.client.EditorError;
+import com.google.gwt.editor.client.EditorVisitor;
+import com.google.web.bindery.event.shared.EventBus;
+import com.google.web.bindery.requestfactory.gwt.client.RequestFactoryEditorDriver;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.RequestFactory;
+import com.google.web.bindery.requestfactory.shared.Violation;
+
+import java.util.Collections;
+import java.util.List;
+
+import javax.validation.ConstraintViolation;
+
+/**
+ * A no-op implementation of {@link RequestFactoryEditorDriver} that records its
+ * inputs.
+ * 
+ * @param <P> the Proxy type being edited
+ * @param <E> the Editor type
+ */
+public class MockRequestFactoryEditorDriver<P, E extends Editor<P>> implements
+    RequestFactoryEditorDriver<P, E> {
+  private static final String[] EMPTY_STRING = new String[0];
+
+  private EventBus eventBus;
+  private E editor;
+  private P proxy;
+  private RequestContext saveRequest;
+  private RequestFactory requestFactory;
+
+  /**
+   * A no-op method.
+   */
+  public void accept(EditorVisitor visitor) {
+  }
+
+  /**
+   * Records its arguments.
+   */
+  public void display(P proxy) {
+    this.proxy = proxy;
+  }
+
+  /**
+   * Records its arguments.
+   */
+  public void edit(P proxy, RequestContext saveRequest) {
+    this.proxy = proxy;
+    this.saveRequest = saveRequest;
+  }
+
+  /**
+   * Returns <code>null</code> or the last value recorded.
+   */
+  public RequestContext flush() {
+    return saveRequest;
+  }
+
+  /**
+   * Returns <code>null</code> or the last value recorded.
+   */
+  public E getEditor() {
+    return editor;
+  }
+
+  /**
+   * Returns an empty list.
+   */
+  public List<EditorError> getErrors() {
+    return Collections.emptyList();
+  }
+
+  /**
+   * Returns <code>null</code> or the last value recorded.
+   */
+  public EventBus getEventBus() {
+    return eventBus;
+  }
+
+  /**
+   * Returns a zero-length array.
+   */
+  public String[] getPaths() {
+    return EMPTY_STRING;
+  }
+
+  /**
+   * Returns <code>null</code> or the last value recorded.
+   */
+  public P getProxy() {
+    return proxy;
+  }
+
+  /**
+   * Returns <code>null</code> or the last value recorded.
+   */
+  public RequestFactory getRequestFactory() {
+    return requestFactory;
+  }
+
+  /**
+   * Returns <code>null</code> or the last value recorded.
+   */
+  public RequestContext getSaveRequest() {
+    return saveRequest;
+  }
+
+  /**
+   * Returns <code>false</code>.
+   */
+  public boolean hasErrors() {
+    return false;
+  }
+
+  public void initialize(E editor) {
+    initialize(null, editor);
+  }
+
+  /**
+   * Records its arguments.
+   */
+  public void initialize(EventBus eventBus, RequestFactory requestFactory,
+      E editor) {
+    this.eventBus = eventBus;
+    this.requestFactory = requestFactory;
+    this.editor = editor;
+  }
+
+  public void initialize(RequestFactory requestFactory, E editor) {
+    this.initialize(requestFactory.getEventBus(), requestFactory, editor);
+  }
+
+  /**
+   * Returns {@code false}.
+   */
+  public boolean isDirty() {
+    return false;
+  }
+
+  /**
+   * A no-op method that always returns false.
+   */
+  public boolean setConstraintViolations(
+      Iterable<ConstraintViolation<?>> violations) {
+    return false;
+  }
+
+  /**
+   * A no-op method that always returns false.
+   */
+  public boolean setViolations(Iterable<Violation> errors) {
+    return false;
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/client/testing/package-info.java b/user/src/com/google/web/bindery/requestfactory/gwt/client/testing/package-info.java
new file mode 100644
index 0000000..75070ee
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/client/testing/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+/**
+ * Classes used for testing the request factory service.
+ *
+ * @since GWT 2.1
+ */
+@com.google.gwt.util.PreventSpuriousRebuilds
+package com.google.web.bindery.requestfactory.gwt.client.testing;
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/rebind/RequestFactoryEditorDriverGenerator.java b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/RequestFactoryEditorDriverGenerator.java
new file mode 100644
index 0000000..347c576
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/RequestFactoryEditorDriverGenerator.java
@@ -0,0 +1,71 @@
+/*
+ * 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.requestfactory.gwt.rebind;
+
+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.editor.rebind.AbstractEditorDriverGenerator;
+import com.google.gwt.editor.rebind.model.EditorData;
+import com.google.web.bindery.requestfactory.gwt.client.RequestFactoryEditorDriver;
+import com.google.web.bindery.requestfactory.gwt.client.impl.AbstractRequestFactoryEditorDriver;
+import com.google.web.bindery.requestfactory.gwt.client.impl.RequestFactoryEditorDelegate;
+import com.google.web.bindery.requestfactory.shared.BaseProxy;
+
+/**
+ * Generates implementations of RFEDs.
+ */
+public class RequestFactoryEditorDriverGenerator extends
+    AbstractEditorDriverGenerator {
+
+  private JClassType baseProxyType;
+
+  @Override
+  public String generate(TreeLogger logger, GeneratorContext context,
+      String typeName) throws UnableToCompleteException {
+    baseProxyType = context.getTypeOracle().findType(
+        BaseProxy.class.getCanonicalName());
+    return super.generate(logger, context, typeName);
+  }
+
+  @Override
+  protected Class<?> getDriverInterfaceType() {
+    return RequestFactoryEditorDriver.class;
+  }
+
+  @Override
+  protected Class<?> getDriverSuperclassType() {
+    return AbstractRequestFactoryEditorDriver.class;
+  }
+
+  @Override
+  protected Class<?> getEditorDelegateType() {
+    return RequestFactoryEditorDelegate.class;
+  }
+
+  @Override
+  protected String mutableObjectExpression(EditorData data,
+      String sourceObjectExpression) {
+    if (baseProxyType.isAssignableFrom(data.getPropertyOwnerType())) {
+      return String.format("((%s) request.edit((%s)))",
+          data.getPropertyOwnerType().getQualifiedSourceName(),
+          sourceObjectExpression);
+    } else {
+      return sourceObjectExpression;
+    }
+  }
+}
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
new file mode 100644
index 0000000..e6d7f5c
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/RequestFactoryGenerator.java
@@ -0,0 +1,505 @@
+/*
+ * 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.requestfactory.gwt.rebind;
+
+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;
+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.JEnumType;
+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.JTypeParameter;
+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 com.google.web.bindery.requestfactory.gwt.client.impl.AbstractClientRequestFactory;
+import com.google.web.bindery.requestfactory.gwt.rebind.model.AcceptsModelVisitor;
+import com.google.web.bindery.requestfactory.gwt.rebind.model.ContextMethod;
+import com.google.web.bindery.requestfactory.gwt.rebind.model.EntityProxyModel;
+import com.google.web.bindery.requestfactory.gwt.rebind.model.ModelVisitor;
+import com.google.web.bindery.requestfactory.gwt.rebind.model.RequestFactoryModel;
+import com.google.web.bindery.requestfactory.gwt.rebind.model.RequestMethod;
+import com.google.web.bindery.requestfactory.gwt.rebind.model.EntityProxyModel.Type;
+import com.google.web.bindery.requestfactory.shared.EntityProxyId;
+import com.google.web.bindery.requestfactory.shared.JsonRpcContent;
+import com.google.web.bindery.requestfactory.shared.impl.AbstractRequest;
+import com.google.web.bindery.requestfactory.shared.impl.AbstractRequestContext;
+import com.google.web.bindery.requestfactory.shared.impl.AbstractRequestFactory;
+import com.google.web.bindery.requestfactory.shared.impl.BaseProxyCategory;
+import com.google.web.bindery.requestfactory.shared.impl.EntityProxyCategory;
+import com.google.web.bindery.requestfactory.shared.impl.RequestData;
+import com.google.web.bindery.requestfactory.shared.impl.ValueProxyCategory;
+import com.google.web.bindery.requestfactory.shared.impl.AbstractRequestContext.Dialect;
+
+import java.io.PrintWriter;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * Generates implementations of
+ * {@link com.google.web.bindery.requestfactory.shared.RequestFactory RequestFactory}
+ * and its nested interfaces.
+ */
+public class RequestFactoryGenerator extends Generator {
+
+  /**
+   * Visits all types reachable from a RequestContext.
+   */
+  private static class AllReachableTypesVisitor extends
+      RequestMethodTypesVisitor {
+    private final RequestFactoryModel model;
+
+    public AllReachableTypesVisitor(RequestFactoryModel model) {
+      this.model = model;
+    }
+
+    @Override
+    void examineTypeOnce(JClassType type) {
+      // Need this to handle List<Foo>, Map<Foo>
+      JParameterizedType parameterized = type.isParameterized();
+      if (parameterized != null) {
+        for (JClassType arg : parameterized.getTypeArgs()) {
+          maybeVisit(arg);
+        }
+      }
+      JClassType base = ModelUtils.ensureBaseType(type);
+      EntityProxyModel peer = model.getPeer(base);
+      if (peer == null) {
+        return;
+      }
+      peer.accept(this);
+    }
+  }
+
+  /**
+   * Visits all types immediately referenced by methods defined in a
+   * RequestContext.
+   */
+  private abstract static class RequestMethodTypesVisitor extends ModelVisitor {
+    private final Set<JClassType> seen = new HashSet<JClassType>();
+
+    @Override
+    public void endVisit(RequestMethod x) {
+      // Request<Foo> -> Foo
+      maybeVisit(x.getDataType());
+      // InstanceRequest<Proxy, Foo> -> Proxy
+      if (x.getInstanceType() != null) {
+        x.getInstanceType().accept(this);
+      }
+      // Request<Void> doSomething(Foo foo, Bar bar) -> Foo, Bar
+      for (JType param : x.getDeclarationMethod().getParameterTypes()) {
+        maybeVisit(param.isClassOrInterface());
+      }
+      // setFoo(Foo foo) -> Foo
+      for (JMethod method : x.getExtraSetters()) {
+        maybeVisit(method.getParameterTypes()[0].isClassOrInterface());
+      }
+    }
+
+    abstract void examineTypeOnce(JClassType type);
+
+    void maybeVisit(JClassType type) {
+      if (type == null) {
+        return;
+      } else if (!seen.add(type)) {
+        // Short-circuit to prevent type-loops
+        return;
+      }
+      examineTypeOnce(type);
+    }
+  }
+
+  private GeneratorContext context;
+  private TreeLogger logger;
+  private RequestFactoryModel 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();
+    String simpleSourceName = toGenerate.getName().replace('.', '_') + "Impl";
+    PrintWriter pw = context.tryCreate(logger, packageName, simpleSourceName);
+    if (pw == null) {
+      return packageName + "." + simpleSourceName;
+    }
+
+    model = new RequestFactoryModel(logger, toGenerate);
+
+    ClassSourceFileComposerFactory factory = new ClassSourceFileComposerFactory(
+        packageName, simpleSourceName);
+    factory.setSuperclass(AbstractClientRequestFactory.class.getCanonicalName());
+    factory.addImplementedInterface(typeName);
+    SourceWriter sw = factory.createSourceWriter(context, pw);
+    writeAutoBeanFactory(sw, model.getAllProxyModels(), findExtraEnums(model));
+    writeContextMethods(sw);
+    writeContextImplementations();
+    writeTypeMap(sw);
+    sw.commit(logger);
+
+    return factory.getCreatedClassName();
+  }
+
+  /**
+   * Find enums that needed to be added to the EnumMap that are not referenced
+   * by any of the proxies. This is necessary because the RequestFactory depends
+   * on the AutoBeanCodex to serialize enum values, which in turn depends on the
+   * AutoBeanFactory's enum map. That enum map only contains enum types
+   * reachable from the AutoBean interfaces, which could lead to method
+   * parameters being un-encodable.
+   */
+  private Set<JEnumType> findExtraEnums(AcceptsModelVisitor method) {
+    final Set<JEnumType> toReturn = new LinkedHashSet<JEnumType>();
+    final Set<JEnumType> referenced = new HashSet<JEnumType>();
+
+    // Called from the adder visitor below on each EntityProxy seen
+    final ModelVisitor remover = new AllReachableTypesVisitor(model) {
+      @Override
+      void examineTypeOnce(JClassType type) {
+        JEnumType asEnum = type.isEnum();
+        if (asEnum != null) {
+          referenced.add(asEnum);
+        }
+        super.examineTypeOnce(type);
+      }
+    };
+
+    // Add enums used by RequestMethods
+    method.accept(new RequestMethodTypesVisitor() {
+      @Override
+      public boolean visit(EntityProxyModel x) {
+        x.accept(remover);
+        return false;
+      }
+
+      @Override
+      void examineTypeOnce(JClassType type) {
+        JEnumType asEnum = type.isEnum();
+        if (asEnum != null) {
+          toReturn.add(asEnum);
+        }
+      }
+    });
+    toReturn.removeAll(referenced);
+    if (toReturn.isEmpty()) {
+      return Collections.emptySet();
+    }
+    return Collections.unmodifiableSet(toReturn);
+  }
+
+  /**
+   * Find all EntityProxyModels reachable from a given ContextMethod.
+   */
+  private Set<EntityProxyModel> findReferencedEntities(ContextMethod method) {
+    final Set<EntityProxyModel> models = new LinkedHashSet<EntityProxyModel>();
+    method.accept(new AllReachableTypesVisitor(model) {
+      @Override
+      public void endVisit(EntityProxyModel x) {
+        models.add(x);
+      }
+    });
+    return models;
+  }
+
+  private void writeAutoBeanFactory(SourceWriter sw,
+      Collection<EntityProxyModel> models, Collection<JEnumType> extraEnums) {
+    if (!extraEnums.isEmpty()) {
+      StringBuilder extraClasses = new StringBuilder();
+      for (JEnumType enumType : extraEnums) {
+        if (extraClasses.length() > 0) {
+          extraClasses.append(",");
+        }
+        extraClasses.append(enumType.getQualifiedSourceName()).append(".class");
+      }
+      sw.println("@%s({%s})", ExtraEnums.class.getCanonicalName(), extraClasses);
+    }
+    // Map in static implementations of EntityProxy methods
+    sw.println("@%s({%s.class, %s.class, %s.class})",
+        Category.class.getCanonicalName(),
+        EntityProxyCategory.class.getCanonicalName(),
+        ValueProxyCategory.class.getCanonicalName(),
+        BaseProxyCategory.class.getCanonicalName());
+    // Don't wrap our id type, because it makes code grungy
+    sw.println("@%s(%s.class)", NoWrap.class.getCanonicalName(),
+        EntityProxyId.class.getCanonicalName());
+    sw.println("interface Factory extends %s {",
+        AutoBeanFactory.class.getCanonicalName());
+    sw.indent();
+
+    for (EntityProxyModel proxy : models) {
+      // AutoBean<FooProxy> com_google_FooProxy();
+      sw.println("%s<%s> %s();", AutoBean.class.getCanonicalName(),
+          proxy.getQualifiedSourceName(),
+          proxy.getQualifiedSourceName().replace('.', '_'));
+    }
+    sw.outdent();
+    sw.println("}");
+
+    // public static final Factory FACTORY = GWT.create(Factory.class);
+    sw.println("public static Factory FACTORY;", GWT.class.getCanonicalName());
+
+    // Write public accessor
+    sw.println("@Override public Factory getAutoBeanFactory() {");
+    sw.indent();
+    sw.println("if (FACTORY == null) {");
+    sw.indentln("FACTORY = %s.create(Factory.class);",
+        GWT.class.getCanonicalName());
+    sw.println("}");
+    sw.println("return FACTORY;");
+    sw.outdent();
+    sw.println("}");
+  }
+
+  private void writeContextImplementations() {
+    for (ContextMethod method : model.getMethods()) {
+      PrintWriter pw = context.tryCreate(logger, method.getPackageName(),
+          method.getSimpleSourceName());
+      if (pw == null) {
+        // Already generated
+        continue;
+      }
+
+      ClassSourceFileComposerFactory factory = new ClassSourceFileComposerFactory(
+          method.getPackageName(), method.getSimpleSourceName());
+      factory.setSuperclass(AbstractRequestContext.class.getCanonicalName());
+      factory.addImplementedInterface(method.getImplementedInterfaceQualifiedSourceName());
+      SourceWriter sw = factory.createSourceWriter(context, pw);
+
+      // Constructor that accepts the parent RequestFactory
+      sw.println(
+          "public %s(%s requestFactory) {super(requestFactory, %s.%s);}",
+          method.getSimpleSourceName(),
+          AbstractRequestFactory.class.getCanonicalName(),
+          Dialect.class.getCanonicalName(), method.getDialect().name());
+
+      Set<EntityProxyModel> models = findReferencedEntities(method);
+      Set<JEnumType> extraEnumTypes = findExtraEnums(method);
+      writeAutoBeanFactory(sw, models, extraEnumTypes);
+
+      // Write each Request method
+      for (RequestMethod request : method.getRequestMethods()) {
+        JMethod jmethod = request.getDeclarationMethod();
+        String operation = request.getOperation();
+
+        // foo, bar, baz
+        StringBuilder parameterArray = new StringBuilder();
+        // final Foo foo, final Bar bar, final Baz baz
+        StringBuilder parameterDeclaration = new StringBuilder();
+        // <P extends Blah>
+        StringBuilder typeParameterDeclaration = new StringBuilder();
+
+        if (request.isInstance()) {
+          // Leave a spot for the using() method to fill in later
+          parameterArray.append(",null");
+        }
+        for (JTypeParameter param : jmethod.getTypeParameters()) {
+          typeParameterDeclaration.append(",").append(
+              param.getQualifiedSourceName());
+        }
+        for (JParameter param : jmethod.getParameters()) {
+          parameterArray.append(",").append(param.getName());
+          parameterDeclaration.append(",final ").append(
+              param.getType().getParameterizedQualifiedSourceName()).append(" ").append(
+              param.getName());
+        }
+        if (parameterArray.length() > 0) {
+          parameterArray.deleteCharAt(0);
+        }
+        if (parameterDeclaration.length() > 0) {
+          parameterDeclaration.deleteCharAt(0);
+        }
+        if (typeParameterDeclaration.length() > 0) {
+          typeParameterDeclaration.deleteCharAt(0).insert(0, "<").append(">");
+        }
+
+        // public Request<Foo> doFoo(final Foo foo) {
+        sw.println("public %s %s %s(%s) {", typeParameterDeclaration,
+            jmethod.getReturnType().getParameterizedQualifiedSourceName(),
+            jmethod.getName(), parameterDeclaration);
+        sw.indent();
+        // The implements clause covers InstanceRequest
+        // class X extends AbstractRequest<Return> implements Request<Return> {
+        sw.println("class X extends %s<%s> implements %s {",
+            AbstractRequest.class.getCanonicalName(),
+            request.getDataType().getParameterizedQualifiedSourceName(),
+            jmethod.getReturnType().getParameterizedQualifiedSourceName());
+        sw.indent();
+
+        // public X() { super(FooRequestContext.this); }
+        sw.println("public X() { super(%s.this);}",
+            method.getSimpleSourceName());
+
+        // This could also be gotten rid of by having only Request /
+        // InstanceRequest
+        sw.println("@Override public X with(String... paths) {super.with(paths); return this;}");
+
+        // makeRequestData()
+        sw.println("@Override protected %s makeRequestData() {",
+            RequestData.class.getCanonicalName());
+        // return new RequestData("Foo::bar", {parameters}, propertyRefs,
+        // List.class, FooProxy.class);
+        String elementType = request.isCollectionType()
+            ? request.getCollectionElementType().getQualifiedSourceName()
+                + ".class" : "null";
+        String returnTypeBaseQualifiedName = ModelUtils.ensureBaseType(
+            request.getDataType()).getQualifiedSourceName();
+        sw.indentln(
+            "return new %s(\"%s\", new Object[] {%s}, propertyRefs, %s.class, %s);",
+            RequestData.class.getCanonicalName(), operation, parameterArray,
+            returnTypeBaseQualifiedName, elementType);
+        sw.println("}");
+
+        /*
+         * Only support extra properties in JSON-RPC payloads. Could add this to
+         * standard requests to provide out-of-band data.
+         */
+        if (method.getDialect().equals(Dialect.JSON_RPC)) {
+          for (JMethod setter : request.getExtraSetters()) {
+            PropertyName propertyNameAnnotation = setter.getAnnotation(PropertyName.class);
+            String propertyName = propertyNameAnnotation == null
+                ? JBeanMethod.SET.inferName(setter)
+                : propertyNameAnnotation.value();
+            String maybeReturn = JBeanMethod.SET_BUILDER.matches(setter)
+                ? "return this;" : "";
+            sw.println(
+                "%s { getRequestData().setNamedParameter(\"%s\", %s); %s}",
+                setter.getReadableDeclaration(false, false, false, false, true),
+                propertyName, setter.getParameters()[0].getName(), maybeReturn);
+          }
+        }
+
+        // end class X{}
+        sw.outdent();
+        sw.println("}");
+
+        // Instantiate, enqueue, and return
+        sw.println("X x = new X();");
+
+        if (request.getApiVersion() != null) {
+          sw.println("x.getRequestData().setApiVersion(\"%s\");",
+              Generator.escape(request.getApiVersion()));
+        }
+
+        // JSON-RPC payloads send their parameters in a by-name fashion
+        if (method.getDialect().equals(Dialect.JSON_RPC)) {
+          for (JParameter param : jmethod.getParameters()) {
+            PropertyName annotation = param.getAnnotation(PropertyName.class);
+            String propertyName = annotation == null ? param.getName()
+                : annotation.value();
+            boolean isContent = param.isAnnotationPresent(JsonRpcContent.class);
+            if (isContent) {
+              sw.println("x.getRequestData().setRequestContent(%s);",
+                  param.getName());
+            } else {
+              sw.println("x.getRequestData().setNamedParameter(\"%s\", %s);",
+                  propertyName, param.getName());
+            }
+          }
+        }
+
+        // See comment in AbstractRequest.using(EntityProxy)
+        if (!request.isInstance()) {
+          sw.println("addInvocation(x);");
+        }
+        sw.println("return x;");
+        sw.outdent();
+        sw.println("}");
+      }
+
+      sw.commit(logger);
+    }
+  }
+
+  private void writeContextMethods(SourceWriter sw) {
+    for (ContextMethod method : model.getMethods()) {
+      // public FooService foo() {
+      sw.println("public %s %s() {", method.getQualifiedSourceName(),
+          method.getMethodName());
+      // return new FooServiceImpl(this);
+      sw.indentln("return new %s(this);", method.getQualifiedSourceName());
+      sw.println("}");
+    }
+  }
+
+  private void writeTypeMap(SourceWriter sw) {
+    sw.println("private static final %1$s<String, Class<?>> tokensToTypes"
+        + " = new %1$s<String, Class<?>>();", HashMap.class.getCanonicalName());
+    sw.println("private static final %1$s<Class<?>, String> typesToTokens"
+        + " = new %1$s<Class<?>, String>();", HashMap.class.getCanonicalName());
+    sw.println(
+        "private static final %1$s<Class<?>> entityProxyTypes = new %1$s<Class<?>>();",
+        HashSet.class.getCanonicalName());
+    sw.println(
+        "private static final %1$s<Class<?>> valueProxyTypes = new %1$s<Class<?>>();",
+        HashSet.class.getCanonicalName());
+    sw.println("static {");
+    sw.indent();
+    for (EntityProxyModel type : model.getAllProxyModels()) {
+      // tokensToTypes.put("Foo", Foo.class);
+      sw.println("tokensToTypes.put(\"%s\", %s.class);",
+          type.getQualifiedBinaryName(), type.getQualifiedSourceName());
+      // typesToTokens.put(Foo.class, Foo);
+      sw.println("typesToTokens.put(%s.class, \"%s\");",
+          type.getQualifiedSourceName(), type.getQualifiedBinaryName());
+      // fooProxyTypes.add(MyFooProxy.class);
+      sw.println("%s.add(%s.class);", type.getType().equals(Type.ENTITY)
+          ? "entityProxyTypes" : "valueProxyTypes",
+          type.getQualifiedSourceName());
+    }
+    sw.outdent();
+    sw.println("}");
+
+    // Write instance methods
+    sw.println("@Override protected Class getTypeFromToken(String typeToken) {");
+    sw.indentln("return tokensToTypes.get(typeToken);");
+    sw.println("}");
+    sw.println("@Override protected String getTypeToken(Class type) {");
+    sw.indentln("return typesToTokens.get(type);");
+    sw.println("}");
+    sw.println("@Override public boolean isEntityType(Class<?> type) {");
+    sw.indentln("return entityProxyTypes.contains(type);");
+    sw.println("}");
+    sw.println("@Override public boolean isValueType(Class<?> type) {");
+    sw.indentln("return valueProxyTypes.contains(type);");
+    sw.println("}");
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/AcceptsModelVisitor.java b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/AcceptsModelVisitor.java
new file mode 100644
index 0000000..b6e11f4
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/AcceptsModelVisitor.java
@@ -0,0 +1,23 @@
+/*
+ * 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.requestfactory.gwt.rebind.model;
+
+/**
+ * A common interface for model types.
+ */
+public interface AcceptsModelVisitor {
+  void accept(ModelVisitor visitor);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/ContextMethod.java b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/ContextMethod.java
new file mode 100644
index 0000000..4de5093
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/ContextMethod.java
@@ -0,0 +1,122 @@
+/*
+ * 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.requestfactory.gwt.rebind.model;
+
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.web.bindery.requestfactory.shared.JsonRpcService;
+import com.google.web.bindery.requestfactory.shared.impl.AbstractRequestContext.Dialect;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Represents a service endpoint.
+ */
+public class ContextMethod implements AcceptsModelVisitor {
+
+  /**
+   * Builds a {@link ContextMethod}.
+   */
+  public static class Builder {
+    private ContextMethod toReturn = new ContextMethod();
+
+    public ContextMethod build() {
+      try {
+        return toReturn;
+      } finally {
+        toReturn = null;
+      }
+    }
+
+    public void setDeclaredMethod(JMethod method) {
+      toReturn.methodName = method.getName();
+      JClassType returnClass = method.getReturnType().isClassOrInterface();
+      toReturn.interfaceName = returnClass.getQualifiedSourceName();
+      toReturn.packageName = returnClass.getPackage().getName();
+      toReturn.simpleSourceName = returnClass.getName().replace('.', '_')
+          + "Impl";
+      toReturn.dialect = returnClass.isAnnotationPresent(JsonRpcService.class)
+          ? Dialect.JSON_RPC : Dialect.STANDARD;
+    }
+
+    public void setRequestMethods(List<RequestMethod> requestMethods) {
+      toReturn.requestMethods = requestMethods;
+    }
+  }
+
+  private Dialect dialect;
+  private String interfaceName;
+  private String methodName;
+  private String packageName;
+  private List<RequestMethod> requestMethods;
+  private String simpleSourceName;
+
+  private ContextMethod() {
+  }
+
+  public void accept(ModelVisitor visitor) {
+    if (visitor.visit(this)) {
+      for (RequestMethod method : getRequestMethods()) {
+        method.accept(visitor);
+      }
+    }
+    visitor.endVisit(this);
+  }
+
+  public Dialect getDialect() {
+    return dialect;
+  }
+
+  /**
+   * The qualified source name of the RequestContext sub-interface (i.e., the
+   * return type of the method declaration).
+   */
+  public String getImplementedInterfaceQualifiedSourceName() {
+    return interfaceName;
+  }
+
+  public String getMethodName() {
+    return methodName;
+  }
+
+  public String getPackageName() {
+    return packageName;
+  }
+
+  /**
+   * The qualified source name of the implementation.
+   */
+  public String getQualifiedSourceName() {
+    return getPackageName() + "." + getSimpleSourceName();
+  }
+
+  public List<RequestMethod> getRequestMethods() {
+    return Collections.unmodifiableList(requestMethods);
+  }
+
+  public String getSimpleSourceName() {
+    return simpleSourceName;
+  }
+
+  /**
+   * For debugging use only.
+   */
+  @Override
+  public String toString() {
+    return getQualifiedSourceName() + " " + getMethodName() + "()";
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/EntityProxyModel.java b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/EntityProxyModel.java
new file mode 100644
index 0000000..87c2eac
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/EntityProxyModel.java
@@ -0,0 +1,113 @@
+/*
+ * 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.requestfactory.gwt.rebind.model;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Represents an EntityProxy subtype.
+ */
+public class EntityProxyModel implements AcceptsModelVisitor {
+  /**
+   * Builds {@link EntityProxyModel}.
+   */
+  public static class Builder {
+    private EntityProxyModel toReturn = new EntityProxyModel();
+
+    public EntityProxyModel build() {
+      try {
+        return toReturn;
+      } finally {
+        toReturn = null;
+      }
+    }
+
+    /**
+     * Allow access to the unfinished EntityProxyModel to allow for circular
+     * type dependencies.
+     */
+    public EntityProxyModel peek() {
+      return toReturn;
+    }
+
+    public void setQualifiedBinaryName(String qualifiedBinaryName) {
+      toReturn.qualifiedBinaryName = qualifiedBinaryName;
+    }
+
+    public void setQualifiedSourceName(String name) {
+      assert !name.contains(" ");
+      toReturn.qualifiedSourceName = name;
+    }
+
+    public void setRequestMethods(List<RequestMethod> requestMethods) {
+      toReturn.requestMethods = requestMethods;
+    }
+
+    public void setType(Type type) {
+      toReturn.type = type;
+    }
+  }
+
+  /**
+   * The kind of proxy. This is an enum in case more proxy types are defined in
+   * the future.
+   */
+  public enum Type {
+    ENTITY, VALUE
+  }
+
+  private String qualifiedBinaryName;
+  private String qualifiedSourceName;
+  private List<RequestMethod> requestMethods;
+  private Type type;
+
+  private EntityProxyModel() {
+  }
+
+  public void accept(ModelVisitor visitor) {
+    if (visitor.visit(this)) {
+      for (RequestMethod method : requestMethods) {
+        method.accept(visitor);
+      }
+    }
+    visitor.endVisit(this);
+  }
+
+  public String getQualifiedBinaryName() {
+    return qualifiedBinaryName;
+  }
+
+  public String getQualifiedSourceName() {
+    return qualifiedSourceName;
+  }
+
+  public List<RequestMethod> getRequestMethods() {
+    return Collections.unmodifiableList(requestMethods);
+  }
+
+  public Type getType() {
+    return type;
+  }
+
+  /**
+   * For debugging use only.
+   */
+  @Override
+  public String toString() {
+    return qualifiedSourceName;
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/ModelVisitor.java b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/ModelVisitor.java
new file mode 100644
index 0000000..09c799a
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/ModelVisitor.java
@@ -0,0 +1,49 @@
+/*
+ * 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.requestfactory.gwt.rebind.model;
+
+/**
+ * Implements traversal across a RequestFactory model.
+ */
+public class ModelVisitor {
+  public void endVisit(ContextMethod x) {
+  }
+
+  public void endVisit(EntityProxyModel x) {
+  }
+
+  public void endVisit(RequestFactoryModel x) {
+  }
+
+  public void endVisit(RequestMethod x) {
+  }
+
+  public boolean visit(ContextMethod x) {
+    return true;
+  }
+
+  public boolean visit(EntityProxyModel x) {
+    return true;
+  }
+
+  public boolean visit(RequestFactoryModel x) {
+    return true;
+  }
+
+  public boolean visit(RequestMethod x) {
+    return true;
+  }
+}
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
new file mode 100644
index 0000000..6af1dbb
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/RequestFactoryModel.java
@@ -0,0 +1,459 @@
+/*
+ * 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.requestfactory.gwt.rebind.model;
+
+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;
+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 com.google.web.bindery.requestfactory.gwt.rebind.model.EntityProxyModel.Type;
+import com.google.web.bindery.requestfactory.gwt.rebind.model.RequestMethod.CollectionType;
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.InstanceRequest;
+import com.google.web.bindery.requestfactory.shared.JsonRpcProxy;
+import com.google.web.bindery.requestfactory.shared.JsonRpcService;
+import com.google.web.bindery.requestfactory.shared.ProxyFor;
+import com.google.web.bindery.requestfactory.shared.ProxyForName;
+import com.google.web.bindery.requestfactory.shared.Request;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.RequestFactory;
+import com.google.web.bindery.requestfactory.shared.Service;
+import com.google.web.bindery.requestfactory.shared.ServiceName;
+import com.google.web.bindery.requestfactory.shared.ValueProxy;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Represents a RequestFactory interface declaration.
+ */
+public class RequestFactoryModel implements AcceptsModelVisitor {
+  public static String poisonedMessage() {
+    return "Unable to create RequestFactoryModel model due to previous errors";
+  }
+
+  static String badContextReturnType(JMethod method,
+      JClassType requestInterface, JClassType instanceRequestInterface) {
+    return String.format(
+        "Return type %s in method %s must be an interface assignable"
+            + " to %s or %s", method.getReturnType(), method.getName(),
+        requestInterface.getSimpleSourceName(),
+        instanceRequestInterface.getSimpleSourceName());
+  }
+
+  static String noSettersAllowed(JMethod found) {
+    return String.format("Optional setters not allowed here: ", found.getName());
+  }
+
+  private final JClassType collectionInterface;
+  private final List<ContextMethod> contextMethods = new ArrayList<ContextMethod>();
+  private final JClassType entityProxyInterface;
+  private final JClassType factoryType;
+  private final JClassType instanceRequestInterface;
+  private final JClassType listInterface;
+  private final TreeLogger logger;
+  private final JClassType mapInterface;
+  private final TypeOracle oracle;
+  /**
+   * This map prevents cyclic type dependencies from overflowing the stack.
+   */
+  private final Map<JClassType, EntityProxyModel.Builder> peerBuilders = new HashMap<JClassType, EntityProxyModel.Builder>();
+  /**
+   * Iterated by {@link #getAllProxyModels()}.
+   */
+  private final Map<JClassType, EntityProxyModel> peers = new LinkedHashMap<JClassType, EntityProxyModel>();
+  private boolean poisoned;
+  private final JClassType requestContextInterface;
+  private final JClassType requestFactoryInterface;
+  private final JClassType requestInterface;
+  private final JClassType setInterface;
+  private final JClassType splittableType;
+
+  private final JClassType valueProxyInterface;
+
+  public RequestFactoryModel(TreeLogger logger, JClassType factoryType)
+      throws UnableToCompleteException {
+    this.logger = logger;
+    this.factoryType = factoryType;
+    this.oracle = factoryType.getOracle();
+    collectionInterface = oracle.findType(Collection.class.getCanonicalName());
+    entityProxyInterface = oracle.findType(EntityProxy.class.getCanonicalName());
+    instanceRequestInterface = oracle.findType(InstanceRequest.class.getCanonicalName());
+    listInterface = oracle.findType(List.class.getCanonicalName());
+    mapInterface = oracle.findType(Map.class.getCanonicalName());
+    requestContextInterface = oracle.findType(RequestContext.class.getCanonicalName());
+    requestFactoryInterface = oracle.findType(RequestFactory.class.getCanonicalName());
+    requestInterface = oracle.findType(Request.class.getCanonicalName());
+    setInterface = oracle.findType(Set.class.getCanonicalName());
+    splittableType = oracle.findType(Splittable.class.getCanonicalName());
+    valueProxyInterface = oracle.findType(ValueProxy.class.getCanonicalName());
+
+    for (JMethod method : factoryType.getOverridableMethods()) {
+      if (method.getEnclosingType().equals(requestFactoryInterface)) {
+        // Ignore methods defined an RequestFactory itself
+        continue;
+      }
+
+      if (method.getParameters().length > 0) {
+        poison("Unexpected parameter on method %s", method.getName());
+        continue;
+      }
+
+      JClassType contextType = method.getReturnType().isInterface();
+      if (contextType == null
+          || !requestContextInterface.isAssignableFrom(contextType)) {
+        poison("Unexpected return type %s on method %s is not"
+            + " an interface assignable to %s",
+            method.getReturnType().getQualifiedSourceName(), method.getName(),
+            requestContextInterface.getSimpleSourceName());
+        continue;
+      }
+
+      ContextMethod.Builder builder = new ContextMethod.Builder();
+      builder.setDeclaredMethod(method);
+      buildContextMethod(builder, contextType);
+      contextMethods.add(builder.build());
+    }
+
+    if (poisoned) {
+      die(poisonedMessage());
+    }
+  }
+
+  public void accept(ModelVisitor visitor) {
+    if (visitor.visit(this)) {
+      for (EntityProxyModel model : getAllProxyModels()) {
+        model.accept(visitor);
+      }
+      for (ContextMethod method : getMethods()) {
+        method.accept(visitor);
+      }
+    }
+    visitor.endVisit(this);
+  }
+
+  public Collection<EntityProxyModel> getAllProxyModels() {
+    return Collections.unmodifiableCollection(peers.values());
+  }
+
+  public JClassType getFactoryType() {
+    return factoryType;
+  }
+
+  public List<ContextMethod> getMethods() {
+    return Collections.unmodifiableList(contextMethods);
+  }
+
+  public EntityProxyModel getPeer(JClassType entityProxyType) {
+    return peers.get(entityProxyType);
+  }
+
+  /**
+   * For debugging use only.
+   */
+  @Override
+  public String toString() {
+    return getFactoryType().getQualifiedSourceName();
+  }
+
+  /**
+   * Examine a RequestContext subtype to populate a ContextMethod.
+   */
+  private void buildContextMethod(ContextMethod.Builder contextBuilder,
+      JClassType contextType) throws UnableToCompleteException {
+    Service serviceAnnotation = contextType.getAnnotation(Service.class);
+    ServiceName serviceNameAnnotation = contextType.getAnnotation(ServiceName.class);
+    JsonRpcService jsonRpcAnnotation = contextType.getAnnotation(JsonRpcService.class);
+    if (serviceAnnotation == null && serviceNameAnnotation == null
+        && jsonRpcAnnotation == null) {
+      poison("RequestContext subtype %s is missing a @%s or @%s annotation",
+          contextType.getQualifiedSourceName(), Service.class.getSimpleName(),
+          JsonRpcService.class.getSimpleName());
+      return;
+    }
+
+    List<RequestMethod> requestMethods = new ArrayList<RequestMethod>();
+    for (JMethod method : contextType.getInheritableMethods()) {
+      if (method.getEnclosingType().equals(requestContextInterface)) {
+        // Ignore methods declared in RequestContext
+        continue;
+      }
+
+      RequestMethod.Builder methodBuilder = new RequestMethod.Builder();
+      methodBuilder.setDeclarationMethod(method);
+
+      if (!validateContextMethodAndSetDataType(methodBuilder, method,
+          jsonRpcAnnotation != null)) {
+        continue;
+      }
+
+      requestMethods.add(methodBuilder.build());
+    }
+
+    contextBuilder.setRequestMethods(requestMethods);
+  }
+
+  private void die(String message) throws UnableToCompleteException {
+    poison(message);
+    throw new UnableToCompleteException();
+  }
+
+  private EntityProxyModel getEntityProxyType(JClassType entityProxyType)
+      throws UnableToCompleteException {
+    entityProxyType = ModelUtils.ensureBaseType(entityProxyType);
+    EntityProxyModel toReturn = peers.get(entityProxyType);
+    if (toReturn == null) {
+      EntityProxyModel.Builder inProgress = peerBuilders.get(entityProxyType);
+      if (inProgress != null) {
+        toReturn = inProgress.peek();
+      }
+    }
+    if (toReturn == null) {
+      EntityProxyModel.Builder builder = new EntityProxyModel.Builder();
+      peerBuilders.put(entityProxyType, builder);
+
+      builder.setQualifiedBinaryName(ModelUtils.getQualifiedBaseBinaryName(entityProxyType));
+      builder.setQualifiedSourceName(ModelUtils.getQualifiedBaseSourceName(entityProxyType));
+      if (entityProxyInterface.isAssignableFrom(entityProxyType)) {
+        builder.setType(Type.ENTITY);
+      } else if (valueProxyInterface.isAssignableFrom(entityProxyType)) {
+        builder.setType(Type.VALUE);
+      } else {
+        poison("The type %s is not assignable to either %s or %s",
+            entityProxyInterface.getQualifiedSourceName(),
+            valueProxyInterface.getQualifiedSourceName());
+        // Cannot continue, since knowing the behavior is crucial
+        die(poisonedMessage());
+      }
+
+      // Get the server domain object type
+      ProxyFor proxyFor = entityProxyType.getAnnotation(ProxyFor.class);
+      ProxyForName proxyForName = entityProxyType.getAnnotation(ProxyForName.class);
+      JsonRpcProxy jsonRpcProxy = entityProxyType.getAnnotation(JsonRpcProxy.class);
+      if (proxyFor == null && proxyForName == null && jsonRpcProxy == null) {
+        poison("The %s type does not have a @%s, @%s, or @%s annotation",
+            entityProxyType.getQualifiedSourceName(),
+            ProxyFor.class.getSimpleName(), ProxyForName.class.getSimpleName(),
+            JsonRpcProxy.class.getSimpleName());
+      }
+
+      // Look at the methods declared on the EntityProxy
+      List<RequestMethod> requestMethods = new ArrayList<RequestMethod>();
+      Map<String, JMethod> duplicatePropertyGetters = new HashMap<String, JMethod>();
+      for (JMethod method : entityProxyType.getInheritableMethods()) {
+        if (method.getEnclosingType().equals(entityProxyInterface)) {
+          // Ignore methods on EntityProxy
+          continue;
+        }
+        RequestMethod.Builder methodBuilder = new RequestMethod.Builder();
+        methodBuilder.setDeclarationMethod(method);
+
+        JType transportedType;
+        String name = method.getName();
+        if (JBeanMethod.GET.matches(method)) {
+          transportedType = method.getReturnType();
+          String propertyName = JBeanMethod.GET.inferName(method);
+          JMethod previouslySeen = duplicatePropertyGetters.get(propertyName);
+          if (previouslySeen == null) {
+            duplicatePropertyGetters.put(propertyName, method);
+          } else {
+            poison("Duplicate accessors for property %s: %s() and %s()",
+                propertyName, previouslySeen.getName(), method.getName());
+          }
+
+        } else if (JBeanMethod.SET.matches(method)
+            || JBeanMethod.SET_BUILDER.matches(method)) {
+          transportedType = method.getParameters()[0].getType();
+
+        } else if (name.equals("stableId")
+            && method.getParameters().length == 0) {
+          // Ignore any overload of stableId
+          continue;
+        } else {
+          poison("The method %s is neither a getter nor a setter",
+              method.getReadableDeclaration());
+          continue;
+        }
+        validateTransportableType(methodBuilder, transportedType, false);
+        RequestMethod requestMethod = methodBuilder.build();
+        requestMethods.add(requestMethod);
+      }
+      builder.setRequestMethods(requestMethods);
+
+      toReturn = builder.build();
+      peers.put(entityProxyType, toReturn);
+      peerBuilders.remove(entityProxyType);
+    }
+    return toReturn;
+  }
+
+  private void poison(String message, Object... args) {
+    logger.log(TreeLogger.ERROR, String.format(message, args));
+    poisoned = true;
+  }
+
+  /**
+   * Examine a RequestContext method to see if it returns a transportable type.
+   */
+  private boolean validateContextMethodAndSetDataType(
+      RequestMethod.Builder methodBuilder, JMethod method, boolean allowSetters)
+      throws UnableToCompleteException {
+    JClassType requestReturnType = method.getReturnType().isInterface();
+    JClassType invocationReturnType;
+    if (requestReturnType == null) {
+      // Primitive return type
+      poison(badContextReturnType(method, requestInterface,
+          instanceRequestInterface));
+      return false;
+    }
+
+    if (instanceRequestInterface.isAssignableFrom(requestReturnType)) {
+      // Instance method invocation
+      JClassType[] params = ModelUtils.findParameterizationOf(
+          instanceRequestInterface, requestReturnType);
+      methodBuilder.setInstanceType(getEntityProxyType(params[0]));
+      invocationReturnType = params[1];
+    } else if (requestInterface.isAssignableFrom(requestReturnType)) {
+      // Static method invocation
+      JClassType[] params = ModelUtils.findParameterizationOf(requestInterface,
+          requestReturnType);
+      invocationReturnType = params[0];
+
+    } else {
+      // Unhandled return type, must be something random
+      poison(badContextReturnType(method, requestInterface,
+          instanceRequestInterface));
+      return false;
+    }
+
+    // Validate the parameters
+    boolean paramsOk = true;
+    JParameter[] params = method.getParameters();
+    for (int i = 0; i < params.length; ++i) {
+      JParameter param = params[i];
+      paramsOk = validateTransportableType(new RequestMethod.Builder(),
+          param.getType(), false)
+          && paramsOk;
+    }
+
+    // Validate any extra properties on the request type
+    for (JMethod maybeSetter : requestReturnType.getInheritableMethods()) {
+      if (JBeanMethod.SET.matches(maybeSetter)
+          || JBeanMethod.SET_BUILDER.matches(maybeSetter)) {
+        if (allowSetters) {
+          methodBuilder.addExtraSetter(maybeSetter);
+        } else {
+          poison(noSettersAllowed(maybeSetter));
+        }
+      }
+    }
+    return validateTransportableType(methodBuilder, invocationReturnType, true);
+  }
+
+  /**
+   * Examines a type to see if it can be transported.
+   */
+  private boolean validateTransportableType(
+      RequestMethod.Builder methodBuilder, JType type, boolean requireObject)
+      throws UnableToCompleteException {
+    JClassType transportedClass = type.isClassOrInterface();
+    if (transportedClass == null) {
+      if (requireObject) {
+        poison("The type %s cannot be transported by RequestFactory as"
+            + " a return type", type.getQualifiedSourceName());
+        return false;
+      } else {
+        // Primitives always ok
+        return true;
+      }
+    }
+
+    if (ModelUtils.isValueType(oracle, transportedClass)
+        || splittableType.equals(transportedClass)) {
+      // Simple values, like Integer and String
+      methodBuilder.setValueType(true);
+    } else if (entityProxyInterface.isAssignableFrom(transportedClass)
+        || valueProxyInterface.isAssignableFrom(transportedClass)) {
+      // EntityProxy and ValueProxy return types
+      methodBuilder.setEntityType(getEntityProxyType(transportedClass));
+    } else if (collectionInterface.isAssignableFrom(transportedClass)) {
+      // Only allow certain collections for now
+      JParameterizedType parameterized = transportedClass.isParameterized();
+      if (parameterized == null) {
+        poison("Requests that return collections of List or Set must be parameterized");
+        return false;
+      }
+      if (listInterface.equals(parameterized.getBaseType())) {
+        methodBuilder.setCollectionType(CollectionType.LIST);
+      } else if (setInterface.equals(parameterized.getBaseType())) {
+        methodBuilder.setCollectionType(CollectionType.SET);
+      } else {
+        poison("Requests that return collections may be declared with"
+            + " %s or %s only", listInterface.getQualifiedSourceName(),
+            setInterface.getQualifiedSourceName());
+        return false;
+      }
+      // Also record the element type in the method builder
+      JClassType elementType = ModelUtils.findParameterizationOf(
+          collectionInterface, transportedClass)[0];
+      methodBuilder.setCollectionElementType(elementType);
+      validateTransportableType(methodBuilder, elementType, requireObject);
+    } else if (mapInterface.isAssignableFrom(transportedClass)) {
+      JParameterizedType parameterized = transportedClass.isParameterized();
+      if (parameterized == null) {
+        poison("Requests that return Maps must be parameterized");
+        return false;
+      }
+      if (mapInterface.equals(parameterized.getBaseType())) {
+        methodBuilder.setCollectionType(CollectionType.MAP);
+      } else {
+        poison("Requests that return maps may be declared with" + " %s only",
+            mapInterface.getQualifiedSourceName());
+        return false;
+      }
+      // Also record the element type in the method builder
+      JClassType[] params = ModelUtils.findParameterizationOf(mapInterface,
+          transportedClass);
+      JClassType keyType = params[0];
+      JClassType valueType = params[1];
+      methodBuilder.setMapKeyType(keyType);
+      methodBuilder.setMapValueType(valueType);
+      validateTransportableType(methodBuilder, keyType, requireObject);
+      validateTransportableType(methodBuilder, valueType, requireObject);
+    } else {
+      // Unknown type, fail
+      poison("Invalid Request parameterization %s",
+          transportedClass.getQualifiedSourceName());
+      return false;
+    }
+    methodBuilder.setDataType(transportedClass);
+    return true;
+  }
+
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/RequestMethod.java b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/RequestMethod.java
new file mode 100644
index 0000000..ec5ef6d
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/RequestMethod.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.requestfactory.gwt.rebind.model;
+
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.web.bindery.requestfactory.shared.JsonRpcWireName;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Represents a method declaration that causes data to be transported. This can
+ * be a method declared in a RequestContext or a getter or setter on an
+ * EntityProxy.
+ */
+public class RequestMethod implements AcceptsModelVisitor {
+
+  /**
+   * Builds a {@link ContextMethod}.
+   */
+  public static class Builder {
+    private RequestMethod toReturn = new RequestMethod();
+
+    public void addExtraSetter(JMethod method) {
+      if (toReturn.extraSetters == null) {
+        toReturn.extraSetters = new ArrayList<JMethod>();
+      }
+      toReturn.extraSetters.add(method);
+    }
+
+    public RequestMethod build() {
+      if (toReturn.extraSetters == null) {
+        toReturn.extraSetters = Collections.emptyList();
+      } else {
+        toReturn.extraSetters = Collections.unmodifiableList(toReturn.extraSetters);
+      }
+      try {
+        return toReturn;
+      } finally {
+        toReturn = null;
+      }
+    }
+
+    public void setCollectionElementType(JClassType elementType) {
+      toReturn.collectionElementType = elementType;
+    }
+
+    public void setCollectionType(CollectionType collectionType) {
+      toReturn.collectionType = collectionType;
+    }
+
+    public void setDataType(JClassType dataType) {
+      toReturn.dataType = dataType;
+    }
+
+    public void setDeclarationMethod(JMethod declarationMethod) {
+      toReturn.declarationMethod = declarationMethod;
+
+      JClassType returnClass = declarationMethod.getReturnType().isClassOrInterface();
+      JsonRpcWireName annotation = returnClass == null ? null
+          : returnClass.getAnnotation(JsonRpcWireName.class);
+      if (annotation == null) {
+        toReturn.operation = declarationMethod.getEnclosingType().getQualifiedBinaryName()
+            + "::" + declarationMethod.getName();
+      } else {
+        toReturn.operation = annotation.value();
+        toReturn.apiVersion = annotation.version();
+      }
+    }
+
+    public void setEntityType(EntityProxyModel entityType) {
+      toReturn.entityType = entityType;
+    }
+
+    public void setInstanceType(EntityProxyModel instanceType) {
+      toReturn.instanceType = instanceType;
+    }
+
+    public void setMapKeyType(JClassType elementType) {
+      toReturn.mapKeyType = elementType;
+    }
+
+    public void setMapValueType(JClassType elementType) {
+      toReturn.mapValueType = elementType;
+    }
+
+    public void setValueType(boolean valueType) {
+      toReturn.valueType = valueType;
+    }
+  }
+
+  /**
+   * Indicates the type of collection that a Request will return.
+   */
+  public enum CollectionType {
+    LIST, SET, MAP
+  }
+
+  private String apiVersion;
+  private JClassType collectionElementType;
+  private CollectionType collectionType;
+  private JClassType dataType;
+  private JMethod declarationMethod;
+  private EntityProxyModel entityType;
+  private List<JMethod> extraSetters = new ArrayList<JMethod>();
+  private EntityProxyModel instanceType;
+  private String operation;
+  private JClassType mapValueType;
+  private JClassType mapKeyType;
+  private boolean valueType;
+
+  private RequestMethod() {
+  }
+
+  public void accept(ModelVisitor visitor) {
+    if (visitor.visit(this)) {
+      // Empty
+    }
+    visitor.endVisit(this);
+  }
+
+  public String getApiVersion() {
+    return apiVersion;
+  }
+
+  /**
+   * If the method returns a collection, this method will return the element
+   * type.
+   * 
+   * @return
+   */
+  public JClassType getCollectionElementType() {
+    return collectionElementType;
+  }
+
+  public CollectionType getCollectionType() {
+    return collectionType;
+  }
+
+  public JClassType getDataType() {
+    return dataType;
+  }
+
+  public JMethod getDeclarationMethod() {
+    return declarationMethod;
+  }
+
+  /**
+   * If the type returned from {@link #getDataType()} refers to an EntityProxy
+   * subtype, or a collection of EntityProxy subtypes, returns the
+   * EntityProxyModel describing the entity.
+   */
+  public EntityProxyModel getEntityType() {
+    return entityType;
+  }
+
+  public List<JMethod> getExtraSetters() {
+    return extraSetters;
+  }
+
+  /**
+   * If the method is intended to be invoked on an instance of an EntityProxy,
+   * returns the EntityProxyModel describing that type.
+   */
+  public EntityProxyModel getInstanceType() {
+    return instanceType;
+  }
+
+  public JClassType getMapKeyType() {
+    return mapKeyType;
+  }
+
+  public JClassType getMapValueType() {
+    return mapValueType;
+  }
+
+  public String getOperation() {
+    return operation;
+  }
+
+  public boolean isCollectionType() {
+    return collectionType != null;
+  }
+
+  public boolean isEntityType() {
+    return entityType != null;
+  }
+
+  public boolean isInstance() {
+    return instanceType != null;
+  }
+
+  public boolean isValueType() {
+    return valueType;
+  }
+
+  /**
+   * For debugging use only.
+   */
+  @Override
+  public String toString() {
+    return getDeclarationMethod().toString();
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/ui/client/EntityProxyKeyProvider.java b/user/src/com/google/web/bindery/requestfactory/gwt/ui/client/EntityProxyKeyProvider.java
new file mode 100644
index 0000000..9aa6523
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/ui/client/EntityProxyKeyProvider.java
@@ -0,0 +1,42 @@
+/*
+ * 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.requestfactory.gwt.ui.client;
+
+import com.google.gwt.view.client.ProvidesKey;
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+
+/**
+ * An {@link EntityProxy}-aware key provider, handy for use with
+ * {@link com.google.gwt.view.client.SelectionModel} and various
+ * cell widgets.
+ * 
+ * @see com.google.gwt.user.cellview.client.CellBrowser
+ * @see com.google.gwt.user.cellview.client.CellList
+ * @see com.google.gwt.user.cellview.client.CellTable
+ * 
+ * @param <P> the proxy type
+ */
+public class EntityProxyKeyProvider<P extends EntityProxy> implements
+    ProvidesKey<P> {
+  /**
+   * Returns the key Object for the given item.
+   *
+   * @param item an item of type P
+   */
+  public Object getKey(P item) {
+    return item == null ? null : item.stableId();
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/ui/client/ProxyRenderer.java b/user/src/com/google/web/bindery/requestfactory/gwt/ui/client/ProxyRenderer.java
new file mode 100644
index 0000000..dd5f733
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/ui/client/ProxyRenderer.java
@@ -0,0 +1,48 @@
+/*
+ * 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.requestfactory.gwt.ui.client;
+
+import com.google.gwt.text.shared.AbstractRenderer;
+
+/**
+ * Renders a proxy object, and reports the properties it requires to do that
+ * rendering.
+ * 
+ * @param <R> the type to render
+ */
+public abstract class ProxyRenderer<R> extends
+    AbstractRenderer<R> {
+
+  private final String[] paths;
+
+  /**
+   * Constructs a {@link ProxyRenderer} with a given set of paths.
+   *
+   * @param paths an Array of Strings
+   */
+  public ProxyRenderer(String[] paths) {
+    this.paths = paths;
+  }
+
+  /**
+   * The properties required by this renderer.
+   *
+   * @return an Array of String paths
+   */
+  public String[] getPaths() {
+    return paths;
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/ui/client/package-info.java b/user/src/com/google/web/bindery/requestfactory/gwt/ui/client/package-info.java
new file mode 100644
index 0000000..f9d62e8
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/ui/client/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+/**
+ * Classes used by the request factory to manage proxies, user logins, and authentication.
+ */
+@com.google.gwt.util.PreventSpuriousRebuilds
+package com.google.web.bindery.requestfactory.gwt.ui.client;
diff --git a/user/src/com/google/web/bindery/requestfactory/server/DeadEntityException.java b/user/src/com/google/web/bindery/requestfactory/server/DeadEntityException.java
new file mode 100644
index 0000000..5244543
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/DeadEntityException.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.
+ */
+package com.google.web.bindery.requestfactory.server;
+
+/**
+ * Indicates the user attempted to perform an operation on an irretrievable
+ * entity.
+ */
+class DeadEntityException extends RuntimeException {
+  /**
+   * Contructs a new {@link DeadEntityException} with a given message.
+   * 
+   * @param message a message String
+   */
+  public DeadEntityException(String message) {
+    super(message);
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/DefaultExceptionHandler.java b/user/src/com/google/web/bindery/requestfactory/server/DefaultExceptionHandler.java
new file mode 100644
index 0000000..ab86e5c
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/DefaultExceptionHandler.java
@@ -0,0 +1,29 @@
+/*
+ * 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.requestfactory.server;
+
+import com.google.web.bindery.requestfactory.shared.ServerFailure;
+
+/**
+ * Default implementation for handling exceptions thrown while processing a
+ * request. Suppresses stack traces and the exception class name.
+ */
+public class DefaultExceptionHandler implements ExceptionHandler {
+  public ServerFailure createServerFailure(Throwable throwable) {
+    return new ServerFailure("Server Error: "
+        + (throwable == null ? null : throwable.getMessage()), null, null, true);
+  }
+}
\ No newline at end of file
diff --git a/user/src/com/google/web/bindery/requestfactory/server/ExceptionHandler.java b/user/src/com/google/web/bindery/requestfactory/server/ExceptionHandler.java
new file mode 100644
index 0000000..7481ed0
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/ExceptionHandler.java
@@ -0,0 +1,35 @@
+/*
+ * 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.requestfactory.server;
+
+import com.google.web.bindery.requestfactory.shared.ServerFailure;
+
+/**
+ * Handles an exception produced while processing a request.
+ * 
+ * @see DefaultExceptionHandler
+ */
+public interface ExceptionHandler {
+  /**
+   * Generates a {@link ServerFailure} based on the information contained in the
+   * received {@code exception}.
+   * 
+   * @param throwable a Throwable instance
+   * @return a {@link ServerFailure} instance
+   * @see DefaultExceptionHandler
+   */
+  ServerFailure createServerFailure(Throwable throwable);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/LocatorServiceLayer.java b/user/src/com/google/web/bindery/requestfactory/server/LocatorServiceLayer.java
new file mode 100644
index 0000000..aa1c5d6
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/LocatorServiceLayer.java
@@ -0,0 +1,236 @@
+/*
+ * 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.requestfactory.server;
+
+import com.google.web.bindery.requestfactory.shared.BaseProxy;
+import com.google.web.bindery.requestfactory.shared.Locator;
+import com.google.web.bindery.requestfactory.shared.ProxyFor;
+import com.google.web.bindery.requestfactory.shared.ProxyForName;
+import com.google.web.bindery.requestfactory.shared.Request;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.Service;
+import com.google.web.bindery.requestfactory.shared.ServiceLocator;
+import com.google.web.bindery.requestfactory.shared.ServiceName;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+/**
+ * Adds support to the ServiceLayer chain for using {@link Locator} and
+ * {@link ServiceLocator} helper objects.
+ */
+final class LocatorServiceLayer extends ServiceLayerDecorator {
+
+  @Override
+  public <T> T createDomainObject(Class<T> clazz) {
+    Locator<T, ?> l = getLocator(clazz);
+    if (l == null) {
+      return super.createDomainObject(clazz);
+    }
+    return l.create(clazz);
+  }
+
+  @Override
+  public <T extends Locator<?, ?>> T createLocator(Class<T> clazz) {
+    return newInstance(clazz, Locator.class);
+  }
+
+  @Override
+  public Object createServiceInstance(Method contextMethod, Method domainMethod) {
+    Class<? extends ServiceLocator> locatorType = getTop().resolveServiceLocator(
+        contextMethod, domainMethod);
+    ServiceLocator locator = getTop().createServiceLocator(locatorType);
+    // Enclosing class may be a parent class, so invoke on service class
+    Class<?> declaringClass = contextMethod.getDeclaringClass();
+    Class<?> serviceClass = getTop().resolveServiceClass(
+        declaringClass.asSubclass(RequestContext.class));
+    return locator.getInstance(serviceClass);
+  }
+
+  @Override
+  public <T extends ServiceLocator> T createServiceLocator(
+      Class<T> serviceLocatorType) {
+    return newInstance(serviceLocatorType, ServiceLocator.class);
+  }
+
+  @Override
+  public Object getId(Object domainObject) {
+    return doGetId(domainObject);
+  }
+
+  @Override
+  public Class<?> getIdType(Class<?> domainType) {
+    Locator<?, ?> l = getLocator(domainType);
+    if (l == null) {
+      return super.getIdType(domainType);
+    }
+    return l.getIdType();
+  }
+
+  @Override
+  public Object getVersion(Object domainObject) {
+    return doGetVersion(domainObject);
+  }
+
+  @Override
+  public boolean isLive(Object domainObject) {
+    return doIsLive(domainObject);
+  }
+
+  @Override
+  public <T> T loadDomainObject(Class<T> clazz, Object domainId) {
+    return doLoadDomainObject(clazz, domainId);
+  }
+
+  /**
+   * Returns true if the context method returns a {@link Request} and the domain
+   * method is non-static.
+   */
+  @Override
+  public boolean requiresServiceLocator(Method contextMethod,
+      Method domainMethod) {
+    return Request.class.isAssignableFrom(contextMethod.getReturnType())
+        && !Modifier.isStatic(domainMethod.getModifiers());
+  }
+
+  @Override
+  public Class<? extends Locator<?, ?>> resolveLocator(Class<?> domainType) {
+    // Find the matching BaseProxy
+    Class<?> proxyType = getTop().resolveClientType(domainType,
+        BaseProxy.class, false);
+    if (proxyType == null) {
+      return null;
+    }
+
+    // Check it for annotations
+    Class<? extends Locator<?, ?>> locatorType;
+    ProxyFor l = proxyType.getAnnotation(ProxyFor.class);
+    ProxyForName ln = proxyType.getAnnotation(ProxyForName.class);
+    if (l != null && !Locator.class.equals(l.locator())) {
+      @SuppressWarnings("unchecked")
+      Class<? extends Locator<?, ?>> found = (Class<? extends Locator<?, ?>>) l.locator();
+      locatorType = found;
+    } else if (ln != null && ln.locator().length() > 0) {
+      try {
+        @SuppressWarnings("unchecked")
+        Class<? extends Locator<?, ?>> found = (Class<? extends Locator<?, ?>>) Class.forName(
+            ln.locator(), false, getTop().getDomainClassLoader()).asSubclass(
+            Locator.class);
+        locatorType = found;
+      } catch (ClassNotFoundException e) {
+        return die(
+            e,
+            "Could not find the locator type specified in the @%s annotation %s",
+            ProxyForName.class.getCanonicalName(), ln.value());
+      }
+    } else {
+      // No locator annotation
+      locatorType = null;
+    }
+    return locatorType;
+  }
+
+  @Override
+  public Class<? extends ServiceLocator> resolveServiceLocator(
+      Method contextMethod, Method domainMethod) {
+    Class<? extends ServiceLocator> locatorType;
+
+    // Look at the RequestContext
+    Class<?> requestContextClass = contextMethod.getDeclaringClass();
+    Service l = requestContextClass.getAnnotation(Service.class);
+    ServiceName ln = requestContextClass.getAnnotation(ServiceName.class);
+    if (l != null && !ServiceLocator.class.equals(l.locator())) {
+      locatorType = l.locator();
+    } else if (ln != null && ln.locator().length() > 0) {
+      try {
+        locatorType = Class.forName(ln.locator(), false,
+            getTop().getDomainClassLoader()).asSubclass(ServiceLocator.class);
+      } catch (ClassNotFoundException e) {
+        return die(
+            e,
+            "Could not find the locator type specified in the @%s annotation %s",
+            ServiceName.class.getCanonicalName(), ln.value());
+      }
+    } else {
+      locatorType = null;
+    }
+    return locatorType;
+  }
+
+  private <T> Object doGetId(T domainObject) {
+    @SuppressWarnings("unchecked")
+    Class<T> clazz = (Class<T>) domainObject.getClass();
+    Locator<T, ?> l = getLocator(clazz);
+    if (l == null) {
+      return super.getId(domainObject);
+    }
+    return l.getId(domainObject);
+  }
+
+  private <T> Object doGetVersion(T domainObject) {
+    @SuppressWarnings("unchecked")
+    Class<T> clazz = (Class<T>) domainObject.getClass();
+    Locator<T, ?> l = getLocator(clazz);
+    if (l == null) {
+      return super.getVersion(domainObject);
+    }
+    return l.getVersion(domainObject);
+  }
+
+  private <T> boolean doIsLive(T domainObject) {
+    @SuppressWarnings("unchecked")
+    Class<T> clazz = (Class<T>) domainObject.getClass();
+    Locator<T, ?> l = getLocator(clazz);
+    if (l == null) {
+      return super.isLive(domainObject);
+    }
+    return l.isLive(domainObject);
+  }
+
+  private <T, I> T doLoadDomainObject(Class<T> clazz, Object domainId) {
+    @SuppressWarnings("unchecked")
+    Locator<T, I> l = (Locator<T, I>) getLocator(clazz);
+    if (l == null) {
+      return super.loadDomainObject(clazz, domainId);
+    }
+    I id = l.getIdType().cast(domainId);
+    return l.find(clazz, id);
+  }
+
+  @SuppressWarnings("unchecked")
+  private <T, I> Locator<T, I> getLocator(Class<T> domainType) {
+    Class<? extends Locator<?, ?>> locatorType = getTop().resolveLocator(
+        domainType);
+    if (locatorType == null) {
+      return null;
+    }
+    return (Locator<T, I>) getTop().createLocator(locatorType);
+  }
+
+  private <T> T newInstance(Class<T> clazz, Class<? super T> base) {
+    Throwable ex;
+    try {
+      return clazz.newInstance();
+    } catch (InstantiationException e) {
+      ex = e;
+    } catch (IllegalAccessException e) {
+      ex = e;
+    }
+    return this.<T> die(ex,
+        "Could not instantiate %s %s. Is it default-instantiable?",
+        base.getSimpleName(), clazz.getCanonicalName());
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/Logging.java b/user/src/com/google/web/bindery/requestfactory/server/Logging.java
new file mode 100644
index 0000000..c213ab1
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/Logging.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.requestfactory.server;
+
+import com.google.gwt.logging.server.RemoteLoggingServiceUtil;
+import com.google.gwt.logging.server.RemoteLoggingServiceUtil.RemoteLoggingException;
+import com.google.gwt.logging.server.StackTraceDeobfuscator;
+import com.google.gwt.user.client.rpc.RpcRequestBuilder;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Server side object that handles log messages sent by
+ * {@link com.google.web.bindery.requestfactory.gwt.client.RequestFactoryLogHandler}.
+ */
+public class Logging {
+
+  private static StackTraceDeobfuscator deobfuscator = new StackTraceDeobfuscator(
+      "");
+
+  /**
+   * Logs a message.
+   * 
+   * @param logRecordJson a json serialized LogRecord, as provided by
+   * {@link com.google.gwt.logging.client.JsonLogRecordClientUtil#logRecordAsJsonObject(LogRecord)}
+   * @throws RemoteLoggingException if logging fails
+   */
+  public static void logMessage(String logRecordJson)
+      throws RemoteLoggingException {
+    /*
+     * if the header does not exist, we pass null, which is handled gracefully
+     * by the deobfuscation code.
+     */
+    HttpServletRequest threadLocalRequest = RequestFactoryServlet.getThreadLocalRequest();
+    String strongName = null;
+    if (threadLocalRequest != null) {
+      // can be null during tests
+      threadLocalRequest.getHeader(RpcRequestBuilder.STRONG_NAME_HEADER);
+    }
+    RemoteLoggingServiceUtil.logOnServer(logRecordJson, strongName,
+        deobfuscator, null);
+  }
+
+  /**
+   * This function is only for server side use which is why it's not in the
+   * LoggingRequest interface.
+   * 
+   * @param dir a directory, specified as a String
+   */
+  public static void setSymbolMapsDirectory(String dir) {
+    deobfuscator.setSymbolMapsDirectory(dir);
+  }
+
+  private String id = "";
+
+  private Integer version = 0;
+
+  /**
+   * Returns the id of this instance.
+   * 
+   * @return a String id
+   * @see #setId(String)
+   */
+  public String getId() {
+    return this.id;
+  }
+
+  /**
+   * Returns the version of this instance.
+   * 
+   * @return an Integer version number
+   * @see #setVersion(Integer)
+   */
+  public Integer getVersion() {
+    return this.version;
+  }
+
+  /**
+   * Sets the id on this instance.
+   * 
+   * @param id a String id
+   * @see #getId()
+   */
+  public void setId(String id) {
+    this.id = id;
+  }
+
+  /**
+   * Sets the version of this instance.
+   * 
+   * @param version an Integer version number
+   * @see #getVersion()
+   */
+  public void setVersion(Integer version) {
+    this.version = version;
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/ReflectiveServiceLayer.java b/user/src/com/google/web/bindery/requestfactory/server/ReflectiveServiceLayer.java
new file mode 100644
index 0000000..3433518
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/ReflectiveServiceLayer.java
@@ -0,0 +1,292 @@
+/*
+ * 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.requestfactory.server;
+
+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;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.Validation;
+import javax.validation.ValidationException;
+import javax.validation.Validator;
+import javax.validation.ValidatorFactory;
+
+/**
+ * Implements all methods that interact with domain objects.
+ */
+final class ReflectiveServiceLayer extends ServiceLayerDecorator {
+  /*
+   * NB: All calls that ReflectiveServiceLayer makes to public APIs inherited
+   * from ServiceLayer should be made to use the instance returned from
+   * getTop().
+   */
+
+  private static final Validator jsr303Validator;
+  private static final Logger log = Logger.getLogger(ServiceLayer.class.getName());
+
+  static {
+    Validator found;
+    try {
+      ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
+      found = validatorFactory.getValidator();
+    } catch (ValidationException e) {
+      log.log(Level.INFO, "Unable to initialize a JSR 303 Bean Validator", e);
+      found = null;
+    }
+    jsr303Validator = found;
+  }
+
+  /**
+   * Linear search, but we want to handle getFoo, isFoo, and hasFoo. The result
+   * of this method will be cached by the ServiceLayerCache.
+   */
+  private static Method getBeanMethod(BeanMethod methodType,
+      Class<?> domainType, String property) {
+    for (Method m : domainType.getMethods()) {
+      if (methodType.matches(m) && property.equals(methodType.inferName(m))) {
+        m.setAccessible(true);
+        return m;
+      }
+    }
+    return null;
+  }
+
+  @Override
+  public <T> T createDomainObject(Class<T> clazz) {
+    Throwable ex;
+    try {
+      Constructor<T> c = clazz.getConstructor();
+      c.setAccessible(true);
+      return c.newInstance();
+    } catch (InstantiationException e) {
+      e.printStackTrace();
+      return this.<T> report("Could not create a new instance of the requested type");
+    } catch (NoSuchMethodException e) {
+      return this.<T> report("The requested type is not default-instantiable");
+    } catch (InvocationTargetException e) {
+      return this.<T> report(e);
+    } catch (IllegalAccessException e) {
+      ex = e;
+    } catch (SecurityException e) {
+      ex = e;
+    } catch (IllegalArgumentException e) {
+      ex = e;
+    }
+    return this.<T> die(ex,
+        "Could not create a new instance of domain type %s",
+        clazz.getCanonicalName());
+  }
+
+  @Override
+  public Method getGetter(Class<?> domainType, String property) {
+    return getBeanMethod(BeanMethod.GET, domainType, property);
+  }
+
+  @Override
+  public Object getId(Object domainObject) {
+    return getTop().getProperty(domainObject, "id");
+  }
+
+  @Override
+  public Class<?> getIdType(Class<?> domainType) {
+    return getFind(domainType).getParameterTypes()[0];
+  }
+
+  @Override
+  public Object getProperty(Object domainObject, String property) {
+    try {
+      Method getter = getTop().getGetter(domainObject.getClass(), property);
+      if (getter == null) {
+        die(null, "Could not determine getter for property %s on type %s",
+            property, domainObject.getClass().getCanonicalName());
+      }
+      Object value = getter.invoke(domainObject);
+      return value;
+    } catch (IllegalAccessException e) {
+      return die(e, "Could not retrieve property %s", property);
+    } catch (InvocationTargetException e) {
+      return report(e);
+    }
+  }
+
+  @Override
+  public Type getRequestReturnType(Method contextMethod) {
+    Class<?> returnClass = contextMethod.getReturnType();
+    if (InstanceRequest.class.isAssignableFrom(returnClass)) {
+      Type[] params = TypeUtils.getParameterization(InstanceRequest.class,
+          contextMethod.getGenericReturnType());
+      assert params.length == 2;
+      return params[1];
+    } else if (Request.class.isAssignableFrom(returnClass)) {
+      Type param = TypeUtils.getSingleParameterization(Request.class,
+          contextMethod.getGenericReturnType());
+      return param;
+    } else {
+      return die(null, "Unknown RequestContext return type %s",
+          returnClass.getCanonicalName());
+    }
+  }
+
+  @Override
+  public Method getSetter(Class<?> domainType, String property) {
+    Method setter = getBeanMethod(BeanMethod.SET, domainType, property);
+    if (setter == null) {
+      setter = getBeanMethod(BeanMethod.SET_BUILDER, domainType, property);
+    }
+    return setter;
+  }
+
+  @Override
+  public Object getVersion(Object domainObject) {
+    return getTop().getProperty(domainObject, "version");
+  }
+
+  @Override
+  public Object invoke(Method domainMethod, Object... args) {
+    Throwable ex;
+    try {
+      domainMethod.setAccessible(true);
+      if (Modifier.isStatic(domainMethod.getModifiers())) {
+        return domainMethod.invoke(null, args);
+      } else {
+        Object[] realArgs = new Object[args.length - 1];
+        System.arraycopy(args, 1, realArgs, 0, realArgs.length);
+        return domainMethod.invoke(args[0], realArgs);
+      }
+    } catch (IllegalArgumentException e) {
+      ex = e;
+    } catch (IllegalAccessException e) {
+      ex = e;
+    } catch (InvocationTargetException e) {
+      return report(e);
+    }
+    return die(ex, "Could not invoke method %s", domainMethod.getName());
+  }
+
+  /**
+   * This implementation attempts to re-load the object from the backing store.
+   */
+  @Override
+  public boolean isLive(Object domainObject) {
+    Object id = getTop().getId(domainObject);
+    return getTop().invoke(getFind(domainObject.getClass()), id) != null;
+  }
+
+  @Override
+  public <T> T loadDomainObject(Class<T> clazz, Object id) {
+    if (id == null) {
+      die(null, "Cannot invoke find method with a null id");
+    }
+    return clazz.cast(getTop().invoke(getFind(clazz), id));
+  }
+
+  @Override
+  public List<Object> loadDomainObjects(List<Class<?>> classes,
+      List<Object> domainIds) {
+    if (classes.size() != domainIds.size()) {
+      die(null,
+          "Size mismatch in paramaters. classes.size() = %d domainIds.size=%d",
+          classes.size(), domainIds.size());
+    }
+    List<Object> toReturn = new ArrayList<Object>(classes.size());
+    Iterator<Class<?>> classIt = classes.iterator();
+    Iterator<Object> idIt = domainIds.iterator();
+    while (classIt.hasNext()) {
+      toReturn.add(getTop().loadDomainObject(classIt.next(), idIt.next()));
+    }
+    return toReturn;
+  }
+
+  @Override
+  public void setProperty(Object domainObject, String property,
+      Class<?> expectedType, Object value) {
+    try {
+      Method setter = getTop().getSetter(domainObject.getClass(), property);
+      if (setter == null) {
+        die(null, "Could not locate setter for property %s in type %s",
+            property, domainObject.getClass().getCanonicalName());
+      }
+      setter.invoke(domainObject, value);
+      return;
+    } catch (IllegalAccessException e) {
+      die(e, "Could not set property %s", property);
+    } catch (InvocationTargetException e) {
+      report(e);
+    }
+  }
+
+  @Override
+  public <T> Set<ConstraintViolation<T>> validate(T domainObject) {
+    if (jsr303Validator != null) {
+      return jsr303Validator.validate(domainObject);
+    }
+    return Collections.emptySet();
+  }
+
+  private Method getFind(Class<?> clazz) {
+    if (clazz == null) {
+      return die(null, "Could not find static method with a single"
+          + " parameter of a key type");
+    }
+    String searchFor = "find" + clazz.getSimpleName();
+    for (Method method : clazz.getMethods()) {
+      if (!Modifier.isStatic(method.getModifiers())) {
+        continue;
+      }
+      if (!searchFor.equals(method.getName())) {
+        continue;
+      }
+      if (method.getParameterTypes().length != 1) {
+        continue;
+      }
+      if (!isKeyType(method.getParameterTypes()[0])) {
+        continue;
+      }
+      return method;
+    }
+    return getFind(clazz.getSuperclass());
+  }
+
+  /**
+   * Returns <code>true</code> if the given class can be used as an id or
+   * version key.
+   */
+  private boolean isKeyType(Class<?> domainClass) {
+    if (ValueCodex.canDecode(domainClass)) {
+      return true;
+    }
+
+    return BaseProxy.class.isAssignableFrom(getTop().resolveClientType(
+        domainClass, BaseProxy.class, true));
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/ReportableException.java b/user/src/com/google/web/bindery/requestfactory/server/ReportableException.java
new file mode 100644
index 0000000..c1e51a5
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/ReportableException.java
@@ -0,0 +1,34 @@
+/*
+ * 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.requestfactory.server;
+
+/**
+ * Encapsulates exceptions that should be thrown back to the client.
+ */
+@SuppressWarnings("serial")
+class ReportableException extends RuntimeException {
+  public ReportableException(String msg) {
+    super(msg);
+  }
+
+  public ReportableException(String msg, Throwable cause) {
+    super(msg, cause);
+  }
+
+  public ReportableException(Throwable cause) {
+    super("Server Error: " + cause.getMessage(), cause);
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidator.java b/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidator.java
new file mode 100644
index 0000000..5e3dfe3
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidator.java
@@ -0,0 +1,1583 @@
+/*
+ * 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.requestfactory.server;
+
+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;
+import com.google.gwt.dev.asm.MethodVisitor;
+import com.google.gwt.dev.asm.Opcodes;
+import com.google.gwt.dev.asm.Type;
+import com.google.gwt.dev.asm.commons.EmptyVisitor;
+import com.google.gwt.dev.asm.commons.Method;
+import com.google.gwt.dev.asm.signature.SignatureReader;
+import com.google.gwt.dev.asm.signature.SignatureVisitor;
+import com.google.gwt.dev.util.Name;
+import com.google.gwt.dev.util.Name.BinaryName;
+import com.google.gwt.dev.util.Name.SourceOrBinaryName;
+import com.google.web.bindery.requestfactory.shared.BaseProxy;
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.InstanceRequest;
+import com.google.web.bindery.requestfactory.shared.ProxyFor;
+import com.google.web.bindery.requestfactory.shared.ProxyForName;
+import com.google.web.bindery.requestfactory.shared.Request;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.RequestFactory;
+import com.google.web.bindery.requestfactory.shared.Service;
+import com.google.web.bindery.requestfactory.shared.ServiceName;
+import com.google.web.bindery.requestfactory.shared.SkipInterfaceValidation;
+import com.google.web.bindery.requestfactory.shared.ValueProxy;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Encapsulates validation logic to determine if a {@link RequestFactory}
+ * interface, its {@link RequestContext}, and associated {@link EntityProxy}
+ * interfaces match their domain counterparts. This implementation examines the
+ * classfiles directly in order to avoid the need to load the types into the
+ * JVM.
+ * <p>
+ * This class is amenable to being used as a unit test:
+ * 
+ * <pre>
+ * public void testRequestFactory() {
+ *   Logger logger = Logger.getLogger("");
+ *   RequestFactoryInterfaceValidator v = new RequestFactoryInterfaceValidator(
+ *     logger, new ClassLoaderLoader(MyRequestContext.class.getClassLoader()));
+ *   v.validateRequestContext(MyRequestContext.class.getName());
+ *   assertFalse(v.isPoisoned());
+ * }
+ * </pre>
+ * This class also has a {@code main} method and can be used as a build-time
+ * tool:
+ * 
+ * <pre>
+ * java -cp gwt-servlet.jar:your-code.jar \
+ *   com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidator \
+ *   com.example.MyRequestFactory
+ * </pre>
+ */
+public class RequestFactoryInterfaceValidator {
+  /**
+   * An implementation of {@link Loader} that uses a {@link ClassLoader} to
+   * retrieve the class files.
+   */
+  public static class ClassLoaderLoader implements Loader {
+    private final ClassLoader loader;
+
+    public ClassLoaderLoader(ClassLoader loader) {
+      this.loader = loader;
+    }
+
+    public boolean exists(String resource) {
+      return loader.getResource(resource) != null;
+    }
+
+    public InputStream getResourceAsStream(String resource) {
+      return loader.getResourceAsStream(resource);
+    }
+  }
+
+  /**
+   * Abstracts the mechanism by which class files are loaded.
+   * 
+   * @see ClassLoaderLoader
+   */
+  public interface Loader {
+    /**
+     * Returns true if the specified resource can be loaded.
+     * 
+     * @param resource a resource name (e.g. <code>com/example/Foo.class</code>)
+     */
+    boolean exists(String resource);
+
+    /**
+     * Returns an InputStream to access the specified resource, or
+     * <code>null</code> if no such resource exists.
+     * 
+     * @param resource a resource name (e.g. <code>com/example/Foo.class</code>)
+     */
+    InputStream getResourceAsStream(String resource);
+  }
+
+  /**
+   * Improves error messages by providing context for the user.
+   * <p>
+   * Visible for testing.
+   */
+  static class ErrorContext {
+    private final Logger logger;
+    private final ErrorContext parent;
+    private Type currentType;
+    private Method currentMethod;
+    private RequestFactoryInterfaceValidator validator;
+
+    public ErrorContext(Logger logger) {
+      this.logger = logger;
+      this.parent = null;
+    }
+
+    protected ErrorContext(ErrorContext parent) {
+      this.logger = parent.logger;
+      this.parent = parent;
+      this.validator = parent.validator;
+    }
+
+    public void poison(String msg, Object... args) {
+      poison();
+      logger.logp(Level.SEVERE, currentType(), currentMethod(),
+          String.format(msg, args));
+      validator.poisoned = true;
+    }
+
+    public void poison(String msg, Throwable t) {
+      poison();
+      logger.logp(Level.SEVERE, currentType(), currentMethod(), msg, t);
+      validator.poisoned = true;
+    }
+
+    public ErrorContext setMethod(Method method) {
+      ErrorContext toReturn = fork();
+      toReturn.currentMethod = method;
+      return toReturn;
+    }
+
+    public ErrorContext setType(Type type) {
+      ErrorContext toReturn = fork();
+      toReturn.currentType = type;
+      return toReturn;
+    }
+
+    public void spam(String msg, Object... args) {
+      logger.logp(Level.FINEST, currentType(), currentMethod(),
+          String.format(msg, args));
+    }
+
+    protected ErrorContext fork() {
+      return new ErrorContext(this);
+    }
+
+    void setValidator(RequestFactoryInterfaceValidator validator) {
+      assert this.validator == null : "Cannot set validator twice";
+      this.validator = validator;
+    }
+
+    private String currentMethod() {
+      if (currentMethod != null) {
+        return print(currentMethod);
+      }
+      if (parent != null) {
+        return parent.currentMethod();
+      }
+      return null;
+    }
+
+    private String currentType() {
+      if (currentType != null) {
+        return print(currentType);
+      }
+      if (parent != null) {
+        return parent.currentType();
+      }
+      return null;
+    }
+
+    /**
+     * Populate {@link RequestFactoryInterfaceValidator#badTypes} with the
+     * current context.
+     */
+    private void poison() {
+      if (currentType != null) {
+        validator.badTypes.add(currentType.getClassName());
+      }
+      if (parent != null) {
+        parent.poison();
+      }
+    }
+  }
+
+  /**
+   * Used internally as a placeholder for types that cannot be mapped to a
+   * domain object.
+   */
+  interface MissingDomainType {
+  }
+
+  /**
+   * Collects the ProxyFor or Service annotation from an EntityProxy or
+   * RequestContext type.
+   */
+  private class DomainMapper extends EmptyVisitor {
+    private final ErrorContext logger;
+    private String domainInternalName;
+    private List<Class<? extends Annotation>> found = new ArrayList<Class<? extends Annotation>>();
+    private String locatorInternalName;
+
+    public DomainMapper(ErrorContext logger) {
+      this.logger = logger;
+      logger.spam("Finding domain mapping annotation");
+    }
+
+    public String getDomainInternalName() {
+      return domainInternalName;
+    }
+
+    public String getLocatorInternalName() {
+      return locatorInternalName;
+    }
+
+    @Override
+    public void visit(int version, int access, String name, String signature,
+        String superName, String[] interfaces) {
+      if ((access & Opcodes.ACC_INTERFACE) == 0) {
+        logger.poison("Type must be an interface");
+      }
+    }
+
+    /**
+     * This method examines one annotation at a time.
+     */
+    @Override
+    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+      // Set to true if the annotation should have class literal values
+      boolean expectClasses = false;
+      // Set to true if the annonation has string values
+      boolean expectNames = false;
+
+      if (desc.equals(Type.getDescriptor(ProxyFor.class))) {
+        expectClasses = true;
+        found.add(ProxyFor.class);
+      } else if (desc.equals(Type.getDescriptor(ProxyForName.class))) {
+        expectNames = true;
+        found.add(ProxyForName.class);
+      } else if (desc.equals(Type.getDescriptor(Service.class))) {
+        expectClasses = true;
+        found.add(Service.class);
+      } else if (desc.equals(Type.getDescriptor(ServiceName.class))) {
+        expectNames = true;
+        found.add(ServiceName.class);
+      }
+
+      if (expectClasses) {
+        return new EmptyVisitor() {
+          @Override
+          public void visit(String name, Object value) {
+            if ("value".equals(name)) {
+              domainInternalName = ((Type) value).getInternalName();
+            } else if ("locator".equals(name)) {
+              locatorInternalName = ((Type) value).getInternalName();
+            }
+          }
+        };
+      }
+
+      if (expectNames) {
+        return new EmptyVisitor() {
+          @Override
+          public void visit(String name, Object value) {
+            String sourceName;
+            boolean locatorRequired = "locator".equals(name);
+            boolean valueRequired = "value".equals(name);
+            if (valueRequired || locatorRequired) {
+              sourceName = (String) value;
+            } else {
+              return;
+            }
+
+            /*
+             * The input is a source name, so we need to convert it to an
+             * internal name. We'll do this by substituting dollar signs for the
+             * last slash in the name until there are no more slashes.
+             */
+            StringBuffer desc = new StringBuffer(sourceName.replace('.', '/'));
+            while (!loader.exists(desc.toString() + ".class")) {
+              logger.spam("Did not find " + desc.toString());
+              int idx = desc.lastIndexOf("/");
+              if (idx == -1) {
+                if (locatorRequired) {
+                  logger.poison("Cannot find locator named %s", value);
+                } else if (valueRequired) {
+                  logger.poison("Cannot find domain type named %s", value);
+                }
+                return;
+              }
+              desc.setCharAt(idx, '$');
+            }
+
+            if (locatorRequired) {
+              locatorInternalName = desc.toString();
+              logger.spam(locatorInternalName);
+            } else if (valueRequired) {
+              domainInternalName = desc.toString();
+              logger.spam(domainInternalName);
+            } else {
+              throw new RuntimeException("Should not reach here");
+            }
+          }
+        };
+      }
+      return null;
+    }
+
+    @Override
+    public void visitEnd() {
+      // Only allow one annotation
+      if (found.size() > 1) {
+        StringBuilder sb = new StringBuilder();
+        for (Class<?> clazz : found) {
+          sb.append(" @").append(clazz.getSimpleName());
+        }
+        logger.poison("Redundant domain mapping annotations present:%s",
+            sb.toString());
+      }
+    }
+  }
+
+  /**
+   * Collects information about domain objects. This visitor is intended to be
+   * iteratively applied to collect all methods in a type hierarchy.
+   */
+  private class MethodsInHierarchyCollector extends EmptyVisitor {
+    private final ErrorContext logger;
+    private Set<RFMethod> methods = new LinkedHashSet<RFMethod>();
+    private Set<String> seen = new HashSet<String>();
+
+    private MethodsInHierarchyCollector(ErrorContext logger) {
+      this.logger = logger;
+    }
+
+    public Set<RFMethod> exec(String internalName) {
+      RequestFactoryInterfaceValidator.this.visit(logger, internalName, this);
+
+      Map<RFMethod, RFMethod> toReturn = new HashMap<RFMethod, RFMethod>();
+      // Return most-derived methods
+      for (RFMethod method : methods) {
+        RFMethod key = new RFMethod(method.getName(), Type.getMethodDescriptor(
+            Type.VOID_TYPE, method.getArgumentTypes()));
+
+        RFMethod compareTo = toReturn.get(key);
+        if (compareTo == null) {
+          toReturn.put(key, method);
+        } else if (isAssignable(logger, compareTo.getReturnType(),
+            method.getReturnType())) {
+          toReturn.put(key, method);
+        }
+      }
+
+      return new HashSet<RFMethod>(toReturn.values());
+    }
+
+    @Override
+    public void visit(int version, int access, String name, String signature,
+        String superName, String[] interfaces) {
+      if (!seen.add(name)) {
+        return;
+      }
+      if (!objectType.getInternalName().equals(superName)) {
+        RequestFactoryInterfaceValidator.this.visit(logger, superName, this);
+      }
+      if (interfaces != null) {
+        for (String intf : interfaces) {
+          RequestFactoryInterfaceValidator.this.visit(logger, intf, this);
+        }
+      }
+    }
+
+    @Override
+    public MethodVisitor visitMethod(int access, String name, String desc,
+        String signature, String[] exceptions) {
+      // Ignore initializers
+      if ("<clinit>".equals(name) || "<init>".equals(name)) {
+        return null;
+      }
+      final RFMethod method = new RFMethod(name, desc);
+      method.setDeclaredStatic((access & Opcodes.ACC_STATIC) != 0);
+      method.setDeclaredSignature(signature);
+      methods.add(method);
+
+      return new EmptyVisitor() {
+        @Override
+        public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+          if (desc.equals(Type.getDescriptor(SkipInterfaceValidation.class))) {
+            method.setValidationSkipped(true);
+          }
+          return null;
+        }
+      };
+    }
+  }
+
+  private static class RFMethod extends Method {
+    private boolean isDeclaredStatic;
+    private String signature;
+    private boolean isValidationSkipped;
+
+    public RFMethod(String name, String desc) {
+      super(name, desc);
+    }
+
+    public String getSignature() {
+      return signature;
+    }
+
+    public boolean isDeclaredStatic() {
+      return isDeclaredStatic;
+    }
+
+    public boolean isValidationSkipped() {
+      return isValidationSkipped;
+    }
+
+    public void setDeclaredSignature(String signature) {
+      this.signature = signature;
+    }
+
+    public void setDeclaredStatic(boolean value) {
+      isDeclaredStatic = value;
+    }
+
+    public void setValidationSkipped(boolean isValidationSkipped) {
+      this.isValidationSkipped = isValidationSkipped;
+    }
+
+    @Override
+    public String toString() {
+      return (isDeclaredStatic ? "static " : "") + super.toString();
+    }
+  }
+
+  private class SupertypeCollector extends EmptyVisitor {
+    private final ErrorContext logger;
+    private final Set<String> seen = new HashSet<String>();
+    private final List<Type> supers = new ArrayList<Type>();
+
+    public SupertypeCollector(ErrorContext logger) {
+      this.logger = logger;
+    }
+
+    public List<Type> exec(Type type) {
+      RequestFactoryInterfaceValidator.this.visit(logger,
+          type.getInternalName(), this);
+      return supers;
+    }
+
+    @Override
+    public void visit(int version, int access, String name, String signature,
+        String superName, String[] interfaces) {
+      if (!seen.add(name)) {
+        return;
+      }
+      supers.add(Type.getObjectType(name));
+      if (!objectType.getInternalName().equals(name)) {
+        RequestFactoryInterfaceValidator.this.visit(logger, superName, this);
+      }
+      if (interfaces != null) {
+        for (String intf : interfaces) {
+          RequestFactoryInterfaceValidator.this.visit(logger, intf, this);
+        }
+      }
+    }
+  }
+
+  /**
+   * Return all types referenced by a method signature.
+   */
+  private static class TypesInSignatureCollector extends SignatureAdapter {
+    private final Set<Type> found = new HashSet<Type>();
+
+    public Type[] getFound() {
+      return found.toArray(new Type[found.size()]);
+    }
+
+    @Override
+    public SignatureVisitor visitArrayType() {
+      return this;
+    }
+
+    @Override
+    public SignatureVisitor visitClassBound() {
+      return this;
+    }
+
+    @Override
+    public void visitClassType(String name) {
+      found.add(Type.getObjectType(name));
+    }
+
+    @Override
+    public SignatureVisitor visitExceptionType() {
+      return this;
+    }
+
+    @Override
+    public SignatureVisitor visitInterface() {
+      return this;
+    }
+
+    @Override
+    public SignatureVisitor visitInterfaceBound() {
+      return this;
+    }
+
+    @Override
+    public SignatureVisitor visitParameterType() {
+      return this;
+    }
+
+    @Override
+    public SignatureVisitor visitReturnType() {
+      return this;
+    }
+
+    @Override
+    public SignatureVisitor visitSuperclass() {
+      return this;
+    }
+
+    @Override
+    public SignatureVisitor visitTypeArgument(char wildcard) {
+      return this;
+    }
+  }
+
+  static final Set<Class<?>> VALUE_TYPES = ValueCodex.getAllValueTypes();
+
+  public static void main(String[] args) {
+    if (args.length == 0) {
+      System.err.println("Usage: java -cp gwt-servlet.jar:your-code.jar "
+          + RequestFactoryInterfaceValidator.class.getCanonicalName()
+          + " com.example.MyRequestFactory");
+      System.exit(1);
+    }
+    RequestFactoryInterfaceValidator validator = new RequestFactoryInterfaceValidator(
+        Logger.getLogger(RequestFactoryInterfaceValidator.class.getName()),
+        new ClassLoaderLoader(Thread.currentThread().getContextClassLoader()));
+    validator.validateRequestFactory(args[0]);
+    System.exit(validator.isPoisoned() ? 1 : 0);
+  }
+
+  static String messageCouldNotFindMethod(Type domainType,
+      List<? extends Method> methods) {
+    StringBuilder sb = new StringBuilder();
+    sb.append(String.format(
+        "Could not find matching method in %s.\nPossible matches:\n",
+        print(domainType)));
+    for (Method domainMethod : methods) {
+      sb.append("  ").append(print(domainMethod)).append("\n");
+    }
+    return sb.toString();
+  }
+
+  private static String print(Method method) {
+    StringBuilder sb = new StringBuilder();
+    sb.append(print(method.getReturnType())).append(" ").append(
+        method.getName()).append("(");
+    for (Type t : method.getArgumentTypes()) {
+      sb.append(print(t)).append(" ");
+    }
+    sb.append(")");
+    return sb.toString();
+  }
+
+  private static String print(Type type) {
+    return SourceOrBinaryName.toSourceName(type.getClassName());
+  }
+
+  /**
+   * A set of binary type names that are known to be bad.
+   */
+  private final Set<String> badTypes = new HashSet<String>();
+  /**
+   * The type {@link BaseProxy}.
+   */
+  private final Type baseProxyIntf = Type.getType(BaseProxy.class);
+  /**
+   * Maps client types (e.g. FooProxy) to server domain types (e.g. Foo).
+   */
+  private final Map<Type, Type> clientToDomainType = new HashMap<Type, Type>();
+  /**
+   * Maps client types (e.g. FooProxy or FooContext) to their locator types
+   * (e.g. FooLocator or FooServiceLocator).
+   */
+  private final Map<Type, Type> clientToLocatorMap = new HashMap<Type, Type>();
+  /**
+   * Maps domain types (e.g Foo) to client proxy types (e.g. FooAProxy,
+   * FooBProxy).
+   */
+  private final Map<Type, List<Type>> domainToClientType = new HashMap<Type, List<Type>>();
+  /**
+   * The type {@link EntityProxy}.
+   */
+  private final Type entityProxyIntf = Type.getType(EntityProxy.class);
+  /**
+   * The type {@link Enum}.
+   */
+  private final Type enumType = Type.getType(Enum.class);
+  /**
+   * A placeholder type for client types that could not be resolved to a domain
+   * type.
+   */
+  private final Type errorType = Type.getType(MissingDomainType.class);
+  /**
+   * The type {@link InstanceRequest}.
+   */
+  private final Type instanceRequestIntf = Type.getType(InstanceRequest.class);
+  private final Loader loader;
+  /**
+   * A cache of all methods defined in a type hierarchy.
+   */
+  private final Map<Type, Set<RFMethod>> methodsInHierarchy = new HashMap<Type, Set<RFMethod>>();
+  /**
+   * The type {@link Object}.
+   */
+  private final Type objectType = Type.getObjectType("java/lang/Object");
+  private final ErrorContext parentLogger;
+  private boolean poisoned;
+  /**
+   * The type {@link Request}.
+   */
+  private final Type requestIntf = Type.getType(Request.class);
+  /**
+   * The type {@link RequestContext}.
+   */
+  private final Type requestContextIntf = Type.getType(RequestContext.class);
+  /**
+   * A map of a type to all types that it could be assigned to.
+   */
+  private final Map<Type, List<Type>> supertypes = new HashMap<Type, List<Type>>();
+
+  /**
+   * The type {@link ValueProxy}.
+   */
+  private final Type valueProxyIntf = Type.getType(ValueProxy.class);
+
+  /**
+   * A set to prevent re-validation of a type.
+   */
+  private final Set<String> validatedTypes = new HashSet<String>();
+
+  /**
+   * Contains vaue types (e.g. Integer).
+   */
+  private final Set<Type> valueTypes = new HashSet<Type>();
+
+  /**
+   * Maps a domain object to the type returned from its getId method.
+   */
+  private final Map<Type, Type> unresolvedKeyTypes = new HashMap<Type, Type>();
+
+  {
+    for (Class<?> clazz : VALUE_TYPES) {
+      valueTypes.add(Type.getType(clazz));
+    }
+  }
+
+  public RequestFactoryInterfaceValidator(Logger logger, Loader loader) {
+    this.parentLogger = new ErrorContext(logger);
+    parentLogger.setValidator(this);
+    this.loader = loader;
+  }
+
+  /**
+   * Visible for testing.
+   */
+  RequestFactoryInterfaceValidator(ErrorContext errorContext, Loader loader) {
+    this.parentLogger = errorContext;
+    this.loader = loader;
+    errorContext.setValidator(this);
+  }
+
+  /**
+   * Reset the poisoned status of the validator so that it may be reused without
+   * destroying cached state.
+   */
+  public void antidote() {
+    poisoned = false;
+  }
+
+  /**
+   * Returns true if validation failed.
+   */
+  public boolean isPoisoned() {
+    return poisoned;
+  }
+
+  /**
+   * This method checks an EntityProxy interface against its peer domain object
+   * to determine if the server code would be able to process a request using
+   * the methods defined in the EntityProxy interface. It does not perform any
+   * checks as to whether or not the EntityProxy could actually be generated by
+   * the Generator.
+   * <p>
+   * This method may be called repeatedly on a single instance of the validator.
+   * Doing so will amortize type calculation costs.
+   * <p>
+   * Checks implemented:
+   * <ul>
+   * <li> <code>binaryName</code> implements EntityProxy</li>
+   * <li><code>binaryName</code> has a {@link ProxyFor} or {@link ProxyForName}
+   * annotation</li>
+   * <li>The domain object has getId() and getVersion() methods</li>
+   * <li>All property methods in the EntityProxy can be mapped onto an
+   * equivalent domain method (unless validation is skipped for the method)</li>
+   * <li>All referenced proxy types are valid</li>
+   * </ul>
+   * 
+   * @param binaryName the binary name (e.g. {@link Class#getName()}) of the
+   *          EntityProxy subtype
+   */
+  public void validateEntityProxy(String binaryName) {
+    validateProxy(binaryName, entityProxyIntf, true);
+  }
+
+  /**
+   * Determine if the specified type implements a proxy interface and apply the
+   * appropriate validations. This can be used as a general-purpose entry method
+   * when writing unit tests.
+   * 
+   * @param binaryName the binary name (e.g. {@link Class#getName()}) of the
+   *          EntityProxy or ValueProxy subtype
+   */
+  public void validateProxy(String binaryName) {
+    /*
+     * Don't call fastFail() here or the proxy may not be validated, since
+     * validateXProxy delegates to validateProxy() which would re-check.
+     */
+    Type proxyType = Type.getObjectType(BinaryName.toInternalName(binaryName));
+    if (isAssignable(parentLogger, entityProxyIntf, proxyType)) {
+      validateEntityProxy(binaryName);
+    } else if (isAssignable(parentLogger, valueProxyIntf, proxyType)) {
+      validateValueProxy(binaryName);
+    } else {
+      parentLogger.poison("%s is neither an %s nor a %s", print(proxyType),
+          print(entityProxyIntf), print(valueProxyIntf));
+    }
+  }
+
+  /**
+   * This method checks a RequestContext interface against its peer domain
+   * domain object to determine if the server code would be able to process a
+   * request using the the methods defined in the RequestContext interface. It
+   * does not perform any checks as to whether or not the RequestContext could
+   * actually be generated by the Generator.
+   * <p>
+   * This method may be called repeatedly on a single instance of the validator.
+   * Doing so will amortize type calculation costs.
+   * <p>
+   * Checks implemented:
+   * <ul>
+   * <li> <code>binaryName</code> implements RequestContext</li>
+   * <li><code>binaryName</code> has a {@link Service} or {@link ServiceName}
+   * annotation</li>
+   * <li>All service methods in the RequestContext can be mapped onto an
+   * equivalent domain method (unless validation is skipped for the method)</li>
+   * <li>All referenced EntityProxy types are valid</li>
+   * </ul>
+   * 
+   * @param binaryName the binary name (e.g. {@link Class#getName()}) of the
+   *          RequestContext subtype
+   * @see #validateEntityProxy(String)
+   */
+  public void validateRequestContext(String binaryName) {
+    if (fastFail(binaryName)) {
+      return;
+    }
+
+    Type requestContextType = Type.getObjectType(BinaryName.toInternalName(binaryName));
+    final ErrorContext logger = parentLogger.setType(requestContextType);
+
+    // Quick sanity check for calling code
+    if (!isAssignable(logger, requestContextIntf, requestContextType)) {
+      logger.poison("%s is not a %s", print(requestContextType),
+          RequestContext.class.getSimpleName());
+      return;
+    }
+
+    Type domainServiceType = getDomainType(logger, requestContextType);
+    if (domainServiceType == errorType) {
+      logger.poison(
+          "The type %s must be annotated with a @%s or @%s annotation",
+          BinaryName.toSourceName(binaryName), Service.class.getSimpleName(),
+          ServiceName.class.getSimpleName());
+      return;
+    }
+
+    for (RFMethod method : getMethodsInHierarchy(logger, requestContextType)) {
+      // Ignore methods in RequestContext itself
+      if (findCompatibleMethod(logger, requestContextIntf, method, false, true,
+          true) != null) {
+        continue;
+      }
+
+      // Check the client method against the domain
+      checkClientMethodInDomain(logger, method, domainServiceType,
+          !clientToLocatorMap.containsKey(requestContextType));
+      maybeCheckReferredProxies(logger, method);
+    }
+
+    checkUnresolvedKeyTypes(logger);
+  }
+
+  /**
+   * This method checks a RequestFactory interface.
+   * <p>
+   * This method may be called repeatedly on a single instance of the validator.
+   * Doing so will amortize type calculation costs. It does not perform any
+   * checks as to whether or not the RequestContext could actually be generated
+   * by the Generator.
+   * <p>
+   * Checks implemented:
+   * <ul>
+   * <li> <code>binaryName</code> implements RequestFactory</li>
+   * <li>All referenced RequestContext types are valid</li>
+   * </ul>
+   * 
+   * @param binaryName the binary name (e.g. {@link Class#getName()}) of the
+   *          RequestContext subtype
+   * @see #validateRequestContext(String)
+   */
+  public void validateRequestFactory(String binaryName) {
+    if (fastFail(binaryName)) {
+      return;
+    }
+
+    Type requestFactoryType = Type.getObjectType(BinaryName.toInternalName(binaryName));
+    ErrorContext logger = parentLogger.setType(requestFactoryType);
+
+    // Quick sanity check for calling code
+    if (!isAssignable(logger, Type.getType(RequestFactory.class),
+        requestFactoryType)) {
+      logger.poison("%s is not a %s", print(requestFactoryType),
+          RequestFactory.class.getSimpleName());
+      return;
+    }
+
+    // Validate each RequestContext method in the RF
+    for (Method contextMethod : getMethodsInHierarchy(logger,
+        requestFactoryType)) {
+      Type returnType = contextMethod.getReturnType();
+      if (isAssignable(logger, requestContextIntf, returnType)) {
+        validateRequestContext(returnType.getClassName());
+      }
+    }
+  }
+
+  /**
+   * This method checks a ValueProxy interface against its peer domain object to
+   * determine if the server code would be able to process a request using the
+   * methods defined in the ValueProxy interface. It does not perform any checks
+   * as to whether or not the ValueProxy could actually be generated by the
+   * Generator.
+   * <p>
+   * This method may be called repeatedly on a single instance of the validator.
+   * Doing so will amortize type calculation costs.
+   * <p>
+   * Checks implemented:
+   * <ul>
+   * <li> <code>binaryName</code> implements ValueProxy</li>
+   * <li><code>binaryName</code> has a {@link ProxyFor} or {@link ProxyForName}
+   * annotation</li>
+   * <li>All property methods in the EntityProxy can be mapped onto an
+   * equivalent domain method (unless validation is skipped for the method)</li>
+   * <li>All referenced proxy types are valid</li>
+   * </ul>
+   * 
+   * @param binaryName the binary name (e.g. {@link Class#getName()}) of the
+   *          EntityProxy subtype
+   */
+  public void validateValueProxy(String binaryName) {
+    validateProxy(binaryName, valueProxyIntf, false);
+  }
+
+  /**
+   * Given the binary name of a domain type, return the BaseProxy type that is
+   * assignable to {@code clientType}. This method allows multiple proxy types
+   * to be assigned to a domain type for use in different contexts (e.g. API
+   * slices). If there are multiple client types mapped to
+   * {@code domainTypeBinaryName} and assignable to {@code clientTypeBinaryName}
+   * , the first matching type will be returned.
+   */
+  String getEntityProxyTypeName(String domainTypeBinaryName,
+      String clientTypeBinaryName) {
+    Type key = Type.getObjectType(BinaryName.toInternalName(domainTypeBinaryName));
+    List<Type> found = domainToClientType.get(key);
+
+    /*
+     * If nothing was found look for proxyable supertypes the domain object can
+     * be upcast to.
+     */
+    if (found == null || found.isEmpty()) {
+      List<Type> types = getSupertypes(parentLogger, key);
+      for (Type type : types) {
+        if (objectType.equals(type)) {
+          break;
+        }
+
+        found = domainToClientType.get(type);
+        if (found != null && !found.isEmpty()) {
+          break;
+        }
+      }
+    }
+
+    if (found == null || found.isEmpty()) {
+      return null;
+    }
+
+    Type typeToReturn = null;
+
+    // Common case
+    if (found.size() == 1) {
+      typeToReturn = found.get(0);
+    } else {
+      // Search for the first assignable type
+      Type assignableTo = Type.getObjectType(BinaryName.toInternalName(clientTypeBinaryName));
+      for (Type t : found) {
+        if (isAssignable(parentLogger, assignableTo, t)) {
+          typeToReturn = t;
+          break;
+        }
+      }
+    }
+
+    return typeToReturn == null ? null : typeToReturn.getClassName();
+  }
+
+  /**
+   * Record the mapping of a domain type to a client type. Proxy types will be
+   * added to {@link #domainToClientType}.
+   */
+  private void addToDomainMap(ErrorContext logger, Type domainType,
+      Type clientType) {
+    clientToDomainType.put(clientType, domainType);
+
+    if (isAssignable(logger, baseProxyIntf, clientType)) {
+      maybeCheckProxyType(logger, clientType);
+      List<Type> list = domainToClientType.get(domainType);
+      if (list == null) {
+        list = new ArrayList<Type>();
+        domainToClientType.put(domainType, list);
+      }
+      list.add(clientType);
+    }
+  }
+
+  /**
+   * Check that a given method RequestContext method declaration can be mapped
+   * to the server's domain type.
+   */
+  private void checkClientMethodInDomain(ErrorContext logger, RFMethod method,
+      Type domainServiceType, boolean requireStaticMethodsForRequestType) {
+    logger = logger.setMethod(method);
+
+    // Create a "translated" method declaration to search for
+    // Request<BlahProxy> foo(int a, BarProxy bar) -> Blah foo(int a, Bar bar);
+    Type returnType = getReturnType(logger, method);
+    Method searchFor = createDomainMethod(logger, new Method(method.getName(),
+        returnType, method.getArgumentTypes()));
+
+    RFMethod found = findCompatibleServiceMethod(logger, domainServiceType,
+        searchFor, !method.isValidationSkipped());
+
+    if (found != null) {
+      boolean isInstance = isAssignable(logger, instanceRequestIntf,
+          method.getReturnType());
+      if (isInstance && found.isDeclaredStatic()) {
+        logger.poison("The method %s is declared to return %s, but the"
+            + " service method is static", method.getName(),
+            InstanceRequest.class.getCanonicalName());
+      } else if (requireStaticMethodsForRequestType && !isInstance
+          && !found.isDeclaredStatic()) {
+        logger.poison("The method %s is declared to return %s, but the"
+            + " service method is not static", method.getName(),
+            Request.class.getCanonicalName());
+      }
+    }
+  }
+
+  /**
+   * Check that the domain object has <code>getId()</code> and
+   * <code>getVersion</code> methods.
+   */
+  private void checkIdAndVersion(ErrorContext logger, Type domainType) {
+    if (objectType.equals(domainType)) {
+      return;
+    }
+    logger = logger.setType(domainType);
+    String findMethodName = "find"
+        + BinaryName.getShortClassName(domainType.getClassName());
+    Type keyType = null;
+    RFMethod findMethod = null;
+
+    boolean foundFind = false;
+    boolean foundId = false;
+    boolean foundVersion = false;
+    for (RFMethod method : getMethodsInHierarchy(logger, domainType)) {
+      if ("getId".equals(method.getName())
+          && method.getArgumentTypes().length == 0) {
+        foundId = true;
+        keyType = method.getReturnType();
+        if (!isResolvedKeyType(logger, keyType)) {
+          unresolvedKeyTypes.put(domainType, keyType);
+        }
+      } else if ("getVersion".equals(method.getName())
+          && method.getArgumentTypes().length == 0) {
+        foundVersion = true;
+        if (!isResolvedKeyType(logger, method.getReturnType())) {
+          unresolvedKeyTypes.put(domainType, method.getReturnType());
+        }
+      } else if (findMethodName.equals(method.getName())
+          && method.getArgumentTypes().length == 1) {
+        foundFind = true;
+        findMethod = method;
+      }
+      if (foundFind && foundId && foundVersion) {
+        break;
+      }
+    }
+    if (!foundId) {
+      logger.poison("There is no getId() method in type %s", print(domainType));
+    }
+    if (!foundVersion) {
+      logger.poison("There is no getVersion() method in type %s",
+          print(domainType));
+    }
+
+    if (foundFind) {
+      if (keyType != null
+          && !isAssignable(logger, findMethod.getArgumentTypes()[0], keyType)) {
+        logger.poison("The key type returned by %s getId()"
+            + " cannot be used as the argument to %s(%s)", print(keyType),
+            findMethod.getName(), print(findMethod.getArgumentTypes()[0]));
+      }
+    } else {
+      logger.poison("There is no %s method in type %s that returns %2$s",
+          findMethodName, print(domainType));
+    }
+  }
+
+  /**
+   * Ensure that the given property method on an EntityProxy exists on the
+   * domain object.
+   */
+  private void checkPropertyMethod(ErrorContext logger,
+      RFMethod clientPropertyMethod, Type domainType) {
+    logger = logger.setMethod(clientPropertyMethod);
+
+    findCompatiblePropertyMethod(logger, domainType,
+        createDomainMethod(logger, clientPropertyMethod),
+        !clientPropertyMethod.isValidationSkipped());
+  }
+
+  private void checkUnresolvedKeyTypes(ErrorContext logger) {
+    unresolvedKeyTypes.values().removeAll(domainToClientType.keySet());
+    if (unresolvedKeyTypes.isEmpty()) {
+      return;
+    }
+
+    for (Map.Entry<Type, Type> type : unresolvedKeyTypes.entrySet()) {
+      logger.setType(type.getKey()).poison(
+          "The domain type %s uses  a non-simple key type (%s)"
+              + " in its getId() or getVersion() method that"
+              + " does not have a proxy mapping.", print(type.getKey()),
+          print(type.getValue()));
+    }
+  }
+
+  /**
+   * Convert a method declaration using client types (e.g. FooProxy) to domain
+   * types (e.g. Foo).
+   */
+  private Method createDomainMethod(ErrorContext logger, Method clientMethod) {
+    Type[] args = clientMethod.getArgumentTypes();
+    for (int i = 0, j = args.length; i < j; i++) {
+      args[i] = getDomainType(logger, args[i]);
+    }
+    Type returnType = getDomainType(logger, clientMethod.getReturnType());
+    return new Method(clientMethod.getName(), returnType, args);
+  }
+
+  /**
+   * Common checks to quickly determine if a type needs to be checked.
+   */
+  private boolean fastFail(String binaryName) {
+    if (!Name.isBinaryName(binaryName)) {
+      parentLogger.poison("%s is not a binary name", binaryName);
+      return true;
+    }
+
+    // Allow the poisoned flag to be reset without losing data
+    if (badTypes.contains(binaryName)) {
+      parentLogger.poison("Type type %s was previously marked as bad",
+          binaryName);
+      return true;
+    }
+
+    // Don't revalidate the same type
+    if (!validatedTypes.add(binaryName)) {
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * Finds a compatible method declaration in <code>domainType</code>'s
+   * hierarchy that is assignment-compatible with the given Method.
+   */
+  private RFMethod findCompatibleMethod(final ErrorContext logger,
+      Type domainType, Method searchFor, boolean mustFind,
+      boolean allowOverloads, boolean boxReturnTypes) {
+    String methodName = searchFor.getName();
+    Type[] clientArgs = searchFor.getArgumentTypes();
+    Type clientReturnType = searchFor.getReturnType();
+    if (boxReturnTypes) {
+      clientReturnType = maybeBoxType(clientReturnType);
+    }
+    // Pull all methods out of the domain type
+    Map<String, List<RFMethod>> domainLookup = new LinkedHashMap<String, List<RFMethod>>();
+    for (RFMethod method : getMethodsInHierarchy(logger, domainType)) {
+      List<RFMethod> list = domainLookup.get(method.getName());
+      if (list == null) {
+        list = new ArrayList<RFMethod>();
+        domainLookup.put(method.getName(), list);
+      }
+      list.add(method);
+    }
+
+    // Find the matching method in the domain object
+    List<RFMethod> methods = domainLookup.get(methodName);
+    if (methods == null) {
+      if (mustFind) {
+        logger.poison("Could not find any methods named %s in %s", methodName,
+            print(domainType));
+      }
+      return null;
+    }
+    if (methods.size() > 1 && !allowOverloads) {
+      StringBuilder sb = new StringBuilder();
+      sb.append(String.format("Method overloads found in type %s named %s:\n",
+          print(domainType), methodName));
+      for (RFMethod method : methods) {
+        sb.append("  ").append(print(method)).append("\n");
+      }
+      logger.poison(sb.toString());
+      return null;
+    }
+
+    // Check each overloaded name
+    for (RFMethod domainMethod : methods) {
+      Type[] domainArgs = domainMethod.getArgumentTypes();
+      Type domainReturnType = domainMethod.getReturnType();
+      if (boxReturnTypes) {
+        /*
+         * When looking for the implementation of a Request<Integer>, we want to
+         * match either int or Integer, so we'll box the domain method's return
+         * type.
+         */
+        domainReturnType = maybeBoxType(domainReturnType);
+      }
+
+      /*
+       * Make sure the client args can be passed into the domain args and the
+       * domain return type into the client return type.
+       */
+      if (isAssignable(logger, domainArgs, clientArgs)
+          && isAssignable(logger, clientReturnType, domainReturnType)) {
+
+        logger.spam("Mapped client method " + print(searchFor) + " to "
+            + print(domainMethod));
+        return domainMethod;
+      }
+    }
+    if (mustFind) {
+      logger.poison(messageCouldNotFindMethod(domainType, methods));
+    }
+    return null;
+  }
+
+  /**
+   * Finds a compatible method declaration in <code>domainType</code>'s
+   * hierarchy that is assignment-compatible with the given Method.
+   */
+  private RFMethod findCompatiblePropertyMethod(final ErrorContext logger,
+      Type domainType, Method searchFor, boolean mustFind) {
+    return findCompatibleMethod(logger, domainType, searchFor, mustFind, false,
+        false);
+  }
+
+  /**
+   * Finds a compatible method declaration in <code>domainType</code>'s
+   * hierarchy that is assignment-compatible with the given Method.
+   */
+  private RFMethod findCompatibleServiceMethod(final ErrorContext logger,
+      Type domainType, Method searchFor, boolean mustFind) {
+    return findCompatibleMethod(logger, domainType, searchFor, mustFind, false,
+        true);
+  }
+
+  /**
+   * This looks like it should be a utility method somewhere else, but I can't
+   * find it.
+   */
+  private Type getBoxedType(Type primitive) {
+    switch (primitive.getSort()) {
+      case Type.BOOLEAN:
+        return Type.getType(Boolean.class);
+      case Type.BYTE:
+        return Type.getType(Byte.class);
+      case Type.CHAR:
+        return Type.getType(Character.class);
+      case Type.DOUBLE:
+        return Type.getType(Double.class);
+      case Type.FLOAT:
+        return Type.getType(Float.class);
+      case Type.INT:
+        return Type.getType(Integer.class);
+      case Type.LONG:
+        return Type.getType(Long.class);
+      case Type.SHORT:
+        return Type.getType(Short.class);
+      case Type.VOID:
+        return Type.getType(Void.class);
+    }
+    throw new RuntimeException(primitive.getDescriptor()
+        + " is not a primitive type");
+  }
+
+  /**
+   * Convert the type used in a client-side EntityProxy or RequestContext
+   * declaration to the equivalent domain type. Value types and supported
+   * collections are a pass-through. EntityProxy types will be resolved to their
+   * domain object type. RequestContext types will be resolved to their service
+   * object.
+   */
+  private Type getDomainType(ErrorContext logger, Type clientType) {
+    Type domainType = clientToDomainType.get(clientType);
+    if (domainType != null) {
+      return domainType;
+    }
+    if (isValueType(logger, clientType) || isCollectionType(logger, clientType)) {
+      domainType = clientType;
+    } else if (entityProxyIntf.equals(clientType)
+        || valueProxyIntf.equals(clientType)) {
+      domainType = objectType;
+    } else {
+      logger = logger.setType(clientType);
+      DomainMapper pv = new DomainMapper(logger);
+      visit(logger, clientType.getInternalName(), pv);
+      if (pv.getDomainInternalName() == null) {
+        logger.poison("%s has no mapping to a domain type (e.g. @%s or @%s)",
+            print(clientType), ProxyFor.class.getSimpleName(),
+            Service.class.getSimpleName());
+        domainType = errorType;
+      } else {
+        domainType = Type.getObjectType(pv.getDomainInternalName());
+      }
+      if (pv.getLocatorInternalName() != null) {
+        Type locatorType = Type.getObjectType(pv.getLocatorInternalName());
+        clientToLocatorMap.put(clientType, locatorType);
+      }
+    }
+    addToDomainMap(logger, domainType, clientType);
+    maybeCheckProxyType(logger, clientType);
+    return domainType;
+  }
+
+  /**
+   * Collect all of the methods defined within a type hierarchy.
+   */
+  private Set<RFMethod> getMethodsInHierarchy(ErrorContext logger,
+      Type domainType) {
+    Set<RFMethod> toReturn = methodsInHierarchy.get(domainType);
+    if (toReturn == null) {
+      logger = logger.setType(domainType);
+      toReturn = new MethodsInHierarchyCollector(logger).exec(domainType.getInternalName());
+      methodsInHierarchy.put(domainType, Collections.unmodifiableSet(toReturn));
+    }
+    return toReturn;
+  }
+
+  /**
+   * Examines a generic RequestContext method declaration and determines the
+   * expected domain return type. This implementation is limited in that it will
+   * not attempt to resolve type bounds since that would essentially require
+   * implementing TypeOracle. In the case where the type bound cannot be
+   * resolved, this method will return Object's type.
+   */
+  private Type getReturnType(ErrorContext logger, RFMethod method) {
+    logger = logger.setMethod(method);
+    final String[] returnType = {objectType.getInternalName()};
+    String signature = method.getSignature();
+
+    final int expectedCount;
+    if (method.getReturnType().equals(instanceRequestIntf)) {
+      expectedCount = 2;
+    } else if (method.getReturnType().equals(requestIntf)) {
+      expectedCount = 1;
+    } else {
+      logger.spam("Punting on " + signature);
+      return Type.getObjectType(returnType[0]);
+    }
+
+    // TODO(bobv): If a class-based TypeOracle is built, use that instead
+    new SignatureReader(signature).accept(new SignatureAdapter() {
+      @Override
+      public SignatureVisitor visitReturnType() {
+        return new SignatureAdapter() {
+          int count;
+
+          @Override
+          public SignatureVisitor visitTypeArgument(char wildcard) {
+            if (++count == expectedCount) {
+              return new SignatureAdapter() {
+                @Override
+                public void visitClassType(String name) {
+                  returnType[0] = name;
+                }
+              };
+            }
+            return super.visitTypeArgument(wildcard);
+          }
+        };
+      }
+    });
+
+    logger.spam("Extracted " + returnType[0]);
+    return Type.getObjectType(returnType[0]);
+  }
+
+  private List<Type> getSupertypes(ErrorContext logger, Type type) {
+    if (type.getSort() != Type.OBJECT) {
+      return Collections.emptyList();
+    }
+    List<Type> toReturn = supertypes.get(type);
+    if (toReturn != null) {
+      return toReturn;
+    }
+
+    logger = logger.setType(type);
+
+    toReturn = new SupertypeCollector(logger).exec(type);
+    supertypes.put(type, Collections.unmodifiableList(toReturn));
+    return toReturn;
+  }
+
+  private boolean isAssignable(ErrorContext logger, Type possibleSupertype,
+      Type possibleSubtype) {
+    // Fast-path for same type
+    if (possibleSupertype.equals(possibleSubtype)) {
+      return true;
+    }
+
+    // Supertype calculation is cached
+    List<Type> allSupertypes = getSupertypes(logger, possibleSubtype);
+    return allSupertypes.contains(possibleSupertype);
+  }
+
+  private boolean isAssignable(ErrorContext logger, Type[] possibleSupertypes,
+      Type[] possibleSubtypes) {
+    // Check the same number of types
+    if (possibleSupertypes.length != possibleSubtypes.length) {
+      return false;
+    }
+    for (int i = 0, j = possibleSupertypes.length; i < j; i++) {
+      if (!isAssignable(logger, possibleSupertypes[i], possibleSubtypes[i])) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  private boolean isCollectionType(
+      @SuppressWarnings("unused") ErrorContext logger, Type type) {
+    // keeping the logger arg just for internal consistency for our small minds
+    return "java/util/List".equals(type.getInternalName())
+        || "java/util/Set".equals(type.getInternalName());
+  }
+
+  /**
+   * Keep in sync with {@code ReflectiveServiceLayer.isKeyType()}.
+   */
+  private boolean isResolvedKeyType(ErrorContext logger, Type type) {
+    if (isValueType(logger, type)) {
+      return true;
+    }
+
+    // We have already seen a mapping for the key type
+    if (domainToClientType.containsKey(type)) {
+      return true;
+    }
+
+    return false;
+  }
+
+  private boolean isValueType(ErrorContext logger, Type type) {
+    if (type.getSort() != Type.OBJECT) {
+      return true;
+    }
+    if (valueTypes.contains(type)) {
+      return true;
+    }
+    logger = logger.setType(type);
+    if (isAssignable(logger, enumType, type)) {
+      return true;
+    }
+    return false;
+  }
+
+  private Type maybeBoxType(Type maybePrimitive) {
+    if (maybePrimitive.getSort() == Type.OBJECT) {
+      return maybePrimitive;
+    }
+    return getBoxedType(maybePrimitive);
+  }
+
+  /**
+   * Examine an array of Types and call {@link #validateEntityProxy(String)} or
+   * {@link #validateValueProxy(String)} if the type is a proxy.
+   */
+  private void maybeCheckProxyType(ErrorContext logger, Type... types) {
+    for (Type type : types) {
+      if (isAssignable(logger, entityProxyIntf, type)) {
+        validateEntityProxy(type.getClassName());
+      } else if (isAssignable(logger, valueProxyIntf, type)) {
+        validateValueProxy(type.getClassName());
+      } else if (isAssignable(logger, baseProxyIntf, type)) {
+        logger.poison(
+            "Invalid type hierarchy for %s. Only types derived from %s or %s may be used.",
+            print(type), print(entityProxyIntf), print(valueProxyIntf));
+      }
+    }
+  }
+
+  /**
+   * Examine the arguments and return value of a method and check any
+   * EntityProxies referred.
+   */
+  private void maybeCheckReferredProxies(ErrorContext logger, RFMethod method) {
+    if (method.getSignature() != null) {
+      TypesInSignatureCollector collector = new TypesInSignatureCollector();
+      SignatureReader reader = new SignatureReader(method.getSignature());
+      reader.accept(collector);
+      maybeCheckProxyType(logger, collector.getFound());
+    } else {
+      Type[] argTypes = method.getArgumentTypes();
+      Type returnType = getReturnType(logger, method);
+
+      // Check EntityProxy args ond return types against the domain
+      maybeCheckProxyType(logger, argTypes);
+      maybeCheckProxyType(logger, returnType);
+    }
+  }
+
+  private void validateProxy(String binaryName, Type expectedType,
+      boolean requireId) {
+    if (fastFail(binaryName)) {
+      return;
+    }
+
+    Type proxyType = Type.getObjectType(BinaryName.toInternalName(binaryName));
+    ErrorContext logger = parentLogger.setType(proxyType);
+
+    // Quick sanity check for calling code
+    if (!isAssignable(logger, expectedType, proxyType)) {
+      parentLogger.poison("%s is not a %s", print(proxyType),
+          print(expectedType));
+      return;
+    }
+
+    // Find the domain type
+    Type domainType = getDomainType(logger, proxyType);
+    if (domainType == errorType) {
+      logger.poison(
+          "The type %s must be annotated with a @%s or @%s annotation",
+          BinaryName.toSourceName(binaryName), ProxyFor.class.getSimpleName(),
+          ProxyForName.class.getSimpleName());
+      return;
+    }
+
+    // Check for getId() and getVersion() in domain if no locator is specified
+    if (requireId) {
+      Type locatorType = clientToLocatorMap.get(proxyType);
+      if (locatorType == null) {
+        checkIdAndVersion(logger, domainType);
+      }
+    }
+
+    // Collect all methods in the client proxy type
+    Set<RFMethod> clientPropertyMethods = getMethodsInHierarchy(logger,
+        proxyType);
+
+    // Find the equivalent domain getter/setter method
+    for (RFMethod clientPropertyMethod : clientPropertyMethods) {
+      // Ignore stableId(). Can't use descriptor due to overrides
+      if ("stableId".equals(clientPropertyMethod.getName())
+          && clientPropertyMethod.getArgumentTypes().length == 0) {
+        continue;
+      }
+      checkPropertyMethod(logger, clientPropertyMethod, domainType);
+      maybeCheckReferredProxies(logger, clientPropertyMethod);
+    }
+  }
+
+  /**
+   * Load the classfile for the given binary name and apply the provided
+   * visitor.
+   * 
+   * @return <code>true</code> if the visitor was successfully visited
+   */
+  private boolean visit(ErrorContext logger, String internalName,
+      ClassVisitor visitor) {
+    assert Name.isInternalName(internalName) : "internalName";
+    logger.spam("Visiting " + internalName);
+    InputStream inputStream = null;
+    try {
+      inputStream = loader.getResourceAsStream(internalName + ".class");
+      if (inputStream == null) {
+        logger.poison("Could not find class file for " + internalName);
+        return false;
+      }
+      ClassReader reader = new ClassReader(inputStream);
+      reader.accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG
+          | ClassReader.SKIP_FRAMES);
+      return true;
+    } catch (IOException e) {
+      logger.poison("Unable to open " + internalName, e);
+    } finally {
+      if (inputStream != null) {
+        try {
+          inputStream.close();
+        } catch (IOException ignored) {
+        }
+      }
+    }
+    return false;
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryJarExtractor.java b/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryJarExtractor.java
new file mode 100644
index 0000000..1395ba8
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryJarExtractor.java
@@ -0,0 +1,877 @@
+/*
+ * 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.requestfactory.server;
+
+import com.google.gwt.dev.asm.AnnotationVisitor;
+import com.google.gwt.dev.asm.Attribute;
+import com.google.gwt.dev.asm.ClassAdapter;
+import com.google.gwt.dev.asm.ClassReader;
+import com.google.gwt.dev.asm.ClassVisitor;
+import com.google.gwt.dev.asm.ClassWriter;
+import com.google.gwt.dev.asm.FieldVisitor;
+import com.google.gwt.dev.asm.Label;
+import com.google.gwt.dev.asm.MethodAdapter;
+import com.google.gwt.dev.asm.MethodVisitor;
+import com.google.gwt.dev.asm.Opcodes;
+import com.google.gwt.dev.asm.Type;
+import com.google.gwt.dev.asm.commons.Method;
+import com.google.gwt.dev.util.Name;
+import com.google.gwt.dev.util.Util;
+import com.google.web.bindery.event.shared.SimpleEventBus;
+import com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidator.ClassLoaderLoader;
+import com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidator.ErrorContext;
+import com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidator.Loader;
+import com.google.web.bindery.requestfactory.shared.BaseProxy;
+import com.google.web.bindery.requestfactory.shared.DefaultProxyStore;
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.EntityProxyChange;
+import com.google.web.bindery.requestfactory.shared.EntityProxyId;
+import com.google.web.bindery.requestfactory.shared.InstanceRequest;
+import com.google.web.bindery.requestfactory.shared.JsonRpcContent;
+import com.google.web.bindery.requestfactory.shared.JsonRpcProxy;
+import com.google.web.bindery.requestfactory.shared.JsonRpcService;
+import com.google.web.bindery.requestfactory.shared.JsonRpcWireName;
+import com.google.web.bindery.requestfactory.shared.Locator;
+import com.google.web.bindery.requestfactory.shared.LoggingRequest;
+import com.google.web.bindery.requestfactory.shared.ProxyFor;
+import com.google.web.bindery.requestfactory.shared.ProxyForName;
+import com.google.web.bindery.requestfactory.shared.ProxySerializer;
+import com.google.web.bindery.requestfactory.shared.ProxyStore;
+import com.google.web.bindery.requestfactory.shared.Receiver;
+import com.google.web.bindery.requestfactory.shared.Request;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.RequestFactory;
+import com.google.web.bindery.requestfactory.shared.RequestTransport;
+import com.google.web.bindery.requestfactory.shared.ServerFailure;
+import com.google.web.bindery.requestfactory.shared.Service;
+import com.google.web.bindery.requestfactory.shared.ServiceLocator;
+import com.google.web.bindery.requestfactory.shared.ServiceName;
+import com.google.web.bindery.requestfactory.shared.ValueProxy;
+import com.google.web.bindery.requestfactory.shared.Violation;
+import com.google.web.bindery.requestfactory.shared.WriteOperation;
+import com.google.web.bindery.requestfactory.vm.RequestFactorySource;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentSkipListSet;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+import java.util.logging.Logger;
+import java.util.zip.ZipEntry;
+
+/**
+ * Used to extract RequestFactory client jars from {@code gwt-user.jar}.
+ */
+public class RequestFactoryJarExtractor {
+  /*
+   * The FooProcessor types are ASM visitors that traverse the bytecode, calling
+   * one of the various processFoo() methods. The visitors will also update the
+   * bytecode with the rebased type names that are returned from the
+   * processFoo() methods.
+   */
+
+  /**
+   * Describes a way to emit the contents of a classpath, typically into a JAR
+   * or filesystem directory.
+   */
+  public interface Emitter {
+    void close() throws IOException;
+
+    void emit(String path, InputStream contents) throws IOException;
+  }
+
+  /**
+   * An Emitter implementation that creates a jar file.
+   */
+  public static class JarEmitter implements Emitter {
+    private int rawByteSize;
+    private final JarOutputStream out;
+
+    public JarEmitter(File outFile) throws IOException {
+      Manifest m = new Manifest();
+      m.getMainAttributes().putValue("Created-By",
+          RequestFactoryJarExtractor.class.getCanonicalName());
+      m.getMainAttributes();
+      out = new JarOutputStream(new FileOutputStream(outFile), m);
+    }
+
+    public void close() throws IOException {
+      out.close();
+    }
+
+    public void emit(String path, InputStream contents) throws IOException {
+      ZipEntry entry = new ZipEntry(path);
+      out.putNextEntry(entry);
+      byte[] bytes = new byte[4096];
+      int read;
+      for (;;) {
+        read = contents.read(bytes);
+        if (read == -1) {
+          break;
+        }
+        rawByteSize += read;
+        out.write(bytes, 0, read);
+      }
+      out.closeEntry();
+    }
+  }
+
+  /**
+   * Controls what is emitted by the tool.
+   */
+  public enum Mode {
+    BOTH(true, true) {
+      @Override
+      protected boolean matches(String target) {
+        return target.endsWith(CODE_AND_SOURCE);
+      }
+    },
+    SOURCE(false, true) {
+      @Override
+      protected boolean matches(String target) {
+        return target.endsWith(SOURCE_ONLY);
+      }
+    },
+    // Order is important, must be last
+    CLASSES(true, false) {
+      @Override
+      protected boolean matches(String target) {
+        return true;
+      }
+    };
+
+    public static Mode match(String target) {
+      for (Mode mode : Mode.values()) {
+        if (mode.matches(target)) {
+          return mode;
+        }
+      }
+      return null;
+    }
+
+    private final boolean emitClasses;
+    private final boolean emitSource;
+
+    private Mode(boolean emitClasses, boolean emitSource) {
+      this.emitClasses = emitClasses;
+      this.emitSource = emitSource;
+    }
+
+    public boolean isEmitClasses() {
+      return emitClasses;
+    }
+
+    public boolean isEmitSource() {
+      return emitSource;
+    }
+
+    protected abstract boolean matches(String target);
+  }
+
+  private class AnnotationProcessor implements AnnotationVisitor {
+    private final String sourceType;
+    private final AnnotationVisitor av;
+
+    public AnnotationProcessor(String sourceType, AnnotationVisitor av) {
+      this.sourceType = sourceType;
+      this.av = av;
+    }
+
+    public void visit(String name, Object value) {
+      value = processConstant(sourceType, value);
+      av.visit(name, value);
+    }
+
+    public AnnotationVisitor visitAnnotation(String name, String desc) {
+      desc = processDescriptor(sourceType, desc);
+      return new AnnotationProcessor(desc, av.visitAnnotation(name, desc));
+    }
+
+    public AnnotationVisitor visitArray(String name) {
+      return new AnnotationProcessor(name, av.visitArray(name));
+    }
+
+    public void visitEnd() {
+      av.visitEnd();
+    }
+
+    public void visitEnum(String name, String desc, String value) {
+      desc = processDescriptor(sourceType, desc);
+      av.visitEnum(name, desc, value);
+    }
+  }
+
+  private class ClassProcessor extends ClassAdapter {
+    private State state;
+    private String sourceType;
+
+    public ClassProcessor(String sourceType, ClassVisitor cv, State state) {
+      super(cv);
+      this.sourceType = sourceType;
+      this.state = state;
+    }
+
+    @Override
+    public void visit(int version, int access, String name, String signature,
+        String superName, String[] interfaces) {
+      name = processInternalName(sourceType, name);
+      superName = processInternalName(sourceType, superName);
+      if (interfaces != null) {
+        for (int i = 0, j = interfaces.length; i < j; i++) {
+          interfaces[i] = processInternalName(sourceType, interfaces[i]);
+        }
+      }
+      super.visit(version, access, name, signature, superName, interfaces);
+    }
+
+    @Override
+    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+      desc = processDescriptor(sourceType, desc);
+      return new AnnotationProcessor(sourceType, super.visitAnnotation(desc, visible));
+    }
+
+    @Override
+    public FieldVisitor visitField(int access, String name, String desc,
+        String signature, Object value) {
+      desc = processDescriptor(sourceType, desc);
+      return new FieldProcessor(sourceType, super.visitField(access, name, desc, signature,
+          value));
+    }
+
+    @Override
+    public void visitInnerClass(String name, String outerName,
+        String innerName, int access) {
+      name = processInternalName(sourceType, name);
+      outerName = processInternalName(sourceType, outerName);
+      super.visitInnerClass(name, outerName, innerName, access);
+    }
+
+    @Override
+    public MethodVisitor visitMethod(int access, String name, String desc,
+        String signature, String[] exceptions) {
+      Method method = processMethod(sourceType, name, desc);
+      desc = method.getDescriptor();
+      if (exceptions != null) {
+        for (int i = 0, j = exceptions.length; i < j; i++) {
+          exceptions[i] = processInternalName(sourceType, exceptions[i]);
+        }
+      }
+      MethodVisitor mv = super.visitMethod(access, name, desc, signature,
+          exceptions);
+      if (mv != null) {
+        mv = new MethodProcessor(sourceType, mv);
+      }
+      return mv;
+    }
+
+    @Override
+    public void visitOuterClass(String owner, String name, String desc) {
+      owner = processInternalName(sourceType, owner);
+      if (desc != null) {
+        desc = processMethod(sourceType, name, desc).getDescriptor();
+      }
+      super.visitOuterClass(owner, name, desc);
+    }
+
+    @Override
+    public void visitSource(String source, String debug) {
+      if (source != null) {
+        state.source = source;
+      }
+      super.visitSource(source, debug);
+    }
+  }
+
+  /**
+   * A unit of work to write one class and its source file into the Emitter.
+   */
+  private class EmitOneType implements Callable<Void> {
+    private final State state;
+
+    /**
+     * @param state
+     */
+    private EmitOneType(State state) {
+      this.state = state;
+    }
+
+    public Void call() throws Exception {
+      if (mode.isEmitClasses()) {
+        String fileName = state.type.getInternalName();
+        if (fileName == null) {
+          System.err.println("Got null filename from " + state.type);
+          return null;
+        }
+        fileName += ".class";
+        emitter.emit(fileName, state.contents);
+      }
+      if (mode.isEmitSource()) {
+        String sourcePath = getPackagePath(state.originalType) + state.source;
+        String destPath = getPackagePath(state.type) + state.source;
+        if (sources.add(sourcePath) && loader.exists(sourcePath)) {
+          String contents = Util.readStreamAsString(loader.getResourceAsStream(sourcePath));
+          emitter.emit(destPath,
+              new ByteArrayInputStream(Util.getBytes(contents)));
+        }
+      }
+      return null;
+    }
+  }
+
+  // There is no FieldAdapter type
+  private class FieldProcessor implements FieldVisitor {
+    private final String sourceType;
+    private final FieldVisitor fv;
+
+    public FieldProcessor(String sourceType, FieldVisitor fv) {
+      this.sourceType = sourceType;
+      this.fv = fv;
+    }
+
+    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+      return new AnnotationProcessor(sourceType, fv.visitAnnotation(desc, visible));
+    }
+
+    public void visitAttribute(Attribute attr) {
+      fv.visitAttribute(attr);
+    }
+
+    public void visitEnd() {
+      fv.visitEnd();
+    }
+  }
+
+  private class MethodProcessor extends MethodAdapter {
+    private final String sourceType;
+    
+    public MethodProcessor(String sourceType, MethodVisitor mv) {
+      super(mv);
+      this.sourceType = sourceType;
+    }
+
+    @Override
+    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+      desc = processDescriptor(sourceType, desc);
+      return super.visitAnnotation(desc, visible);
+    }
+
+    @Override
+    public AnnotationVisitor visitAnnotationDefault() {
+      return new AnnotationProcessor(sourceType, super.visitAnnotationDefault());
+    }
+
+    @Override
+    public void visitFieldInsn(int opcode, String owner, String name,
+        String desc) {
+      owner = processInternalName(sourceType, owner);
+      desc = processDescriptor(sourceType, desc);
+      super.visitFieldInsn(opcode, owner, name, desc);
+    }
+
+    @Override
+    public void visitFrame(int type, int nLocal, Object[] local, int nStack,
+        Object[] stack) {
+      for (int i = 0, j = local.length; i < j; i++) {
+        if (local[i] instanceof String) {
+          local[i] = processInternalName(sourceType, (String) local[i]);
+        }
+      }
+      for (int i = 0, j = stack.length; i < j; i++) {
+        if (stack[i] instanceof String) {
+          stack[i] = processInternalName(sourceType, (String) stack[i]);
+        }
+      }
+      super.visitFrame(type, nLocal, local, nStack, stack);
+    }
+
+    @Override
+    public void visitLdcInsn(Object cst) {
+      cst = processConstant(sourceType, cst);
+      super.visitLdcInsn(cst);
+    }
+
+    @Override
+    public void visitLocalVariable(String name, String desc, String signature,
+        Label start, Label end, int index) {
+      desc = processDescriptor(sourceType, desc);
+      super.visitLocalVariable(name, desc, signature, start, end, index);
+    }
+
+    @Override
+    public void visitMethodInsn(int opcode, String owner, String name,
+        String desc) {
+      owner = processInternalName(sourceType, owner);
+      desc = processMethod(sourceType, name, desc).getDescriptor();
+      super.visitMethodInsn(opcode, owner, name, desc);
+    }
+
+    @Override
+    public void visitMultiANewArrayInsn(String desc, int dims) {
+      desc = processDescriptor(sourceType, desc);
+      super.visitMultiANewArrayInsn(desc, dims);
+    }
+
+    @Override
+    public AnnotationVisitor visitParameterAnnotation(int parameter,
+        String desc, boolean visible) {
+      desc = processDescriptor(sourceType, desc);
+      return super.visitParameterAnnotation(parameter, desc, visible);
+    }
+
+    @Override
+    public void visitTryCatchBlock(Label start, Label end, Label handler,
+        String type) {
+      type = processInternalName(sourceType, type);
+      super.visitTryCatchBlock(start, end, handler, type);
+    }
+
+    @Override
+    public void visitTypeInsn(int opcode, String type) {
+      type = processInternalName(sourceType, type);
+      super.visitTypeInsn(opcode, type);
+    }
+  }
+
+  /**
+   * Replaces native methods with stub implementations that throw an exception.
+   * This allows any dangling GWT types to be loaded by a JVM without triggering
+   * an {@link UnsatisfiedLinkError}.
+   */
+  private class NativeMethodDefanger extends ClassAdapter {
+    public NativeMethodDefanger(ClassVisitor cv) {
+      super(cv);
+    }
+
+    @Override
+    public MethodVisitor visitMethod(int access, String name, String desc,
+        String signature, String[] exceptions) {
+      if ((access & Opcodes.ACC_NATIVE) != 0) {
+        MethodVisitor mv = super.visitMethod(access & ~Opcodes.ACC_NATIVE,
+            name, desc, signature, exceptions);
+        if (mv != null) {
+          mv.visitCode();
+          String exceptionName = Type.getInternalName(RuntimeException.class);
+          // <empty>
+          mv.visitTypeInsn(Opcodes.NEW, exceptionName);
+          // obj
+          mv.visitInsn(Opcodes.DUP);
+          // obj, obj
+          mv.visitLdcInsn(NATIVE_METHOD_ERROR);
+          // obj, obj, string
+          mv.visitMethodInsn(Opcodes.INVOKESPECIAL, exceptionName, "<init>",
+              "(Ljava/lang/String;)V");
+          // obj
+          mv.visitInsn(Opcodes.ATHROW);
+
+          // Count argument slots - long and double arguments each take up 2 slots
+          int numSlots = 0;
+          for (Type t : Type.getArgumentTypes(desc)) {
+            numSlots += t.getSize();
+          }
+          if ((access & Opcodes.ACC_STATIC) == 0) {
+            // Add one for 'this' reference
+            numSlots++;
+          }
+          mv.visitMaxs(3, numSlots);
+          mv.visitEnd();
+        }
+        return null;
+      } else {
+        return super.visitMethod(access, name, desc, signature, exceptions);
+      }
+    }
+  }
+
+  /**
+   * This is the main bytecode-processing entry point. It will read in one
+   * classfile and produce a mutated copy. Any referenced types will be enqueued
+   * via {@link RequestFactoryJarExtractor#processType(String, Type)}.
+   */
+  private class ProcessOneType implements Callable<State> {
+
+    private final State state;
+    private final String typeName;
+
+    public ProcessOneType(Type type) {
+      state = new State(type);
+      typeName = type.getClassName();
+    }
+
+    public State call() {
+      ClassWriter writer = new ClassWriter(0);
+      ClassVisitor cv = writer;
+      cv = new ClassProcessor(typeName, cv, state);
+      cv = new NativeMethodDefanger(cv);
+      visit(logger.setType(state.type), loader, state.type.getInternalName(),
+          cv);
+      state.contents = new ByteArrayInputStream(writer.toByteArray());
+      assert seen.containsKey(state.originalType) : "No type for "
+          + state.type.getClassName();
+      state.type = seen.get(state.originalType);
+
+      emit(state);
+      return state;
+    }
+  }
+
+  /**
+   * Metadata about a single type.
+   */
+  private static class State {
+    boolean containsNativeMethods;
+    /**
+     * Will contain the data to be written to disk, possibly mutated class data.
+     */
+    InputStream contents;
+    String source;
+    /**
+     * The possibly rebased type name.
+     */
+    Type type;
+    final Type originalType;
+
+    public State(Type type) {
+      this.originalType = this.type = type;
+    }
+
+    @Override
+    public String toString() {
+      StringBuilder sb = new StringBuilder();
+      sb.append(type.getInternalName());
+      if (containsNativeMethods) {
+        sb.append(" NATIVE");
+      }
+      if (source != null) {
+        sb.append(" ").append(source);
+      }
+      return sb.toString();
+    }
+  }
+
+  /**
+   * If true, print a trace of dependencies to System.out.
+   */
+  private static final boolean VERBOSE = false;
+
+  /**
+   * 
+   */
+  private static final String CODE_AND_SOURCE = "+src";
+
+  /**
+   * 
+   */
+  private static final String SOURCE_ONLY = "-src";
+
+  private static final String NATIVE_METHOD_ERROR = "Cannot call native method";
+
+  /**
+   * A map of target names to the types that target should use as a base.
+   */
+  private static final Map<String, List<Class<?>>> SEEDS = new LinkedHashMap<String, List<Class<?>>>();
+
+  /**
+   * Server public API classes and interfaces.
+   */
+  private static final Class<?>[] SERVER_CLASSES = {
+      DefaultExceptionHandler.class, ExceptionHandler.class, Logging.class, LoggingRequest.class,
+      RequestFactoryServlet.class, ServiceLayer.class, ServiceLayerDecorator.class,
+      SimpleRequestProcessor.class
+  };
+
+  /**
+   * Shared public API classes and interfaces.
+   */
+  private static final Class<?>[] SHARED_CLASSES = {
+      BaseProxy.class, DefaultProxyStore.class, EntityProxy.class, EntityProxyChange.class,
+      EntityProxyId.class, InstanceRequest.class, JsonRpcContent.class, JsonRpcProxy.class,
+      JsonRpcService.class, JsonRpcWireName.class, Locator.class, ProxyFor.class,
+      ProxyForName.class, ProxySerializer.class, ProxyStore.class, Receiver.class, Request.class,
+      RequestContext.class, RequestFactory.class, RequestTransport.class, ServerFailure.class,
+      Service.class, ServiceLocator.class, ServiceName.class, ValueProxy.class, Violation.class,
+      WriteOperation.class, RequestFactorySource.class, SimpleEventBus.class
+  };
+
+  /**
+   * Maximum number of threads to use to run the Extractor.
+   */
+  private static final int MAX_THREADS = 4;
+  
+  static {
+    List<Class<?>> sharedClasses = Arrays.<Class<?>> asList(SHARED_CLASSES);
+
+    List<Class<?>> clientClasses = new ArrayList<Class<?>>();
+    clientClasses.addAll(sharedClasses);
+
+    List<Class<?>> serverClasses = new ArrayList<Class<?>>();
+    serverClasses.addAll(Arrays.<Class<?>> asList(SERVER_CLASSES));
+    serverClasses.addAll(sharedClasses);
+
+    SEEDS.put("client", Collections.unmodifiableList(clientClasses));
+    SEEDS.put("server", Collections.unmodifiableList(serverClasses));
+
+    Set<Class<?>> all = new LinkedHashSet<Class<?>>();
+    for (List<Class<?>> value : SEEDS.values()) {
+      all.addAll(value);
+    }
+    SEEDS.put("all", Collections.unmodifiableList(new ArrayList<Class<?>>(all)));
+
+    for (String target : new ArrayList<String>(SEEDS.keySet())) {
+      SEEDS.put(target + SOURCE_ONLY, SEEDS.get(target));
+      SEEDS.put(target + CODE_AND_SOURCE, SEEDS.get(target));
+    }
+
+    /*
+     * Allows the rebased package to be tested. This is done with a by-name
+     * lookup, since the gwt-user code is compiled separately from its tests.
+     */
+    try {
+      SEEDS.put(
+          "test" + CODE_AND_SOURCE,
+          Collections.unmodifiableList(Arrays.<Class<?>> asList(
+              Class.forName("com.google.web.bindery.requestfactory.vm.RequestFactoryJreSuite"),
+              Class.forName("com.google.web.bindery.requestfactory.server.SimpleBar"))));
+    } catch (ClassNotFoundException ignored) {
+    }
+  }
+
+  public static void main(String[] args) throws IOException {
+    if (args.length < 2) {
+      System.err.println("Usage: java -cp gwt-dev.jar:gwt-user.jar:json.jar"
+          + RequestFactoryJarExtractor.class.getCanonicalName()
+          + " <target-name> outfile.jar");
+      System.err.println("Valid targets:");
+      for (String target : SEEDS.keySet()) {
+        System.err.println("  " + target);
+      }
+      System.exit(1);
+    }
+    String target = args[0];
+    List<Class<?>> seeds = SEEDS.get(target);
+    if (seeds == null) {
+      System.err.println("Unknown target: " + target);
+      System.exit(1);
+    }
+    Mode mode = Mode.match(target);
+    Logger errorContext = Logger.getLogger(RequestFactoryJarExtractor.class.getName());
+    ClassLoaderLoader classLoader = new ClassLoaderLoader(
+        Thread.currentThread().getContextClassLoader());
+    JarEmitter jarEmitter = new JarEmitter(new File(args[1]));
+    RequestFactoryJarExtractor extractor = new RequestFactoryJarExtractor(
+        errorContext, classLoader, jarEmitter, seeds, mode);
+    extractor.run();
+    System.exit(extractor.isExecutionFailed() ? 1 : 0);
+  }
+
+  /**
+   * Given a Type, return a path-prefix based on the type's package.
+   */
+  private static String getPackagePath(Type t) {
+    String name = t.getInternalName();
+    return name.substring(0, name.lastIndexOf('/') + 1);
+  }
+
+  /**
+   * Load the classfile for the given binary name and apply the provided
+   * visitor.
+   * 
+   * @return <code>true</code> if the visitor was successfully visited
+   */
+  private static boolean visit(ErrorContext logger, Loader loader,
+      String internalName, ClassVisitor visitor) {
+    assert Name.isInternalName(internalName) : "internalName";
+    logger.spam("Visiting " + internalName);
+    InputStream inputStream = null;
+    try {
+      inputStream = loader.getResourceAsStream(internalName + ".class");
+      if (inputStream == null) {
+        System.err.println("Could not find class file for " + internalName);
+        logger.poison("Could not find class file for " + internalName);
+        return false;
+      }
+      ClassReader reader = new ClassReader(inputStream);
+      reader.accept(visitor, 0);
+      return true;
+    } catch (IOException e) {
+      logger.poison("Unable to open " + internalName, e);
+    } finally {
+      if (inputStream != null) {
+        try {
+          inputStream.close();
+        } catch (IOException ignored) {
+        }
+      }
+    }
+    return false;
+  }
+
+  private boolean executionFailed = false;
+  private final Emitter emitter;
+  private final ExecutorService ex;
+  private final BlockingQueue<Future<?>> inProcess = new LinkedBlockingQueue<Future<?>>();
+  private final ErrorContext logger;
+  private final Loader loader;
+  private final Mode mode;
+  private final List<Class<?>> seeds;
+  private final Map<Type, Type> seen = new ConcurrentHashMap<Type, Type>();
+  private final Set<String> sources = new ConcurrentSkipListSet<String>();
+  private final ExecutorService writerService;
+
+  public RequestFactoryJarExtractor(Logger logger, Loader loader,
+      Emitter emitter, List<Class<?>> seeds, Mode mode) {
+    this.logger = new ErrorContext(logger);
+    this.loader = loader;
+    this.emitter = emitter;
+    this.seeds = seeds;
+    this.mode = mode;
+
+    int numThreads = Math.min(MAX_THREADS, Runtime.getRuntime().availableProcessors());
+    ex = Executors.newFixedThreadPool(numThreads);
+    writerService = Executors.newSingleThreadExecutor();
+  }
+
+  /**
+   * Blocks until all work has been finished.
+   */
+  public void run() throws IOException {
+    for (Class<?> seed : seeds) {
+      processType("seeds", Type.getType(seed));
+    }
+    // Wait for all tasks to be completed
+    while (!inProcess.isEmpty()) {
+      try {
+        Future<?> task = inProcess.take();
+        task.get();
+      } catch (InterruptedException retry) {
+      } catch (ExecutionException e) {
+        e.getCause().printStackTrace();
+        executionFailed  = true;
+      }
+    }
+    emitter.close();
+  }
+
+  /**
+   * Write one type into the output.
+   */
+  private void emit(final State state) {
+    inProcess.add(writerService.submit(new EmitOneType(state)));
+  }
+
+  private boolean isExecutionFailed() {
+    return executionFailed;
+  }
+
+  /**
+   * Look at constant values from the bytecode, processing referenced types.
+   */
+  private Object processConstant(String sourceType, Object value) {
+    if (value instanceof Type) {
+      value = processType(sourceType, (Type) value);
+    }
+    return value;
+  }
+
+  /**
+   * Process the type represented by the descriptor, possibly returning a
+   * rebased descriptor.
+   */
+  private String processDescriptor(String sourceType, String desc) {
+    if (desc == null) {
+      return null;
+    }
+    return processType(sourceType, Type.getType(desc)).getDescriptor();
+  }
+
+  /**
+   * Process the type represented by the name, possibly returning a rebased
+   * name.
+   */
+  private String processInternalName(String sourceType, String internalName) {
+    if (internalName == null) {
+      return null;
+    }
+    return processType(sourceType, Type.getObjectType(internalName)).getInternalName();
+  }
+
+  /**
+   * Produce a rebased method declaration, also visiting referenced types.
+   */
+  private Method processMethod(String sourceType, String name, String desc) {
+    Method method = new Method(name, desc);
+    Type[] argumentTypes = method.getArgumentTypes();
+    for (int i = 0, j = argumentTypes.length; i < j; i++) {
+      argumentTypes[i] = processType(sourceType, argumentTypes[i]);
+    }
+    method = new Method(name, processType(sourceType, method.getReturnType()),
+        argumentTypes);
+    return method;
+  }
+
+  /**
+   * Process a type, possibly returning a rebased type.
+   * @param sourceType TODO
+   */
+  private Type processType(String sourceType, Type type) {
+    Type toReturn;
+    synchronized (seen) {
+      toReturn = seen.get(type);
+      if (toReturn != null) {
+        return toReturn;
+      }
+      toReturn = Type.getType(type.getDescriptor());
+      seen.put(type, toReturn);
+    }
+    int sort = type.getSort();
+    if (sort != Type.OBJECT && sort != Type.ARRAY) {
+      return toReturn;
+    }
+    if (sort == Type.ARRAY) {
+      processType(sourceType, type.getElementType());
+      return toReturn;
+    }
+    assert type.getInternalName().charAt(0) != 'L';
+    if (type.getInternalName().startsWith("java/")
+        || type.getInternalName().startsWith("javax/")) {
+      return toReturn;
+    }
+    if (VERBOSE) {
+      System.out.println(sourceType + " -> " + type.getClassName());
+    }
+    Future<State> future = ex.submit(new ProcessOneType(type));
+    inProcess.add(future);
+    return toReturn;
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryServlet.java b/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryServlet.java
new file mode 100644
index 0000000..9d45633
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryServlet.java
@@ -0,0 +1,145 @@
+/*
+ * 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.requestfactory.server;
+
+import com.google.gwt.user.server.rpc.RPCServletUtils;
+import com.google.web.bindery.requestfactory.shared.RequestFactory;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Handles GWT RequestFactory JSON requests.
+ */
+@SuppressWarnings("serial")
+public class RequestFactoryServlet extends HttpServlet {
+
+  private static final boolean DUMP_PAYLOAD = Boolean.getBoolean("gwt.rpc.dumpPayload");
+  private static final String JSON_CHARSET = "UTF-8";
+  private static final String JSON_CONTENT_TYPE = "application/json";
+  private static final Logger log = Logger.getLogger(RequestFactoryServlet.class.getCanonicalName());
+
+  /**
+   * These ThreadLocals are used to allow service objects to obtain access to
+   * the HTTP transaction.
+   */
+  private static final ThreadLocal<HttpServletRequest> perThreadRequest = new ThreadLocal<HttpServletRequest>();
+  private static final ThreadLocal<HttpServletResponse> perThreadResponse = new ThreadLocal<HttpServletResponse>();
+
+  /**
+   * Returns the thread-local {@link HttpServletRequest}.
+   * 
+   * @return an {@link HttpServletRequest} instance
+   */
+  public static HttpServletRequest getThreadLocalRequest() {
+    return perThreadRequest.get();
+  }
+
+  /**
+   * Returns the thread-local {@link HttpServletResponse}.
+   * 
+   * @return an {@link HttpServletResponse} instance
+   */
+  public static HttpServletResponse getThreadLocalResponse() {
+    return perThreadResponse.get();
+  }
+
+  private final SimpleRequestProcessor processor;
+
+  /**
+   * Constructs a new {@link RequestFactoryServlet} with a
+   * {@code DefaultExceptionHandler}.
+   */
+  public RequestFactoryServlet() {
+    this(new DefaultExceptionHandler());
+  }
+
+  /**
+   * Use this constructor in subclasses to provide a custom
+   * {@link ExceptionHandler}.
+   * 
+   * @param exceptionHandler an {@link ExceptionHandler} instance
+   * @param serviceDecorators an array of ServiceLayerDecorators that change how
+   *          the RequestFactory request processor interact with the domain
+   *          objects
+   */
+  public RequestFactoryServlet(ExceptionHandler exceptionHandler,
+      ServiceLayerDecorator... serviceDecorators) {
+    processor = new SimpleRequestProcessor(
+        ServiceLayer.create(serviceDecorators));
+    processor.setExceptionHandler(exceptionHandler);
+  }
+
+  /**
+   * Processes a POST to the server.
+   * 
+   * @param request an {@link HttpServletRequest} instance
+   * @param response an {@link HttpServletResponse} instance
+   * @throws IOException if an internal I/O error occurs
+   * @throws ServletException if an error occurs in the servlet
+   */
+  @Override
+  protected void doPost(HttpServletRequest request, HttpServletResponse response)
+      throws IOException, ServletException {
+
+    perThreadRequest.set(request);
+    perThreadResponse.set(response);
+
+    // No new code should be placed outside of this try block.
+    try {
+      ensureConfig();
+      String jsonRequestString = RPCServletUtils.readContent(request,
+          JSON_CONTENT_TYPE, JSON_CHARSET);
+      if (DUMP_PAYLOAD) {
+        System.out.println(">>> " + jsonRequestString);
+      }
+
+      try {
+        String payload = processor.process(jsonRequestString);
+        if (DUMP_PAYLOAD) {
+          System.out.println("<<< " + payload);
+        }
+        response.setStatus(HttpServletResponse.SC_OK);
+        response.setContentType(RequestFactory.JSON_CONTENT_TYPE_UTF8);
+        // The Writer must be obtained after setting the content type
+        PrintWriter writer = response.getWriter();
+        writer.print(payload);
+        writer.flush();
+      } catch (RuntimeException e) {
+        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+        log.log(Level.SEVERE, "Unexpected error", e);
+      }
+    } finally {
+      perThreadRequest.set(null);
+      perThreadResponse.set(null);
+    }
+  }
+
+  private void ensureConfig() {
+    String symbolMapsDirectory = getServletConfig().getInitParameter(
+        "symbolMapsDirectory");
+    if (symbolMapsDirectory != null) {
+      Logging.setSymbolMapsDirectory(symbolMapsDirectory);
+    }
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/RequestState.java b/user/src/com/google/web/bindery/requestfactory/server/RequestState.java
new file mode 100644
index 0000000..e167c41
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/RequestState.java
@@ -0,0 +1,295 @@
+/*
+ * 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.requestfactory.server;
+
+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;
+import com.google.web.bindery.requestfactory.shared.ValueProxy;
+import com.google.web.bindery.requestfactory.shared.impl.Constants;
+import com.google.web.bindery.requestfactory.shared.impl.EntityCodex;
+import com.google.web.bindery.requestfactory.shared.impl.IdFactory;
+import com.google.web.bindery.requestfactory.shared.impl.MessageFactoryHolder;
+import com.google.web.bindery.requestfactory.shared.impl.SimpleProxyId;
+import com.google.web.bindery.requestfactory.shared.messages.IdMessage;
+import com.google.web.bindery.requestfactory.shared.messages.IdMessage.Strength;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Encapsulates all state relating to the processing of a single request so that
+ * the SimpleRequestProcessor can be stateless.
+ */
+class RequestState implements EntityCodex.EntitySource {
+  final IdToEntityMap beans = new IdToEntityMap();
+  private final Map<Object, SimpleProxyId<?>> domainObjectsToId;
+  private final IdFactory idFactory;
+  private final ServiceLayer service;
+  private final Resolver resolver;
+
+  public RequestState(RequestState parent) {
+    idFactory = parent.idFactory;
+    domainObjectsToId = parent.domainObjectsToId;
+    service = parent.service;
+    resolver = new Resolver(this);
+  }
+
+  public RequestState(final ServiceLayer service) {
+    this.service = service;
+    idFactory = new IdFactory() {
+      @Override
+      public boolean isEntityType(Class<?> clazz) {
+        return EntityProxy.class.isAssignableFrom(clazz);
+      }
+
+      @Override
+      public boolean isValueType(Class<?> clazz) {
+        return ValueProxy.class.isAssignableFrom(clazz);
+      }
+
+      @Override
+      @SuppressWarnings("unchecked")
+      protected <P extends BaseProxy> Class<P> getTypeFromToken(String typeToken) {
+        return (Class<P>) service.resolveClass(typeToken);
+      }
+
+      @Override
+      protected String getTypeToken(Class<? extends BaseProxy> clazz) {
+        return service.resolveTypeToken(clazz);
+      }
+    };
+    domainObjectsToId = new IdentityHashMap<Object, SimpleProxyId<?>>();
+    resolver = new Resolver(this);
+  }
+
+  /**
+   * Turn a domain value into a wire format message.
+   */
+  public Splittable flatten(Object domainValue) {
+    Splittable flatValue;
+    if (ValueCodex.canDecode(domainValue.getClass())) {
+      flatValue = ValueCodex.encode(domainValue);
+    } else {
+      flatValue = new SimpleRequestProcessor(service).createOobMessage(Collections.singletonList(domainValue));
+    }
+    return flatValue;
+  }
+
+  /**
+   * Get or create a BaseProxy AutoBean for the given id.
+   */
+  public <Q extends BaseProxy> AutoBean<Q> getBeanForPayload(
+      SimpleProxyId<Q> id, Object domainObject) {
+    @SuppressWarnings("unchecked")
+    AutoBean<Q> toReturn = (AutoBean<Q>) beans.get(id);
+    if (toReturn == null) {
+      toReturn = createProxyBean(id, domainObject);
+    }
+    return toReturn;
+  }
+
+  /**
+   * EntityCodex support.
+   */
+  public <Q extends BaseProxy> AutoBean<Q> getBeanForPayload(
+      Splittable serializedProxyId) {
+    IdMessage idMessage = AutoBeanCodex.decode(MessageFactoryHolder.FACTORY,
+        IdMessage.class, serializedProxyId).as();
+    @SuppressWarnings("unchecked")
+    AutoBean<Q> toReturn = (AutoBean<Q>) getBeansForPayload(
+        Collections.singletonList(idMessage)).get(0);
+    return toReturn;
+  }
+
+  /**
+   * Get or create BaseProxy AutoBeans for a list of id-bearing messages.
+   */
+  public List<AutoBean<? extends BaseProxy>> getBeansForPayload(
+      List<? extends IdMessage> idMessages) {
+    List<SimpleProxyId<?>> ids = new ArrayList<SimpleProxyId<?>>(
+        idMessages.size());
+    for (IdMessage idMessage : idMessages) {
+      SimpleProxyId<?> id;
+      if (Strength.SYNTHETIC.equals(idMessage.getStrength())) {
+        Class<? extends BaseProxy> clazz = service.resolveClass(idMessage.getTypeToken());
+        id = idFactory.allocateSyntheticId(clazz, idMessage.getSyntheticId());
+      } else {
+        String decodedId = idMessage.getServerId() == null ? null
+            : SimpleRequestProcessor.fromBase64(idMessage.getServerId());
+        id = idFactory.getId(idMessage.getTypeToken(), decodedId,
+            idMessage.getClientId());
+      }
+      ids.add(id);
+    }
+    return getBeansForIds(ids);
+  }
+
+  public IdFactory getIdFactory() {
+    return idFactory;
+  }
+
+  public Resolver getResolver() {
+    return resolver;
+  }
+
+  /**
+   * EntityCodex support. This method is identical to
+   * {@link IdFactory#getHistoryToken(SimpleProxyId)} except that it
+   * base64-encodes the server ids.
+   * <p>
+   * XXX: Merge this with AbstsractRequestContext's implementation
+   */
+  public Splittable getSerializedProxyId(SimpleProxyId<?> stableId) {
+    AutoBean<IdMessage> bean = MessageFactoryHolder.FACTORY.id();
+    IdMessage ref = bean.as();
+    ref.setTypeToken(service.resolveTypeToken(stableId.getProxyClass()));
+    if (stableId.isSynthetic()) {
+      ref.setStrength(Strength.SYNTHETIC);
+      ref.setSyntheticId(stableId.getSyntheticId());
+    } else if (stableId.isEphemeral()) {
+      ref.setStrength(Strength.EPHEMERAL);
+      ref.setClientId(stableId.getClientId());
+    } else {
+      ref.setServerId(SimpleRequestProcessor.toBase64(stableId.getServerId()));
+    }
+    return AutoBeanCodex.encode(bean);
+  }
+
+  public ServiceLayer getServiceLayer() {
+    return service;
+  }
+
+  /**
+   * If the given domain object has been previously associated with an id,
+   * return it.
+   */
+  public SimpleProxyId<?> getStableId(Object domain) {
+    return domainObjectsToId.get(domain);
+  }
+
+  /**
+   * EntityCodex support.
+   */
+  public boolean isEntityType(Class<?> clazz) {
+    return idFactory.isEntityType(clazz);
+  }
+
+  /**
+   * EntityCodex support.
+   */
+  public boolean isValueType(Class<?> clazz) {
+    return idFactory.isValueType(clazz);
+  }
+
+  /**
+   * Creates an AutoBean for the given id, tracking a domain object.
+   */
+  private <Q extends BaseProxy> AutoBean<Q> createProxyBean(
+      SimpleProxyId<Q> id, Object domainObject) {
+    AutoBean<Q> toReturn = AutoBeanFactorySource.createBean(id.getProxyClass(),
+        SimpleRequestProcessor.CONFIGURATION);
+    toReturn.setTag(Constants.STABLE_ID, id);
+    toReturn.setTag(Constants.DOMAIN_OBJECT, domainObject);
+    beans.put(id, toReturn);
+    return toReturn;
+  }
+
+  /**
+   * Returns the AutoBeans corresponding to the given ids, or creates them if
+   * they do not yet exist.
+   */
+  private List<AutoBean<? extends BaseProxy>> getBeansForIds(
+      List<SimpleProxyId<?>> ids) {
+    List<Class<?>> domainClasses = new ArrayList<Class<?>>(ids.size());
+    List<Object> domainIds = new ArrayList<Object>(ids.size());
+    List<SimpleProxyId<?>> idsToLoad = new ArrayList<SimpleProxyId<?>>();
+
+    /*
+     * Create proxies for ephemeral or synthetic ids that we haven't seen. Queue
+     * up the domain ids for entities that need to be loaded.
+     */
+    for (SimpleProxyId<?> id : ids) {
+      Class<?> domainClass = service.resolveDomainClass(id.getProxyClass());
+      if (beans.containsKey(id)) {
+        // Already have a proxy for this id, no-op
+      } else if (id.isEphemeral() || id.isSynthetic()) {
+        // Create a new domain object for the short-lived id
+        Object domain = service.createDomainObject(domainClass);
+        if (domain == null) {
+          throw new UnexpectedException("Could not create instance of "
+              + domainClass.getCanonicalName(), null);
+        }
+        AutoBean<? extends BaseProxy> bean = createProxyBean(id, domain);
+        beans.put(id, bean);
+        domainObjectsToId.put(domain, id);
+      } else {
+        // Decode the domain parameter
+        Splittable split = StringQuoter.split(id.getServerId());
+        Class<?> param = service.getIdType(domainClass);
+        Object domainParam;
+        if (ValueCodex.canDecode(param)) {
+          domainParam = ValueCodex.decode(param, split);
+        } else {
+          domainParam = new SimpleRequestProcessor(service).decodeOobMessage(
+              param, split).get(0);
+        }
+
+        // Enqueue
+        domainClasses.add(service.resolveDomainClass(id.getProxyClass()));
+        domainIds.add(domainParam);
+        idsToLoad.add(id);
+      }
+    }
+
+    // Actually load the data
+    if (!domainClasses.isEmpty()) {
+      assert domainClasses.size() == domainIds.size()
+          && domainClasses.size() == idsToLoad.size();
+      List<Object> loaded = service.loadDomainObjects(domainClasses, domainIds);
+      if (idsToLoad.size() != loaded.size()) {
+        throw new UnexpectedException("Expected " + idsToLoad.size()
+            + " objects to be loaded, got " + loaded.size(), null);
+      }
+
+      Iterator<Object> itLoaded = loaded.iterator();
+      for (SimpleProxyId<?> id : idsToLoad) {
+        Object domain = itLoaded.next();
+        domainObjectsToId.put(domain, id);
+        AutoBean<? extends BaseProxy> bean = createProxyBean(id, domain);
+        beans.put(id, bean);
+      }
+    }
+
+    // Construct the return value
+    List<AutoBean<? extends BaseProxy>> toReturn = new ArrayList<AutoBean<? extends BaseProxy>>(
+        ids.size());
+    for (SimpleProxyId<?> id : ids) {
+      toReturn.add(beans.get(id));
+    }
+    return toReturn;
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/Resolver.java b/user/src/com/google/web/bindery/requestfactory/server/Resolver.java
new file mode 100644
index 0000000..9f641eb
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/Resolver.java
@@ -0,0 +1,450 @@
+/*
+ * 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.requestfactory.server;
+
+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;
+import com.google.web.bindery.requestfactory.shared.impl.Constants;
+import com.google.web.bindery.requestfactory.shared.impl.SimpleProxyId;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * Responsible for converting between domain and client entities. This class has
+ * a small amount of temporary state used to handle graph cycles and assignment
+ * of synthetic ids.
+ * 
+ * @see RequestState#getResolver()
+ */
+class Resolver {
+  /**
+   * A parameterized type with a single parameter.
+   */
+  private static class CollectionType implements ParameterizedType {
+    private final Class<?> rawType;
+    private final Class<?> elementType;
+
+    private CollectionType(Class<?> rawType, Class<?> elementType) {
+      this.rawType = rawType;
+      this.elementType = elementType;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (!(o instanceof CollectionType)) {
+        return false;
+      }
+      CollectionType other = (CollectionType) o;
+      return rawType.equals(other.rawType)
+          && elementType.equals(other.elementType);
+    }
+
+    public Type[] getActualTypeArguments() {
+      return new Type[] {elementType};
+    }
+
+    public Type getOwnerType() {
+      return null;
+    }
+
+    public Type getRawType() {
+      return rawType;
+    }
+
+    @Override
+    public int hashCode() {
+      return rawType.hashCode() * 13 + elementType.hashCode() * 7;
+    }
+  }
+
+  /**
+   * Used to map the objects being resolved and its API slice to the client-side
+   * value. This handles the case where a domain object is returned to the
+   * client mapped to two proxies of differing types.
+   */
+  private static class ResolutionKey {
+    private final Object domainObject;
+    private final int hashCode;
+    private final Type requestedType;
+
+    public ResolutionKey(Object domainObject, Type requestedType) {
+      this.domainObject = domainObject;
+      this.requestedType = requestedType;
+      this.hashCode = System.identityHashCode(domainObject) * 13
+          + requestedType.hashCode() * 7;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (!(o instanceof ResolutionKey)) {
+        return false;
+      }
+      ResolutionKey other = (ResolutionKey) o;
+      // Object identity comparison intentional
+      if (domainObject != other.domainObject) {
+        return false;
+      }
+      if (!requestedType.equals(other.requestedType)) {
+        return false;
+      }
+      return true;
+    }
+
+    @Override
+    public int hashCode() {
+      return hashCode;
+    }
+
+    /**
+     * For debugging use only.
+     */
+    @Override
+    public String toString() {
+      return domainObject.toString() + " => " + requestedType.toString();
+    }
+  }
+
+  /**
+   * Returns the trailing {@code [n]} index value from a path.
+   */
+  static int index(String path) {
+    int idx = path.lastIndexOf('[');
+    if (idx == -1) {
+      return -1;
+    }
+    return Integer.parseInt(path.substring(idx + 1, path.lastIndexOf(']')));
+  }
+
+  /**
+   * Returns {@code true} if the given prefix is one of the requested property
+   * references.
+   */
+  static boolean matchesPropertyRef(Set<String> propertyRefs, String newPrefix) {
+    return propertyRefs.contains(newPrefix.replaceAll("\\[\\d+\\]", ""));
+  }
+
+  /**
+   * Removes the trailing {@code [n]} from a path.
+   */
+  static String snipIndex(String path) {
+    int idx = path.lastIndexOf('[');
+    if (idx == -1) {
+      return path;
+    }
+    return path.substring(0, idx);
+  }
+
+  /**
+   * Maps domain values to client values. This map prevents cycles in the object
+   * graph from causing infinite recursion.
+   */
+  private final Map<ResolutionKey, Object> resolved = new HashMap<ResolutionKey, Object>();
+  private final ServiceLayer service;
+  private final RequestState state;
+  private int syntheticId;
+
+  /**
+   * Should only be called from {@link RequestState}.
+   */
+  Resolver(RequestState state) {
+    this.state = state;
+    this.service = state.getServiceLayer();
+  }
+
+  /**
+   * Given a domain object, return a value that can be encoded by the client.
+   * 
+   * @param domainValue the domain object to be converted into a client-side
+   *          value
+   * @param assignableTo the type in the client to which the resolved value
+   *          should be assignable. A value of {@code null} indicates that any
+   *          resolution will suffice.
+   * @param propertyRefs the property references requested by the client
+   */
+  public Object resolveClientValue(Object domainValue, Type assignableTo,
+      Set<String> propertyRefs) {
+    return resolveClientValue(domainValue, assignableTo,
+        getPropertyRefs(propertyRefs), "");
+  }
+
+  /**
+   * Convert a client-side value into a domain value.
+   * 
+   * @param maybeEntityProxy the client object to resolve
+   * @param detectDeadEntities if <code>true</code> this method will throw a
+   *          ReportableException containing a {@link DeadEntityException} if an
+   *          EntityProxy cannot be resolved
+   */
+  public Object resolveDomainValue(Object maybeEntityProxy,
+      boolean detectDeadEntities) {
+    if (maybeEntityProxy instanceof BaseProxy) {
+      AutoBean<BaseProxy> bean = AutoBeanUtils.getAutoBean((BaseProxy) maybeEntityProxy);
+      Object domain = bean.getTag(Constants.DOMAIN_OBJECT);
+      if (domain == null && detectDeadEntities) {
+        throw new ReportableException(new DeadEntityException(
+            "The requested entity is not available on the server"));
+      }
+      return domain;
+    } else if (maybeEntityProxy instanceof Collection<?>) {
+      Collection<Object> accumulator;
+      if (maybeEntityProxy instanceof List<?>) {
+        accumulator = new ArrayList<Object>();
+      } else if (maybeEntityProxy instanceof Set<?>) {
+        accumulator = new HashSet<Object>();
+      } else {
+        throw new ReportableException("Unsupported collection type "
+            + maybeEntityProxy.getClass().getName());
+      }
+      for (Object o : (Collection<?>) maybeEntityProxy) {
+        accumulator.add(resolveDomainValue(o, detectDeadEntities));
+      }
+      return accumulator;
+    }
+    return maybeEntityProxy;
+  }
+
+  /**
+   * Expand the property references in an InvocationMessage into a
+   * fully-expanded list of properties. For example, <code>[foo.bar.baz]</code>
+   * will be converted into <code>[foo, foo.bar, foo.bar.baz]</code>.
+   */
+  private Set<String> getPropertyRefs(Set<String> refs) {
+    if (refs == null) {
+      return Collections.emptySet();
+    }
+
+    Set<String> toReturn = new TreeSet<String>();
+    for (String raw : refs) {
+      for (int idx = raw.length(); idx >= 0; idx = raw.lastIndexOf('.', idx - 1)) {
+        toReturn.add(raw.substring(0, idx));
+      }
+    }
+    return toReturn;
+  }
+
+  /**
+   * Converts a domain entity into an EntityProxy that will be sent to the
+   * client.
+   */
+  private <T extends BaseProxy> T resolveClientProxy(final Object domainEntity,
+      Class<T> proxyType, final Set<String> propertyRefs, ResolutionKey key,
+      final String prefix) {
+    if (domainEntity == null) {
+      return null;
+    }
+
+    SimpleProxyId<? extends BaseProxy> id = state.getStableId(domainEntity);
+
+    boolean isEntityProxy = state.isEntityType(proxyType);
+    final boolean isOwnerValueProxy = state.isValueType(proxyType);
+    Object domainVersion;
+
+    // Create the id or update an ephemeral id by calculating its address
+    if (id == null || id.isEphemeral()) {
+      // The address is an id or an id plus a path
+      Object domainId;
+      if (isEntityProxy) {
+        // Compute data needed to return id to the client
+        domainId = service.getId(domainEntity);
+        domainVersion = service.getVersion(domainEntity);
+      } else {
+        domainId = null;
+        domainVersion = null;
+      }
+      if (id == null) {
+        if (domainId == null) {
+          /*
+           * This will happen when server code attempts to return an unpersisted
+           * object to the client. In this case, we'll assign a synthetic id
+           * that is valid for the duration of the response. The client is
+           * expected to assign a client-local id to this object and then it
+           * will behave as though it were an object newly-created by the
+           * client.
+           */
+          id = state.getIdFactory().allocateSyntheticId(proxyType,
+              ++syntheticId);
+        } else {
+          Splittable flatValue = state.flatten(domainId);
+          id = state.getIdFactory().getId(proxyType, flatValue.getPayload(), 0);
+        }
+      } else if (domainId != null) {
+        // Mark an ephemeral id as having been persisted
+        Splittable flatValue = state.flatten(domainId);
+        id.setServerId(flatValue.getPayload());
+      }
+    } else if (isEntityProxy) {
+      // Already have the id, just pull the current version
+      domainVersion = service.getVersion(domainEntity);
+    } else {
+      // The version of a value object is always null
+      domainVersion = null;
+    }
+
+    @SuppressWarnings("unchecked")
+    AutoBean<T> bean = (AutoBean<T>) state.getBeanForPayload(id, domainEntity);
+    resolved.put(key, bean.as());
+    bean.setTag(Constants.IN_RESPONSE, true);
+    if (domainVersion != null) {
+      Splittable flatVersion = state.flatten(domainVersion);
+      bean.setTag(Constants.VERSION_PROPERTY_B64,
+          SimpleRequestProcessor.toBase64(flatVersion.getPayload()));
+    }
+
+    bean.accept(new AutoBeanVisitor() {
+
+      @Override
+      public boolean visitReferenceProperty(String propertyName,
+          AutoBean<?> value, PropertyContext ctx) {
+        // Does the user care about the property?
+        String newPrefix = (prefix.length() > 0 ? (prefix + ".") : "")
+            + propertyName;
+
+        /*
+         * Send the property if the enclosing type is a ValueProxy, if the owner
+         * requested the property, or if the property is a list of values.
+         */
+        Class<?> elementType = ctx instanceof CollectionPropertyContext
+            ? ((CollectionPropertyContext) ctx).getElementType() : null;
+        boolean shouldSend = isOwnerValueProxy
+            || matchesPropertyRef(propertyRefs, newPrefix)
+            || elementType != null && ValueCodex.canDecode(elementType);
+
+        if (!shouldSend) {
+          return false;
+        }
+
+        // Call the getter
+        Object domainValue = service.getProperty(domainEntity, propertyName);
+        if (domainValue == null) {
+          return false;
+        }
+
+        // Turn the domain object into something usable on the client side
+        Type type;
+        if (elementType == null) {
+          type = ctx.getType();
+        } else {
+          type = new CollectionType(ctx.getType(), elementType);
+        }
+        Object clientValue = resolveClientValue(domainValue, type,
+            propertyRefs, newPrefix);
+
+        ctx.set(clientValue);
+        return false;
+      }
+
+      @Override
+      public boolean visitValueProperty(String propertyName, Object value,
+          PropertyContext ctx) {
+        // Limit unrequested value properties?
+        value = service.getProperty(domainEntity, propertyName);
+        ctx.set(value);
+        return false;
+      }
+    });
+
+    return bean.as();
+  }
+
+  /**
+   * Recursive-descent implementation.
+   */
+  private Object resolveClientValue(Object domainValue, Type returnType,
+      Set<String> propertyRefs, String prefix) {
+    if (domainValue == null) {
+      return null;
+    }
+
+    boolean anyType = returnType == null;
+    if (anyType) {
+      returnType = Object.class;
+    }
+
+    Class<?> assignableTo = TypeUtils.ensureBaseType(returnType);
+    ResolutionKey key = new ResolutionKey(domainValue, returnType);
+
+    Object previous = resolved.get(key);
+    if (previous != null && assignableTo.isInstance(previous)) {
+      return assignableTo.cast(previous);
+    }
+
+    Class<?> returnClass = service.resolveClientType(domainValue.getClass(),
+        assignableTo, true);
+
+    if (anyType) {
+      assignableTo = returnClass;
+    }
+
+    // Pass simple values through
+    if (ValueCodex.canDecode(returnClass)) {
+      return assignableTo.cast(domainValue);
+    }
+
+    // Convert entities to EntityProxies or EntityProxyIds
+    boolean isProxy = BaseProxy.class.isAssignableFrom(returnClass);
+    boolean isId = EntityProxyId.class.isAssignableFrom(returnClass);
+    if (isProxy || isId) {
+      Class<? extends BaseProxy> proxyClass = assignableTo.asSubclass(BaseProxy.class);
+      BaseProxy entity = resolveClientProxy(domainValue, proxyClass,
+          propertyRefs, key, prefix);
+      if (isId) {
+        return assignableTo.cast(((EntityProxy) entity).stableId());
+      }
+      return assignableTo.cast(entity);
+    }
+
+    // Convert collections
+    if (Collection.class.isAssignableFrom(returnClass)) {
+      Collection<Object> accumulator;
+      if (List.class.isAssignableFrom(returnClass)) {
+        accumulator = new ArrayList<Object>();
+      } else if (Set.class.isAssignableFrom(returnClass)) {
+        accumulator = new HashSet<Object>();
+      } else {
+        throw new ReportableException("Unsupported collection type"
+            + returnClass.getName());
+      }
+      resolved.put(key, accumulator);
+
+      Type elementType = TypeUtils.getSingleParameterization(Collection.class,
+          returnType);
+      for (Object o : (Collection<?>) domainValue) {
+        accumulator.add(resolveClientValue(o, elementType, propertyRefs, prefix));
+      }
+      return assignableTo.cast(accumulator);
+    }
+
+    throw new ReportableException("Unsupported domain type "
+        + returnClass.getCanonicalName());
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/ResolverServiceLayer.java b/user/src/com/google/web/bindery/requestfactory/server/ResolverServiceLayer.java
new file mode 100644
index 0000000..f95e896
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/ResolverServiceLayer.java
@@ -0,0 +1,203 @@
+/*
+ * 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.requestfactory.server;
+
+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;
+import com.google.web.bindery.requestfactory.shared.ProxyFor;
+import com.google.web.bindery.requestfactory.shared.ProxyForName;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.Service;
+import com.google.web.bindery.requestfactory.shared.ServiceName;
+import com.google.web.bindery.requestfactory.shared.ValueProxy;
+
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Logger;
+
+/**
+ * Implements all of the resolution methods in ServiceLayer.
+ */
+final class ResolverServiceLayer extends ServiceLayerDecorator {
+
+  private static final Logger log = Logger.getLogger(ServiceLayer.class.getName());
+
+  /**
+   * All instances of the service layer that are loaded by the same classloader
+   * can use a shared validator. The use of the validator should be
+   * synchronized, since it is stateful.
+   */
+  private static final RequestFactoryInterfaceValidator validator =
+      new RequestFactoryInterfaceValidator(log,
+          new RequestFactoryInterfaceValidator.ClassLoaderLoader(ServiceLayer.class
+              .getClassLoader()));
+
+  @Override
+  public ClassLoader getDomainClassLoader() {
+    return Thread.currentThread().getContextClassLoader();
+  }
+
+  @Override
+  public Class<? extends BaseProxy> resolveClass(String typeToken) {
+    Class<?> found = forName(typeToken);
+    if (!EntityProxy.class.isAssignableFrom(found) && !ValueProxy.class.isAssignableFrom(found)) {
+      die(null, "The requested type %s is not assignable to %s or %s", typeToken, EntityProxy.class
+          .getCanonicalName(), ValueProxy.class.getCanonicalName());
+    }
+    synchronized (validator) {
+      validator.antidote();
+      validator.validateProxy(found.getName());
+      if (validator.isPoisoned()) {
+        die(null, "The type %s did not pass RequestFactory validation", found.getCanonicalName());
+      }
+    }
+    return found.asSubclass(BaseProxy.class);
+  }
+
+  @Override
+  public <T> Class<? extends T> resolveClientType(Class<?> domainClass, Class<T> clientClass,
+      boolean required) {
+    String name;
+    synchronized (validator) {
+      name = validator.getEntityProxyTypeName(domainClass.getName(), clientClass.getName());
+    }
+    if (name != null) {
+      return forName(name).asSubclass(clientClass);
+    }
+    if (List.class.isAssignableFrom(domainClass)) {
+      return List.class.asSubclass(clientClass);
+    }
+    if (Set.class.isAssignableFrom(domainClass)) {
+      return Set.class.asSubclass(clientClass);
+    }
+    if (TypeUtils.isValueType(domainClass)) {
+      return domainClass.asSubclass(clientClass);
+    }
+    if (required) {
+      die(null, "The domain type %s cannot be sent to the client", domainClass.getCanonicalName());
+    }
+    return null;
+  }
+
+  @Override
+  public Class<?> resolveDomainClass(Class<?> clazz) {
+    if (List.class.equals(clazz)) {
+      return List.class;
+    } else if (Set.class.equals(clazz)) {
+      return Set.class;
+    } else if (BaseProxy.class.isAssignableFrom(clazz)) {
+      ProxyFor pf = clazz.getAnnotation(ProxyFor.class);
+      if (pf != null) {
+        return pf.value();
+      }
+      ProxyForName pfn = clazz.getAnnotation(ProxyForName.class);
+      if (pfn != null) {
+        Class<?> toReturn = forName(pfn.value());
+        return toReturn;
+      }
+    }
+    return die(null, "Could not resolve a domain type for client type %s", clazz.getCanonicalName());
+  }
+
+  @Override
+  public Method resolveDomainMethod(Method requestContextMethod) {
+    Class<?> declaringClass = requestContextMethod.getDeclaringClass();
+    Class<?> searchIn =
+        getTop().resolveServiceClass(declaringClass.asSubclass(RequestContext.class));
+    Class<?>[] parameterTypes = requestContextMethod.getParameterTypes();
+    Class<?>[] domainArgs = new Class<?>[parameterTypes.length];
+    for (int i = 0, j = domainArgs.length; i < j; i++) {
+      if (BaseProxy.class.isAssignableFrom(parameterTypes[i])) {
+        domainArgs[i] = getTop().resolveDomainClass(parameterTypes[i].asSubclass(BaseProxy.class));
+      } else if (EntityProxyId.class.isAssignableFrom(parameterTypes[i])) {
+        domainArgs[i] =
+            TypeUtils.ensureBaseType(TypeUtils.getSingleParameterization(EntityProxyId.class,
+                requestContextMethod.getGenericParameterTypes()[i]));
+      } else {
+        domainArgs[i] = parameterTypes[i];
+      }
+    }
+
+    Throwable ex;
+    try {
+      return searchIn.getMethod(requestContextMethod.getName(), domainArgs);
+    } catch (SecurityException e) {
+      ex = e;
+    } catch (NoSuchMethodException e) {
+      return report("Could not locate domain method %s", requestContextMethod.getName());
+    }
+    return die(ex, "Could not get domain method %s in type %s", requestContextMethod.getName(),
+        searchIn.getCanonicalName());
+  }
+
+  @Override
+  public Method resolveRequestContextMethod(String requestContextClass, String methodName) {
+    synchronized (validator) {
+      validator.antidote();
+      validator.validateRequestContext(requestContextClass);
+      if (validator.isPoisoned()) {
+        die(null, "The RequestContext type %s did not pass validation", requestContextClass);
+      }
+    }
+    Class<?> searchIn = forName(requestContextClass);
+    for (Method method : searchIn.getMethods()) {
+      if (method.getName().equals(methodName)) {
+        return method;
+      }
+    }
+    return report("Could not locate %s method %s::%s", RequestContext.class.getSimpleName(),
+        requestContextClass, methodName);
+  }
+
+  @Override
+  public Class<?> resolveServiceClass(Class<? extends RequestContext> requestContextClass) {
+    Class<?> searchIn = null;
+    Service s = requestContextClass.getAnnotation(Service.class);
+    // TODO Handle case when both annotations are present
+    if (s != null) {
+      searchIn = s.value();
+    }
+    ServiceName sn = requestContextClass.getAnnotation(ServiceName.class);
+    if (sn != null) {
+      searchIn = forName(sn.value());
+    }
+    if (searchIn == null) {
+      die(null, "The %s type %s did not specify a service type", RequestContext.class
+          .getSimpleName(), requestContextClass.getCanonicalName());
+    }
+    return searchIn;
+  }
+
+  @Override
+  public String resolveTypeToken(Class<? extends BaseProxy> clazz) {
+    return clazz.getName();
+  }
+
+  /**
+   * Call {@link Class#forName(String)} and report any errors through
+   * {@link #die()}.
+   */
+  private Class<?> forName(String name) {
+    try {
+      return Class.forName(name, false, getTop().getDomainClassLoader());
+    } catch (ClassNotFoundException e) {
+      return die(e, "Could not locate class %s", name);
+    }
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/ServiceLayer.java b/user/src/com/google/web/bindery/requestfactory/server/ServiceLayer.java
new file mode 100644
index 0000000..484f954
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/ServiceLayer.java
@@ -0,0 +1,402 @@
+/*
+ * 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.requestfactory.server;
+
+import com.google.web.bindery.requestfactory.shared.BaseProxy;
+import com.google.web.bindery.requestfactory.shared.Locator;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.ServiceLocator;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+import javax.validation.ConstraintViolation;
+
+/**
+ * The ServiceLayer mediates all interactions between the
+ * {@link SimpleRequestProcessor} and the domain environment. The core service
+ * logic can be decorated by extending an {@link ServiceLayerDecorator}.
+ * <p>
+ * This API is subject to change in future releases.
+ */
+public abstract class ServiceLayer {
+  /*
+   * NB: This type cannot be directly extended by the user since it has a
+   * package-protected constructor. This means that any API-compatibility work
+   * that needs to happen can be done in ServiceLayerDecorator in order to keep
+   * this interface as clean as possible.
+   */
+
+  /**
+   * Provides a flag to disable the ServiceLayerCache for debugging purposes.
+   */
+  private static final boolean ENABLE_CACHE = Boolean.valueOf(System.getProperty(
+      "gwt.rf.ServiceLayerCache", "true"));
+
+  /**
+   * Create a RequestFactory ServiceLayer that is optionally modified by the
+   * given decorators.
+   * 
+   * @param decorators the decorators that will modify the behavior of the core
+   *          service layer implementation
+   * @return a ServiceLayer instance
+   */
+  public static ServiceLayer create(ServiceLayerDecorator... decorators) {
+    List<ServiceLayerDecorator> list = new ArrayList<ServiceLayerDecorator>();
+    // Always hit the cache first
+    ServiceLayerDecorator cache = ENABLE_CACHE ? new ServiceLayerCache()
+        : new ServiceLayerDecorator();
+    list.add(cache);
+    // The the user-provided decorators
+    if (decorators != null) {
+      list.addAll(Arrays.asList(decorators));
+    }
+    // Support for Locator objects
+    list.add(new LocatorServiceLayer());
+    // Interact with domain objects
+    list.add(new ReflectiveServiceLayer());
+    // Locate domain objects
+    list.add(new ResolverServiceLayer());
+
+    // Make the last layer point to the cache
+    list.get(list.size() - 1).top = cache;
+
+    // Point each entry at the next
+    for (int i = list.size() - 2; i >= 0; i--) {
+      ServiceLayerDecorator layer = list.get(i);
+      layer.next = list.get(i + 1);
+      layer.top = cache;
+    }
+
+    return cache;
+  }
+
+  /**
+   * A pointer to the top-most ServiceLayer instance.
+   */
+  ServiceLayer top;
+
+  /**
+   * Not generally-extensible.
+   */
+  ServiceLayer() {
+  }
+
+  /**
+   * Create an instance of the requested domain type.
+   * 
+   * @param <T> the requested domain type
+   * @param clazz the requested domain type
+   * @return an instance of the requested domain type
+   */
+  public abstract <T> T createDomainObject(Class<T> clazz);
+
+  /**
+   * Create an instance of the requested {@link Locator} type.
+   * 
+   * @param <T> the requested Locator type
+   * @param clazz the requested Locator type
+   * @return an instance of the requested Locator type
+   */
+  public abstract <T extends Locator<?, ?>> T createLocator(Class<T> clazz);
+
+  /**
+   * Create an instance of a service object that can be used as the target for
+   * the given method invocation.
+   * 
+   * @param contextMethod a method defined in a RequestContext
+   * @param domainMethod the method that the service object must implement
+   * @return an instance of the requested service object
+   */
+  public abstract Object createServiceInstance(Method contextMethod,
+      Method domainMethod);
+
+  /**
+   * Create an instance of the requested {@link ServiceLocator} type.
+   * 
+   * @param <T> the requested ServiceLocator type
+   * @param clazz the requested ServiceLocator type
+   * @return an instance of the requested ServiceLocator type
+   */
+  public abstract <T extends ServiceLocator> T createServiceLocator(
+      Class<T> clazz);
+
+  /**
+   * Returns the ClassLoader that should be used when attempting to access
+   * domain classes or resources.
+   * <p>
+   * The default implementation returns
+   * {@code Thread.currentThread().getContextClassLoader()}.
+   */
+  public abstract ClassLoader getDomainClassLoader();
+
+  /**
+   * Determine the method to invoke when retrieving the given property.
+   * 
+   * @param domainType a domain entity type
+   * @param property the name of the property to be retrieved
+   * @return the Method that should be invoked to retrieve the property or
+   *         {@code null} if the method could not be located
+   */
+  public abstract Method getGetter(Class<?> domainType, String property);
+
+  /**
+   * Return the persistent id for a domain object. May return {@code null} to
+   * indicate that the domain object has not been persisted. The value returned
+   * from this method must be a simple type (e.g. Integer, String) or a domain
+   * type for which a mapping to an EntityProxy or Value proxy exists.
+   * <p>
+   * The values returned from this method may be passed to
+   * {@link #loadDomainObject(Class, Object)} in the future.
+   * 
+   * @param domainObject a domain object
+   * @return the persistent id of the domain object or {@code null} if the
+   *         object is not persistent
+   */
+  public abstract Object getId(Object domainObject);
+
+  /**
+   * Returns the type of object the domain type's {@code findFoo()} or
+   * {@link com.google.web.bindery.requestfactory.shared.Locator#getId(Object)
+   * Locator.getId()} expects to receive.
+   * 
+   * @param domainType a domain entity type
+   * @return the type of the persistent id value used to represent the domain
+   *         type
+   */
+  public abstract Class<?> getIdType(Class<?> domainType);
+
+  /**
+   * Retrieve the named property from the domain object.
+   * 
+   * @param domainObject the domain object being examined
+   * @param property the property name
+   * @return the value of the property
+   */
+  public abstract Object getProperty(Object domainObject, String property);
+
+  /**
+   * Compute the return type for a method declared in a RequestContext by
+   * analyzing the generic method declaration.
+   */
+  public abstract Type getRequestReturnType(Method contextMethod);
+
+  /**
+   * Determine the method to invoke when setting the given property.
+   * 
+   * @param domainType a domain entity type
+   * @param property the name of the property to be set
+   * @return the Method that should be invoked to set the property or
+   *         {@code null} if the method could not be located
+   */
+  public abstract Method getSetter(Class<?> domainType, String property);
+
+  /**
+   * May return {@code null} to indicate that the domain object has not been
+   * persisted. The value returned from this method must be a simple type (e.g.
+   * Integer, String) or a domain type for which a mapping to an EntityProxy or
+   * Value proxy exists.
+   * 
+   * @param domainObject a domain object
+   * @return the version of the domain object or {@code null} if the object is
+   *         not persistent
+   */
+  public abstract Object getVersion(Object domainObject);
+
+  /**
+   * Invoke a domain service method. The underlying eventually calls
+   * {@link Method#invoke(Object, Object...)}.
+   * 
+   * @param domainMethod the method to invoke
+   * @param args the arguments to pass to the method
+   * @return the value returned from the method invocation
+   */
+  public abstract Object invoke(Method domainMethod, Object... args);
+
+  /**
+   * Returns {@code true} if the given domain object is still live (i.e. not
+   * deleted) in the backing store.
+   * 
+   * @param domainObject a domain entity
+   * @return {@code true} if {@code domainObject} could be retrieved at a later
+   *         point in time
+   */
+  public abstract boolean isLive(Object domainObject);
+
+  /**
+   * Load an object from the backing store. This method may return {@code null}
+   * to indicate that the requested object is no longer available.
+   * 
+   * @param <T> the type of object to load
+   * @param clazz the type of object to load
+   * @param domainId an id previously returned from {@link #getId(Object)}
+   * @return the requested object or {@code null} if it is irretrievable
+   */
+  public abstract <T> T loadDomainObject(Class<T> clazz, Object domainId);
+
+  /**
+   * Load multiple objects from the backing store. This method is intended to
+   * allow more efficient access to the backing store by providing all objects
+   * referenced in an incoming payload.
+   * <p>
+   * The default implementation of this method will delegate to
+   * {@link #loadDomainObject(Class, Object)}.
+   * 
+   * @param classes type type of each object to load
+   * @param domainIds the ids previously returned from {@link #getId(Object)}
+   * @return the requested objects, elements of which may be {@code null} if the
+   *         requested objects were irretrievable
+   */
+  public abstract List<Object> loadDomainObjects(List<Class<?>> classes,
+      List<Object> domainIds);
+
+  /**
+   * Determines if the invocation of a domain method requires a
+   * {@link ServiceLocator} as the 0th parameter when passed into
+   * {@link #invoke(Method, Object...)}.
+   * 
+   * @param contextMethod a method defined in a RequestContext
+   * @param domainMethod a domain method
+   * @return {@code true} if a ServiceLocator is required
+   */
+  public abstract boolean requiresServiceLocator(Method contextMethod,
+      Method domainMethod);
+
+  /**
+   * Given a type token previously returned from
+   * {@link #resolveTypeToken(Class)}, return the Class literal associated with
+   * the token.
+   * 
+   * @param typeToken a string token
+   * @return the type represented by the token
+   */
+  public abstract Class<? extends BaseProxy> resolveClass(String typeToken);
+
+  /**
+   * Determine the type used by the client code to represent a given domain
+   * type. If multiple proxy types have been mapped to the same domain type, the
+   * {@code clientType} parameter is used to ensure assignability.
+   * 
+   * @param domainClass the server-side type to be transported to the client
+   * @param clientType the type to which the returned type must be assignable
+   * @param required if {@code true} and no mapping is available, throw an
+   *          exception, otherwise the method will return {@code null}
+   * @return a class that represents {@code domainClass} on the client which is
+   *         assignable to {@code clientType}
+   */
+  public abstract <T> Class<? extends T> resolveClientType(
+      Class<?> domainClass, Class<T> clientType, boolean required);
+
+  /**
+   * Determine the domain (server-side) type that the given client type is
+   * mapped to.
+   * 
+   * @param clientType a client-side type
+   * @return the domain type that {@code clientType} represents
+   */
+  public abstract Class<?> resolveDomainClass(Class<?> clientType);
+
+  /**
+   * Return the domain service method associated with a RequestContext method
+   * declaration. The {@code requestContextMethod} will have been previously
+   * resolved by {@link #resolveRequestContextMethod(String, String)}.
+   * 
+   * @param requestContextMethod a RequestContext method declaration.
+   * @return the domain service method that should be invoked
+   */
+  public abstract Method resolveDomainMethod(Method requestContextMethod);
+
+  /**
+   * Return the type of {@link Locator} that should be used to access the given
+   * domain type.
+   * 
+   * @param domainType a domain (server-side) type
+   * @return the type of Locator to use, or {@code null} if the type conforms to
+   *         the RequestFactory entity protocol
+   */
+  public abstract Class<? extends Locator<?, ?>> resolveLocator(
+      Class<?> domainType);
+
+  /**
+   * Find a RequestContext method declaration by name.
+   * 
+   * @param requestContextClass the fully-qualified binary name of the
+   *          RequestContext
+   * @param methodName the name of the service method declared within the
+   *          RequestContext
+   * @return the method declaration, or {@code null} if the method does not
+   *         exist
+   */
+  public abstract Method resolveRequestContextMethod(
+      String requestContextClass, String methodName);
+
+  /**
+   * Given a {@link RequestContext} method, find the service class referenced in
+   * the {@link Service} or {@link ServiceName} annotation.
+   * 
+   * @param requestContextClass a RequestContext interface
+   * @return the type of service to use
+   */
+  public abstract Class<?> resolveServiceClass(
+      Class<? extends RequestContext> requestContextClass);
+
+  /**
+   * Given a RequestContext method declaration, resolve the
+   * {@link ServiceLocator} that should be used when invoking the domain method.
+   * This method will only be called if
+   * {@link #requiresServiceLocator(Method, Method)} returned {@code true} for
+   * the associated domain method.
+   * 
+   * @param contextMethod a RequestContext method declaration
+   * @param domainMethod the domain method that will be invoked
+   * @return the type of ServiceLocator to use
+   */
+  public abstract Class<? extends ServiceLocator> resolveServiceLocator(
+      Method contextMethod, Method domainMethod);
+
+  /**
+   * Return a string used to represent the given type in the wire protocol.
+   * 
+   * @param proxyType a client-side EntityProxy or ValueProxy type
+   * @return the type token used to represent the proxy type
+   */
+  public abstract String resolveTypeToken(Class<? extends BaseProxy> proxyType);
+
+  /**
+   * Sets a property on a domain object.
+   * 
+   * @param domainObject the domain object to operate on
+   * @param property the name of the property to set
+   * @param expectedType the type of the property
+   * @param value the new value
+   */
+  public abstract void setProperty(Object domainObject, String property,
+      Class<?> expectedType, Object value);
+
+  /**
+   * Invoke a JSR 303 validator on the given domain object. If no validator is
+   * available, this method is a no-op.
+   * 
+   * @param <T> the type of data being validated
+   * @param domainObject the domain objcet to validate
+   * @return the violations associated with the domain object
+   */
+  public abstract <T> Set<ConstraintViolation<T>> validate(T domainObject);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/ServiceLayerCache.java b/user/src/com/google/web/bindery/requestfactory/server/ServiceLayerCache.java
new file mode 100644
index 0000000..4d8ac6e
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/ServiceLayerCache.java
@@ -0,0 +1,234 @@
+/*
+ * 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.requestfactory.server;
+
+import com.google.gwt.rpc.server.Pair;
+import com.google.web.bindery.requestfactory.shared.BaseProxy;
+import com.google.web.bindery.requestfactory.shared.Locator;
+import com.google.web.bindery.requestfactory.shared.ServiceLocator;
+
+import java.lang.ref.SoftReference;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * A cache for idempotent methods in {@link ServiceLayer}. The caching is
+ * separate from {@link ReflectiveServiceLayer} so that the cache can be applied
+ * to any decorators injected by the user.
+ */
+class ServiceLayerCache extends ServiceLayerDecorator {
+
+  /**
+   * ConcurrentHashMaps don't allow null keys or values, but sometimes we want
+   * to cache a null value.
+   */
+  private static final Object NULL_MARKER = new Object();
+
+  private static SoftReference<Map<Method, Map<Object, Object>>> methodCache;
+
+  private static final Method createLocator;
+  private static final Method createServiceInstance;
+  private static final Method getDomainClassLoader;
+  private static final Method getGetter;
+  private static final Method getIdType;
+  private static final Method getRequestReturnType;
+  private static final Method getSetter;
+  private static final Method requiresServiceLocator;
+  private static final Method resolveClass;
+  private static final Method resolveClientType;
+  private static final Method resolveDomainClass;
+  private static final Method resolveDomainMethod;
+  private static final Method resolveLocator;
+  private static final Method resolveRequestContextMethod;
+  private static final Method resolveServiceLocator;
+  private static final Method resolveTypeToken;
+
+  static {
+    createLocator = getMethod("createLocator", Class.class);
+    createServiceInstance = getMethod("createServiceInstance", Method.class, Method.class);
+    getDomainClassLoader = getMethod("getDomainClassLoader");
+    getGetter = getMethod("getGetter", Class.class, String.class);
+    getIdType = getMethod("getIdType", Class.class);
+    getRequestReturnType = getMethod("getRequestReturnType", Method.class);
+    getSetter = getMethod("getSetter", Class.class, String.class);
+    requiresServiceLocator = getMethod("requiresServiceLocator", Method.class, Method.class);
+    resolveClass = getMethod("resolveClass", String.class);
+    resolveClientType = getMethod("resolveClientType", Class.class, Class.class, boolean.class);
+    resolveDomainClass = getMethod("resolveDomainClass", Class.class);
+    resolveDomainMethod = getMethod("resolveDomainMethod", Method.class);
+    resolveLocator = getMethod("resolveLocator", Class.class);
+    resolveRequestContextMethod =
+        getMethod("resolveRequestContextMethod", String.class, String.class);
+    resolveServiceLocator = getMethod("resolveServiceLocator", Method.class, Method.class);
+    resolveTypeToken = getMethod("resolveTypeToken", Class.class);
+  }
+
+  private static Map<Method, Map<Object, Object>> getCache() {
+    Map<Method, Map<Object, Object>> toReturn = methodCache == null ? null : methodCache.get();
+    if (toReturn == null) {
+      toReturn = new ConcurrentHashMap<Method, Map<Object, Object>>();
+      methodCache = new SoftReference<Map<Method, Map<Object, Object>>>(toReturn);
+    }
+    return toReturn;
+  }
+
+  private static Method getMethod(String name, Class<?>... argTypes) {
+    try {
+      return ServiceLayer.class.getMethod(name, argTypes);
+    } catch (SecurityException e) {
+      throw new RuntimeException("Could not set up ServiceLayerCache Methods", e);
+    } catch (NoSuchMethodException e) {
+      throw new RuntimeException("Could not set up ServiceLayerCache Methods", e);
+    }
+  }
+
+  private final Map<Method, Map<Object, Object>> methodMap = getCache();
+
+  @Override
+  public <T extends Locator<?, ?>> T createLocator(Class<T> clazz) {
+    return getOrCache(createLocator, clazz, clazz, clazz);
+  }
+
+  @Override
+  public Object createServiceInstance(Method contextMethod, Method domainMethod) {
+    return getOrCache(createServiceInstance, new Pair<Method, Method>(contextMethod, domainMethod),
+        Object.class, contextMethod, domainMethod);
+  }
+
+  @Override
+  public ClassLoader getDomainClassLoader() {
+    return getOrCache(getDomainClassLoader, NULL_MARKER, ClassLoader.class);
+  }
+
+  @Override
+  public Method getGetter(Class<?> domainType, String property) {
+    return getOrCache(getGetter, new Pair<Class<?>, String>(domainType, property), Method.class,
+        domainType, property);
+  }
+
+  @Override
+  public Class<?> getIdType(Class<?> domainType) {
+    return getOrCache(getIdType, domainType, Class.class, domainType);
+  }
+
+  @Override
+  public Type getRequestReturnType(Method contextMethod) {
+    return getOrCache(getRequestReturnType, contextMethod, Type.class, contextMethod);
+  }
+
+  @Override
+  public Method getSetter(Class<?> domainType, String property) {
+    return getOrCache(getSetter, new Pair<Class<?>, String>(domainType, property), Method.class,
+        domainType, property);
+  }
+
+  @Override
+  public boolean requiresServiceLocator(Method contextMethod, Method domainMethod) {
+    return getOrCache(requiresServiceLocator,
+        new Pair<Method, Method>(contextMethod, domainMethod), Boolean.class, contextMethod,
+        domainMethod);
+  }
+
+  @Override
+  public Class<? extends BaseProxy> resolveClass(String typeToken) {
+    Class<?> found = getOrCache(resolveClass, typeToken, Class.class, typeToken);
+    return found.asSubclass(BaseProxy.class);
+  }
+
+  @Override
+  public <T> Class<? extends T> resolveClientType(Class<?> domainClass, Class<T> clientType,
+      boolean required) {
+    Class<?> clazz =
+        getOrCache(resolveClientType, new Pair<Class<?>, Class<?>>(domainClass, clientType),
+            Class.class, domainClass, clientType, required);
+    return clazz == null ? null : clazz.asSubclass(clientType);
+  }
+
+  @Override
+  public Class<?> resolveDomainClass(Class<?> clazz) {
+    return getOrCache(resolveDomainClass, clazz, Class.class, clazz);
+  }
+
+  @Override
+  public Method resolveDomainMethod(Method requestContextMethod) {
+    return getOrCache(resolveDomainMethod, requestContextMethod, Method.class, requestContextMethod);
+  }
+
+  @Override
+  @SuppressWarnings("unchecked")
+  public Class<? extends Locator<?, ?>> resolveLocator(Class<?> domainType) {
+    return getOrCache(resolveLocator, domainType, Class.class, domainType);
+  }
+
+  @Override
+  public Method resolveRequestContextMethod(String requestContextClass, String methodName) {
+    return getOrCache(resolveRequestContextMethod, new Pair<String, String>(requestContextClass,
+        methodName), Method.class, requestContextClass, methodName);
+  }
+
+  @Override
+  public Class<? extends ServiceLocator> resolveServiceLocator(Method contextMethod,
+      Method domainMethod) {
+    Class<?> clazz =
+        getOrCache(resolveServiceLocator, new Pair<Method, Method>(contextMethod, domainMethod),
+            Class.class, contextMethod, domainMethod);
+    return clazz == null ? null : clazz.asSubclass(ServiceLocator.class);
+  }
+
+  @Override
+  public String resolveTypeToken(Class<? extends BaseProxy> domainClass) {
+    return getOrCache(resolveTypeToken, domainClass, String.class, domainClass);
+  }
+
+  private <K, T> T getOrCache(Method method, K key, Class<T> valueType, Object... args) {
+    Map<Object, Object> map = methodMap.get(method);
+    if (map == null) {
+      map = new ConcurrentHashMap<Object, Object>();
+      methodMap.put(method, map);
+    }
+    Object raw = map.get(key);
+    if (raw == NULL_MARKER) {
+      return null;
+    }
+    T toReturn = valueType.cast(raw);
+    if (toReturn == null) {
+      Throwable ex = null;
+      try {
+        toReturn = valueType.cast(method.invoke(getNext(), args));
+        map.put(key, toReturn == null ? NULL_MARKER : toReturn);
+      } catch (InvocationTargetException e) {
+        // The next layer threw an exception
+        Throwable cause = e.getCause();
+        if (cause instanceof RuntimeException) {
+          // Re-throw RuntimeExceptions, which likely originate from die()
+          throw ((RuntimeException) cause);
+        }
+        die(cause, "Unexpected checked exception");
+      } catch (IllegalArgumentException e) {
+        ex = e;
+      } catch (IllegalAccessException e) {
+        ex = e;
+      }
+      if (ex != null) {
+        die(ex, "Bad method invocation");
+      }
+    }
+    return toReturn;
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/ServiceLayerDecorator.java b/user/src/com/google/web/bindery/requestfactory/server/ServiceLayerDecorator.java
new file mode 100644
index 0000000..b2a7f89
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/ServiceLayerDecorator.java
@@ -0,0 +1,266 @@
+/*
+ * 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.requestfactory.server;
+
+import com.google.web.bindery.requestfactory.shared.BaseProxy;
+import com.google.web.bindery.requestfactory.shared.Locator;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.ServiceLocator;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.validation.ConstraintViolation;
+
+/**
+ * Users that intend to alter how RequestFactory interacts with the domain
+ * environment can extend this type and provide it to
+ * {@link ServiceLayer#create(ServiceLayerDecorator...)}. The methods defined in
+ * this type will automatically delegate to the next decorator or the root
+ * service object after being processed by{@code create()}.
+ */
+public class ServiceLayerDecorator extends ServiceLayer {
+  private static final Logger log = Logger.getLogger(ServiceLayer.class.getName());
+
+  /**
+   * A pointer to the next deepest layer.
+   */
+  ServiceLayer next;
+
+  @Override
+  public <T> T createDomainObject(Class<T> clazz) {
+    return getNext().createDomainObject(clazz);
+  }
+
+  @Override
+  public <T extends Locator<?, ?>> T createLocator(Class<T> clazz) {
+    return getNext().createLocator(clazz);
+  }
+
+  @Override
+  public Object createServiceInstance(Method contextMethod, Method domainMethod) {
+    return getNext().createServiceInstance(contextMethod, domainMethod);
+  }
+
+  @Override
+  public <T extends ServiceLocator> T createServiceLocator(Class<T> clazz) {
+    return getNext().createServiceLocator(clazz);
+  }
+
+  @Override
+  public ClassLoader getDomainClassLoader() {
+    return getNext().getDomainClassLoader();
+  }
+
+  @Override
+  public Method getGetter(Class<?> domainType, String property) {
+    return getNext().getGetter(domainType, property);
+  }
+
+  @Override
+  public Object getId(Object domainObject) {
+    return getNext().getId(domainObject);
+  }
+
+  @Override
+  public Class<?> getIdType(Class<?> domainType) {
+    return getNext().getIdType(domainType);
+  }
+
+  @Override
+  public Object getProperty(Object domainObject, String property) {
+    return getNext().getProperty(domainObject, property);
+  }
+
+  @Override
+  public Type getRequestReturnType(Method contextMethod) {
+    return getNext().getRequestReturnType(contextMethod);
+  }
+
+  @Override
+  public Method getSetter(Class<?> domainType, String property) {
+    return getNext().getSetter(domainType, property);
+  }
+
+  @Override
+  public Object getVersion(Object domainObject) {
+    return getNext().getVersion(domainObject);
+  }
+
+  @Override
+  public Object invoke(Method domainMethod, Object... args) {
+    return getNext().invoke(domainMethod, args);
+  }
+
+  @Override
+  public boolean isLive(Object domainObject) {
+    return getNext().isLive(domainObject);
+  }
+
+  @Override
+  public <T> T loadDomainObject(Class<T> clazz, Object domainId) {
+    return getNext().loadDomainObject(clazz, domainId);
+  }
+
+  @Override
+  public List<Object> loadDomainObjects(List<Class<?>> classes,
+      List<Object> domainIds) {
+    return getNext().loadDomainObjects(classes, domainIds);
+  }
+
+  @Override
+  public boolean requiresServiceLocator(Method contextMethod,
+      Method domainMethod) {
+    return getNext().requiresServiceLocator(contextMethod, domainMethod);
+  }
+
+  @Override
+  public Class<? extends BaseProxy> resolveClass(String typeToken) {
+    return getNext().resolveClass(typeToken);
+  }
+
+  @Override
+  public <T> Class<? extends T> resolveClientType(Class<?> domainClass,
+      Class<T> clientType, boolean required) {
+    return getNext().resolveClientType(domainClass, clientType, required);
+  }
+
+  @Override
+  public Class<?> resolveDomainClass(Class<?> clazz) {
+    return getNext().resolveDomainClass(clazz);
+  }
+
+  @Override
+  public Method resolveDomainMethod(Method requestContextMethod) {
+    return getNext().resolveDomainMethod(requestContextMethod);
+  }
+
+  @Override
+  public Class<? extends Locator<?, ?>> resolveLocator(Class<?> domainType) {
+    return getNext().resolveLocator(domainType);
+  }
+
+  @Override
+  public Method resolveRequestContextMethod(String requestContextClass,
+      String methodName) {
+    return getNext().resolveRequestContextMethod(requestContextClass,
+        methodName);
+  }
+
+  @Override
+  public Class<?> resolveServiceClass(
+      Class<? extends RequestContext> requestContextClass) {
+    return getNext().resolveServiceClass(requestContextClass);
+  }
+
+  @Override
+  public Class<? extends ServiceLocator> resolveServiceLocator(
+      Method contextMethod, Method domainMethod) {
+    return getNext().resolveServiceLocator(contextMethod, domainMethod);
+  }
+
+  @Override
+  public String resolveTypeToken(Class<? extends BaseProxy> proxyType) {
+    return getNext().resolveTypeToken(proxyType);
+  }
+
+  @Override
+  public void setProperty(Object domainObject, String property,
+      Class<?> expectedType, Object value) {
+    getNext().setProperty(domainObject, property, expectedType, value);
+  }
+
+  @Override
+  public <T> Set<ConstraintViolation<T>> validate(T domainObject) {
+    return getNext().validate(domainObject);
+  }
+
+  /**
+   * Throw a fatal error up into the top-level processing code. This method
+   * should be used to provide diagnostic information that will help the
+   * end-developer track down problems when that data would expose
+   * implementation details of the server to the client.
+   * 
+   * @param e a throwable with more data, may be {@code null}
+   * @param message a printf-style format string
+   * @param args arguments for the message
+   * @throws UnexpectedException this method never returns normally
+   * @see #report(String, Object...)
+   */
+  protected final <T> T die(Throwable e, String message, Object... args)
+      throws UnexpectedException {
+    String msg = String.format(message, args);
+    log.log(Level.SEVERE, msg, e);
+    throw new UnexpectedException(msg, e);
+  }
+
+  /**
+   * Returns the top-most service layer. General-purpose ServiceLayer decorators
+   * should use the instance provided by {@code getTop()} when calling public
+   * methods on the ServiceLayer API to allow higher-level decorators to
+   * override behaviors built into lower-level decorators.
+   * 
+   * @return the ServiceLayer returned by
+   *         {@link #create(ServiceLayerDecorator...)}
+   */
+  protected final ServiceLayer getTop() {
+    return top;
+  }
+
+  /**
+   * Report an exception thrown by code that is under the control of the
+   * end-developer.
+   * 
+   * @param userGeneratedException an {@link InvocationTargetException} thrown
+   *          by an invocation of user-provided code
+   * @throws ReportableException this method never returns normally
+   */
+  protected final <T> T report(InvocationTargetException userGeneratedException)
+      throws ReportableException {
+    throw new ReportableException(userGeneratedException.getCause());
+  }
+
+  /**
+   * Return a message to the client. This method should not include any data
+   * that was not sent to the server by the client to avoid leaking data.
+   * 
+   * @param msg a printf-style format string
+   * @param args arguments for the message
+   * @throws ReportableException this method never returns normally
+   * @see #die(Throwable, String, Object...)
+   */
+  protected final <T> T report(String msg, Object... args)
+      throws ReportableException {
+    throw new ReportableException(String.format(msg, args));
+  }
+
+  /**
+   * Retrieves the next service layer. Used only by the server-package code and
+   * accessed by used code via {@code super.doSomething()}.
+   */
+  final ServiceLayer getNext() {
+    if (next == null) {
+      // Unexpected, all methods should be implemented by some layer
+      throw new UnsupportedOperationException();
+    }
+    return next;
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/SignatureAdapter.java b/user/src/com/google/web/bindery/requestfactory/server/SignatureAdapter.java
new file mode 100644
index 0000000..e33cf0b
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/SignatureAdapter.java
@@ -0,0 +1,85 @@
+/*
+ * 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.requestfactory.server;
+
+import com.google.gwt.dev.asm.signature.SignatureVisitor;
+
+/**
+ * An empty implementation of SignatureVisitor, used by
+ * {@link RequestFactoryInterfaceValidator}. This is a copy of the dev package's
+ * EmptySignatureVisitor.
+ */
+class SignatureAdapter implements SignatureVisitor {
+
+  private static final SignatureAdapter ignore = new SignatureAdapter();
+
+  public SignatureVisitor visitArrayType() {
+    return ignore;
+  }
+
+  public void visitBaseType(char descriptor) {
+  }
+
+  public SignatureVisitor visitClassBound() {
+    return ignore;
+  }
+
+  public void visitClassType(String name) {
+  }
+
+  public void visitEnd() {
+  }
+
+  public SignatureVisitor visitExceptionType() {
+    return ignore;
+  }
+
+  public void visitFormalTypeParameter(String name) {
+  }
+
+  public void visitInnerClassType(String name) {
+  }
+
+  public SignatureVisitor visitInterface() {
+    return ignore;
+  }
+
+  public SignatureVisitor visitInterfaceBound() {
+    return ignore;
+  }
+
+  public SignatureVisitor visitParameterType() {
+    return ignore;
+  }
+
+  public SignatureVisitor visitReturnType() {
+    return ignore;
+  }
+
+  public SignatureVisitor visitSuperclass() {
+    return ignore;
+  }
+
+  public void visitTypeArgument() {
+  }
+
+  public SignatureVisitor visitTypeArgument(char wildcard) {
+    return ignore;
+  }
+
+  public void visitTypeVariable(String name) {
+  }
+}
\ No newline at end of file
diff --git a/user/src/com/google/web/bindery/requestfactory/server/SimpleRequestProcessor.java b/user/src/com/google/web/bindery/requestfactory/server/SimpleRequestProcessor.java
new file mode 100644
index 0000000..a763dbb
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/SimpleRequestProcessor.java
@@ -0,0 +1,556 @@
+/*
+ * 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.requestfactory.server;
+
+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;
+import com.google.web.bindery.requestfactory.shared.Request;
+import com.google.web.bindery.requestfactory.shared.ServerFailure;
+import com.google.web.bindery.requestfactory.shared.WriteOperation;
+import com.google.web.bindery.requestfactory.shared.impl.BaseProxyCategory;
+import com.google.web.bindery.requestfactory.shared.impl.Constants;
+import com.google.web.bindery.requestfactory.shared.impl.EntityCodex;
+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;
+import com.google.web.bindery.requestfactory.shared.messages.RequestMessage;
+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 java.io.UnsupportedEncodingException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.validation.ConstraintViolation;
+
+/**
+ * Processes request payloads from a RequestFactory client. This implementation
+ * is stateless. A single instance may be reused and is thread-safe.
+ */
+public class SimpleRequestProcessor {
+  /**
+   * This parameterization is so long, it improves readability to have a
+   * specific type.
+   */
+  @SuppressWarnings("serial")
+  static class IdToEntityMap extends
+      HashMap<SimpleProxyId<?>, AutoBean<? extends BaseProxy>> {
+  }
+
+  /**
+   * Allows the creation of properly-configured AutoBeans without having to
+   * create an AutoBeanFactory with the desired annotations.
+   */
+  static final Configuration CONFIGURATION = new Configuration.Builder().setCategories(
+      EntityProxyCategory.class, ValueProxyCategory.class,
+      BaseProxyCategory.class).setNoWrap(EntityProxyId.class).build();
+
+  /**
+   * Vends message objects.
+   */
+  static final MessageFactory FACTORY = AutoBeanFactorySource.create(MessageFactory.class);
+
+  static String fromBase64(String encoded) {
+    try {
+      return new String(Base64Utils.fromBase64(encoded), "UTF-8");
+    } catch (UnsupportedEncodingException e) {
+      throw new UnexpectedException(e);
+    }
+  }
+
+  static String toBase64(String data) {
+    try {
+      return Base64Utils.toBase64(data.getBytes("UTF-8"));
+    } catch (UnsupportedEncodingException e) {
+      throw new UnexpectedException(e);
+    }
+  }
+
+  private ExceptionHandler exceptionHandler = new DefaultExceptionHandler();
+  private final ServiceLayer service;
+
+  public SimpleRequestProcessor(ServiceLayer serviceLayer) {
+    this.service = serviceLayer;
+  }
+
+  /**
+   * Process a payload sent by a RequestFactory client.
+   * 
+   * @param payload the payload sent by the client
+   * @return a payload to return to the client
+   */
+  public String process(String payload) {
+    RequestMessage req = AutoBeanCodex.decode(FACTORY, RequestMessage.class,
+        payload).as();
+    AutoBean<ResponseMessage> responseBean = FACTORY.response();
+    try {
+      process(req, responseBean.as());
+    } catch (ReportableException e) {
+      e.printStackTrace();
+      // Create a new response envelope, since the state is unknown
+      responseBean = FACTORY.response();
+      responseBean.as().setGeneralFailure(createFailureMessage(e).as());
+    }
+    // Return a JSON-formatted payload
+    return AutoBeanCodex.encode(responseBean).getPayload();
+  }
+
+  public void setExceptionHandler(ExceptionHandler exceptionHandler) {
+    this.exceptionHandler = exceptionHandler;
+  }
+
+  /**
+   * Encode a list of objects into a self-contained message that can be used for
+   * out-of-band communication.
+   */
+  <T> Splittable createOobMessage(List<T> domainValues) {
+    RequestState state = new RequestState(service);
+
+    List<Splittable> encodedValues = new ArrayList<Splittable>(
+        domainValues.size());
+    for (T domainValue : domainValues) {
+      Object clientValue;
+      if (domainValue == null) {
+        clientValue = null;
+      } else {
+        Class<?> clientType = service.resolveClientType(domainValue.getClass(),
+            BaseProxy.class, true);
+        clientValue = state.getResolver().resolveClientValue(domainValue,
+            clientType, Collections.<String> emptySet());
+      }
+      encodedValues.add(EntityCodex.encode(state, clientValue));
+    }
+
+    IdToEntityMap map = new IdToEntityMap();
+    map.putAll(state.beans);
+    List<OperationMessage> operations = new ArrayList<OperationMessage>();
+    createReturnOperations(operations, state, map);
+
+    InvocationMessage invocation = FACTORY.invocation().as();
+    invocation.setParameters(encodedValues);
+
+    AutoBean<RequestMessage> bean = FACTORY.request();
+    RequestMessage resp = bean.as();
+    resp.setInvocations(Collections.singletonList(invocation));
+    resp.setOperations(operations);
+    return AutoBeanCodex.encode(bean);
+  }
+
+  /**
+   * Decode an out-of-band message.
+   */
+  <T> List<T> decodeOobMessage(Class<T> domainClass, Splittable payload) {
+    Class<?> proxyType = service.resolveClientType(domainClass,
+        BaseProxy.class, true);
+    RequestState state = new RequestState(service);
+    RequestMessage message = AutoBeanCodex.decode(FACTORY,
+        RequestMessage.class, payload).as();
+    processOperationMessages(state, message);
+    List<Object> decoded = decodeInvocationArguments(state,
+        message.getInvocations().get(0).getParameters(),
+        new Class<?>[] {proxyType}, new Type[] {domainClass});
+
+    @SuppressWarnings("unchecked")
+    List<T> toReturn = (List<T>) decoded;
+    return toReturn;
+  }
+
+  /**
+   * Main processing method.
+   */
+  void process(RequestMessage req, ResponseMessage resp) {
+    final RequestState source = new RequestState(service);
+    // Apply operations
+    processOperationMessages(source, req);
+
+    // Validate entities
+    List<ViolationMessage> errorMessages = validateEntities(source);
+
+    if (!errorMessages.isEmpty()) {
+      resp.setViolations(errorMessages);
+      return;
+    }
+
+    RequestState returnState = new RequestState(source);
+
+    // Invoke methods
+    List<Splittable> invocationResults = new ArrayList<Splittable>();
+    List<Boolean> invocationSuccess = new ArrayList<Boolean>();
+    processInvocationMessages(source, req, invocationResults,
+        invocationSuccess, returnState);
+
+    // Store return objects
+    List<OperationMessage> operations = new ArrayList<OperationMessage>();
+    IdToEntityMap toProcess = new IdToEntityMap();
+    toProcess.putAll(source.beans);
+    toProcess.putAll(returnState.beans);
+    createReturnOperations(operations, returnState, toProcess);
+
+    assert invocationResults.size() == invocationSuccess.size();
+    if (!invocationResults.isEmpty()) {
+      resp.setInvocationResults(invocationResults);
+      resp.setStatusCodes(invocationSuccess);
+    }
+    if (!operations.isEmpty()) {
+      resp.setOperations(operations);
+    }
+  }
+
+  private AutoBean<ServerFailureMessage> createFailureMessage(
+      ReportableException e) {
+    ServerFailure failure = exceptionHandler.createServerFailure(e.getCause() == null
+        ? e : e.getCause());
+    AutoBean<ServerFailureMessage> bean = FACTORY.failure();
+    ServerFailureMessage msg = bean.as();
+    msg.setExceptionType(failure.getExceptionType());
+    msg.setMessage(failure.getMessage());
+    msg.setStackTrace(failure.getStackTraceString());
+    msg.setFatal(failure.isFatal());
+    return bean;
+  }
+
+  private void createReturnOperations(List<OperationMessage> operations,
+      RequestState returnState, IdToEntityMap toProcess) {
+    for (Map.Entry<SimpleProxyId<?>, AutoBean<? extends BaseProxy>> entry : toProcess.entrySet()) {
+      SimpleProxyId<?> id = entry.getKey();
+
+      AutoBean<? extends BaseProxy> bean = entry.getValue();
+      Object domainObject = bean.getTag(Constants.DOMAIN_OBJECT);
+      WriteOperation writeOperation;
+
+      if (id.isEphemeral()) {
+        // See if the entity has been persisted in the meantime
+        returnState.getResolver().resolveClientValue(domainObject,
+            id.getProxyClass(), Collections.<String> emptySet());
+      }
+
+      if (id.isEphemeral() || id.isSynthetic() || domainObject == null) {
+        // If the object isn't persistent, there's no reason to send an update
+        writeOperation = null;
+      } else if (!service.isLive(domainObject)) {
+        writeOperation = WriteOperation.DELETE;
+      } else if (id.wasEphemeral()) {
+        writeOperation = WriteOperation.PERSIST;
+      } else {
+        writeOperation = WriteOperation.UPDATE;
+      }
+
+      Splittable version = null;
+      if (writeOperation == WriteOperation.PERSIST
+          || writeOperation == WriteOperation.UPDATE) {
+        /*
+         * If we're sending an operation, the domain object must be persistent.
+         * This means that it must also have a non-null version.
+         */
+        Object domainVersion = service.getVersion(domainObject);
+        if (domainVersion == null) {
+          throw new UnexpectedException("The persisted entity with id "
+              + service.getId(domainObject) + " has a null version", null);
+        }
+        version = returnState.flatten(domainVersion);
+      }
+
+      boolean inResponse = bean.getTag(Constants.IN_RESPONSE) != null;
+
+      /*
+       * Don't send any data back to the client for an update on an object that
+       * isn't part of the response payload when the client's version matches
+       * the domain version.
+       */
+      if (WriteOperation.UPDATE.equals(writeOperation) && !inResponse) {
+        String previousVersion = bean.<String> getTag(Constants.VERSION_PROPERTY_B64);
+        if (version != null && previousVersion != null
+            && version.equals(fromBase64(previousVersion))) {
+          continue;
+        }
+      }
+
+      OperationMessage op = FACTORY.operation().as();
+
+      /*
+       * Send a client id if the id is ephemeral or was previously associated
+       * with a client id.
+       */
+      if (id.wasEphemeral()) {
+        op.setClientId(id.getClientId());
+      }
+
+      op.setOperation(writeOperation);
+
+      // Only send properties for entities that are part of the return graph
+      if (inResponse) {
+        Map<String, Splittable> propertyMap = new LinkedHashMap<String, Splittable>();
+        // Add all non-null properties to the serialized form
+        Map<String, Object> diff = AutoBeanUtils.getAllProperties(bean);
+        for (Map.Entry<String, Object> d : diff.entrySet()) {
+          Object value = d.getValue();
+          if (value != null) {
+            propertyMap.put(d.getKey(), EntityCodex.encode(returnState, value));
+          }
+        }
+        op.setPropertyMap(propertyMap);
+      }
+
+      if (!id.isEphemeral() && !id.isSynthetic()) {
+        // Send the server address only for persistent objects
+        op.setServerId(toBase64(id.getServerId()));
+      }
+
+      if (id.isSynthetic()) {
+        op.setStrength(Strength.SYNTHETIC);
+        op.setSyntheticId(id.getSyntheticId());
+      } else if (id.isEphemeral()) {
+        op.setStrength(Strength.EPHEMERAL);
+      }
+
+      op.setTypeToken(service.resolveTypeToken(id.getProxyClass()));
+      if (version != null) {
+        op.setVersion(toBase64(version.getPayload()));
+      }
+
+      operations.add(op);
+    }
+  }
+
+  /**
+   * Decode the arguments to pass into the domain method. If the domain method
+   * is not static, the instance object will be in the 0th position.
+   */
+  private List<Object> decodeInvocationArguments(RequestState source,
+      InvocationMessage invocation, Method contextMethod) {
+    boolean isStatic = Request.class.isAssignableFrom(contextMethod.getReturnType());
+    int baseLength = contextMethod.getParameterTypes().length;
+    int length = baseLength + (isStatic ? 0 : 1);
+    int offset = isStatic ? 0 : 1;
+    Class<?>[] contextArgs = new Class<?>[length];
+    Type[] genericArgs = new Type[length];
+
+    if (!isStatic) {
+      genericArgs[0] = TypeUtils.getSingleParameterization(
+          InstanceRequest.class, contextMethod.getGenericReturnType());
+      contextArgs[0] = TypeUtils.ensureBaseType(genericArgs[0]);
+    }
+    System.arraycopy(contextMethod.getParameterTypes(), 0, contextArgs, offset,
+        baseLength);
+    System.arraycopy(contextMethod.getGenericParameterTypes(), 0, genericArgs,
+        offset, baseLength);
+
+    List<Object> args = decodeInvocationArguments(source,
+        invocation.getParameters(), contextArgs, genericArgs);
+    return args;
+  }
+
+  /**
+   * Handles instance invocations as the instance at the 0th parameter.
+   */
+  private List<Object> decodeInvocationArguments(RequestState source,
+      List<Splittable> parameters, Class<?>[] contextArgs, Type[] genericArgs) {
+    if (parameters == null) {
+      // Can't return Collections.emptyList() because this must be mutable
+      return new ArrayList<Object>();
+    }
+
+    assert parameters.size() == contextArgs.length;
+    List<Object> args = new ArrayList<Object>(contextArgs.length);
+    for (int i = 0, j = contextArgs.length; i < j; i++) {
+      Class<?> type = contextArgs[i];
+      Class<?> elementType = null;
+      Splittable split;
+      if (Collection.class.isAssignableFrom(type)) {
+        elementType = TypeUtils.ensureBaseType(TypeUtils.getSingleParameterization(
+            Collection.class, genericArgs[i]));
+        split = parameters.get(i);
+      } else {
+        split = parameters.get(i);
+      }
+      Object arg = EntityCodex.decode(source, type, elementType, split);
+      arg = source.getResolver().resolveDomainValue(arg,
+          !EntityProxyId.class.equals(contextArgs[i]));
+      args.add(arg);
+    }
+
+    return args;
+  }
+
+  private void processInvocationMessages(RequestState state,
+      RequestMessage req, List<Splittable> results, List<Boolean> success,
+      RequestState returnState) {
+    List<InvocationMessage> invocations = req.getInvocations();
+    if (invocations == null) {
+      // No method invocations which can happen via RequestContext.fire()
+      return;
+    }
+    for (InvocationMessage invocation : invocations) {
+      try {
+        // Find the Method
+        String[] operation = invocation.getOperation().split("::");
+        Method contextMethod = service.resolveRequestContextMethod(
+            operation[0], operation[1]);
+        if (contextMethod == null) {
+          throw new UnexpectedException("Cannot resolve operation "
+              + invocation.getOperation(), null);
+        }
+        Method domainMethod = service.resolveDomainMethod(contextMethod);
+        if (domainMethod == null) {
+          throw new UnexpectedException("Cannot resolve domain method "
+              + invocation.getOperation(), null);
+        }
+
+        // Compute the arguments
+        List<Object> args = decodeInvocationArguments(state, invocation,
+            contextMethod);
+        // Possibly use a ServiceLocator
+        if (service.requiresServiceLocator(contextMethod, domainMethod)) {
+          Object serviceInstance = service.createServiceInstance(contextMethod,
+              domainMethod);
+          args.add(0, serviceInstance);
+        }
+        // Invoke it
+        Object returnValue = service.invoke(domainMethod, args.toArray());
+
+        // Convert domain object to client object
+        Type requestReturnType = service.getRequestReturnType(contextMethod);
+        returnValue = state.getResolver().resolveClientValue(returnValue,
+            requestReturnType, invocation.getPropertyRefs());
+
+        // Convert the client object to a string
+        results.add(EntityCodex.encode(returnState, returnValue));
+        success.add(true);
+      } catch (ReportableException e) {
+        results.add(AutoBeanCodex.encode(createFailureMessage(e)));
+        success.add(false);
+      }
+    }
+  }
+
+  private void processOperationMessages(final RequestState state,
+      RequestMessage req) {
+    List<OperationMessage> operations = req.getOperations();
+    if (operations == null) {
+      return;
+    }
+
+    List<AutoBean<? extends BaseProxy>> beans = state.getBeansForPayload(operations);
+    assert operations.size() == beans.size();
+
+    Iterator<OperationMessage> itOp = operations.iterator();
+    for (AutoBean<? extends BaseProxy> bean : beans) {
+      OperationMessage operation = itOp.next();
+      // Save the client's version information to reduce payload size later
+      bean.setTag(Constants.VERSION_PROPERTY_B64, operation.getVersion());
+
+      // Load the domain object with properties, if it exists
+      final Object domain = bean.getTag(Constants.DOMAIN_OBJECT);
+      if (domain != null) {
+        // Apply any property updates
+        final Map<String, Splittable> flatValueMap = operation.getPropertyMap();
+        if (flatValueMap != null) {
+          bean.accept(new AutoBeanVisitor() {
+            @Override
+            public boolean visitReferenceProperty(String propertyName,
+                AutoBean<?> value, PropertyContext ctx) {
+              // containsKey to distinguish null from unknown
+              if (flatValueMap.containsKey(propertyName)) {
+                Class<?> elementType = ctx instanceof CollectionPropertyContext
+                    ? ((CollectionPropertyContext) ctx).getElementType() : null;
+                Object newValue = EntityCodex.decode(state, ctx.getType(),
+                    elementType, flatValueMap.get(propertyName));
+                Object resolved = state.getResolver().resolveDomainValue(
+                    newValue, false);
+                service.setProperty(domain, propertyName,
+                    service.resolveDomainClass(ctx.getType()), resolved);
+              }
+              return false;
+            }
+
+            @Override
+            public boolean visitValueProperty(String propertyName,
+                Object value, PropertyContext ctx) {
+              if (flatValueMap.containsKey(propertyName)) {
+                Splittable split = flatValueMap.get(propertyName);
+                Object newValue = ValueCodex.decode(ctx.getType(), split);
+                Object resolved = state.getResolver().resolveDomainValue(
+                    newValue, false);
+                service.setProperty(domain, propertyName, ctx.getType(),
+                    resolved);
+              }
+              return false;
+            }
+          });
+        }
+      }
+    }
+  }
+
+  /**
+   * Validate all of the entities referenced in a RequestState.
+   */
+  private List<ViolationMessage> validateEntities(RequestState source) {
+    List<ViolationMessage> errorMessages = new ArrayList<ViolationMessage>();
+    for (Map.Entry<SimpleProxyId<?>, AutoBean<? extends BaseProxy>> entry : source.beans.entrySet()) {
+      AutoBean<? extends BaseProxy> bean = entry.getValue();
+      Object domainObject = bean.getTag(Constants.DOMAIN_OBJECT);
+
+      // The object could have been deleted
+      if (domainObject != null) {
+        Set<ConstraintViolation<Object>> errors = service.validate(domainObject);
+        if (errors != null && !errors.isEmpty()) {
+          SimpleProxyId<?> id = entry.getKey();
+          for (ConstraintViolation<Object> error : errors) {
+            ViolationMessage message = FACTORY.violation().as();
+            message.setClientId(id.getClientId());
+            message.setMessage(error.getMessage());
+            message.setPath(error.getPropertyPath().toString());
+            if (id.isEphemeral()) {
+              message.setClientId(id.getClientId());
+              message.setStrength(Strength.EPHEMERAL);
+            } else {
+              message.setServerId(toBase64(id.getServerId()));
+            }
+            message.setTypeToken(service.resolveTypeToken(id.getProxyClass()));
+            errorMessages.add(message);
+          }
+        }
+      }
+    }
+    return errorMessages;
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/UnexpectedException.java b/user/src/com/google/web/bindery/requestfactory/server/UnexpectedException.java
new file mode 100644
index 0000000..8ca35ae
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/UnexpectedException.java
@@ -0,0 +1,30 @@
+/*
+ * 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.requestfactory.server;
+
+/**
+ * Encapsulates exceptions that indicate something went wrong in RequestFactory
+ * code.
+ */
+class UnexpectedException extends RuntimeException {
+  public UnexpectedException(String msg, Throwable cause) {
+    super(msg, cause);
+  }
+
+  public UnexpectedException(Throwable cause) {
+    super(cause);
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/impl/FindService.java b/user/src/com/google/web/bindery/requestfactory/server/impl/FindService.java
new file mode 100644
index 0000000..66d27ba
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/impl/FindService.java
@@ -0,0 +1,32 @@
+/*
+ * 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.requestfactory.server.impl;
+
+/**
+ * Server side service to support a generic find method.
+ */
+public class FindService {
+
+  /**
+   * For now, a simple implementation of find will work.
+   * 
+   * @param entityInstance an entity instance
+   * @return the passed-in entity instance
+   */
+  public static <T> T find(T entityInstance) {
+    return entityInstance;
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/package-info.java b/user/src/com/google/web/bindery/requestfactory/server/package-info.java
new file mode 100644
index 0000000..d14e6b9
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/package-info.java
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+/**
+ * Server side classes for mediating between the client side and the persistent
+ * datastore.
+ * 
+ * This package contains classes that can receive client side read and write
+ * requests in the JSON format, perform the necessary operations on the
+ * persistent datastore, and return the results in JSON format.
+ * 
+ * @since GWT 2.1
+ */
+@com.google.gwt.util.PreventSpuriousRebuilds
+package com.google.web.bindery.requestfactory.server;
+
diff --git a/user/src/com/google/web/bindery/requestfactory/server/testing/InProcessRequestTransport.java b/user/src/com/google/web/bindery/requestfactory/server/testing/InProcessRequestTransport.java
new file mode 100644
index 0000000..7b8c99e
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/testing/InProcessRequestTransport.java
@@ -0,0 +1,66 @@
+/*
+ * 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.requestfactory.server.testing;
+
+import com.google.web.bindery.requestfactory.server.SimpleRequestProcessor;
+import com.google.web.bindery.requestfactory.shared.RequestTransport;
+import com.google.web.bindery.requestfactory.shared.ServerFailure;
+
+/**
+ * A RequestTransport that calls a {@link SimpleRequestProcessor}. This test can
+ * be used for end-to-end tests of RequestFactory service methods in non-GWT
+ * test suites.
+ * 
+ * <pre>
+ * ServiceLayer serviceLayer = ServiceLayer.create();
+ * SimpleRequestProcessor processor = new SimpleRequestProcessor(serviceLayer);
+ * EventBus eventBus = new SimpleEventBus();
+ * MyRequestFactory f = RequestFactorySource.create(MyRequestFactory.class);
+ * f.initialize(eventBus, new InProcessRequestTransport(processor));
+ * </pre>
+ * 
+ * @see com.google.web.bindery.vm.RequestFactorySource
+ * @see com.google.web.bindery.requestfactory.server.ServiceLayer#create(com.google.gwt.requestfactory.server.ServiceLayerDecorator...)
+ *      ServiceLayer.create()
+ * @see com.google.gwt.event.shared.SimpleEventBus SimpleEventBus
+ * @see SimpleRequestProcessor
+ */
+public class InProcessRequestTransport implements RequestTransport {
+  private static final boolean DUMP_PAYLOAD = Boolean.getBoolean("gwt.rpc.dumpPayload");
+  private final SimpleRequestProcessor processor;
+
+  public InProcessRequestTransport(SimpleRequestProcessor processor) {
+    this.processor = processor;
+  }
+
+  public void send(String payload, TransportReceiver receiver) {
+    String result;
+    try {
+      if (DUMP_PAYLOAD) {
+        System.out.println(">>> " + payload);
+      }
+      result = processor.process(payload);
+      if (DUMP_PAYLOAD) {
+        System.out.println("<<< " + result);
+      }
+    } catch (RuntimeException e) {
+      e.printStackTrace();
+      receiver.onTransportFailure(new ServerFailure(e.getMessage()));
+      return;
+    }
+    receiver.onTransportSuccess(result);
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/BaseProxy.java b/user/src/com/google/web/bindery/requestfactory/shared/BaseProxy.java
new file mode 100644
index 0000000..0dd2900
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/BaseProxy.java
@@ -0,0 +1,27 @@
+/*
+ * 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.requestfactory.shared;
+
+/**
+ * The root type from which all client-side proxy objects are derived. Users
+ * should not extend the BaseProxy type directly, but use either
+ * {@link EntityProxy} or {@link ValueProxy}.
+ * 
+ * @see EntityProxy
+ * @see ValueProxy
+ */
+public interface BaseProxy {
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/DefaultProxyStore.java b/user/src/com/google/web/bindery/requestfactory/shared/DefaultProxyStore.java
new file mode 100644
index 0000000..44dcbce
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/DefaultProxyStore.java
@@ -0,0 +1,89 @@
+/*
+ * 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.requestfactory.shared;
+
+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;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * An in-memory ProxyStore store that can encode its state as a JSON object
+ * literal.
+ */
+public class DefaultProxyStore implements ProxyStore {
+  /**
+   * Provide a little bit of future-proofing to at least detect an encoded
+   * payload that can't be decoded.
+   */
+  private static final String EXPECTED_VERSION = "211";
+  private final AutoBean<OperationMessage> messageBean;
+  private final Map<String, Splittable> map;
+
+  /**
+   * Construct an empty DefaultProxyStore.
+   */
+  public DefaultProxyStore() {
+    messageBean = MessageFactoryHolder.FACTORY.operation();
+    map = new HashMap<String, Splittable>();
+
+    OperationMessage message = messageBean.as();
+    message.setPropertyMap(map);
+    message.setVersion(EXPECTED_VERSION);
+  }
+
+  /**
+   * Construct a DefaultProxyStore using the a value returned from
+   * {@link #encode()}.
+   * 
+   * @param payload a String previously returned from {@link #encode()}
+   * @throws IllegalArgumentException if the payload cannot be parsed
+   */
+  public DefaultProxyStore(String payload) throws IllegalArgumentException {
+    messageBean = AutoBeanCodex.decode(MessageFactoryHolder.FACTORY,
+        OperationMessage.class, payload);
+
+    OperationMessage message = messageBean.as();
+    if (!EXPECTED_VERSION.equals(message.getVersion())) {
+      throw new IllegalArgumentException(
+          "Unexpected version string in payload " + message.getVersion());
+    }
+    map = message.getPropertyMap();
+  }
+
+  /**
+   * Return a JSON object literal with the contents of the store.
+   */
+  public String encode() {
+    return AutoBeanCodex.encode(messageBean).getPayload();
+  }
+
+  public Splittable get(String key) {
+    return map.get(key);
+  }
+
+  public int nextId() {
+    return map.size();
+  }
+
+  public void put(String key, Splittable value) {
+    map.put(key, value);
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/EntityProxy.java b/user/src/com/google/web/bindery/requestfactory/shared/EntityProxy.java
new file mode 100644
index 0000000..eaab4bc
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/EntityProxy.java
@@ -0,0 +1,37 @@
+/*
+ * 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.requestfactory.shared;
+
+/**
+ * A proxy for a server-side domain object.
+ */
+public interface EntityProxy extends BaseProxy {
+  /**
+   * Returns the {@link EntityProxyId} that identifies a particular instance of
+   * the type proxied by the receiver.
+   * <p>
+   * An id returned by a proxy newly created by {@link RequestContext#create}
+   * {@link Object#equals(Object) equals} those returned later by proxies to the
+   * persisted object.
+   * <p>
+   * Subtypes should override to declare they return a stable id of their own
+   * type, to allow type safe use of the request objects returned by
+   * {@link RequestFactory#find(EntityProxyId)}.
+   * 
+   * @return an {@link EntityProxyId} instance
+   */
+  EntityProxyId<?> stableId();
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/EntityProxyChange.java b/user/src/com/google/web/bindery/requestfactory/shared/EntityProxyChange.java
new file mode 100644
index 0000000..ab013cb
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/EntityProxyChange.java
@@ -0,0 +1,126 @@
+/*
+ * 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.requestfactory.shared;
+
+import com.google.web.bindery.event.shared.Event;
+import com.google.web.bindery.event.shared.EventBus;
+import com.google.web.bindery.event.shared.HandlerRegistration;
+
+/**
+ * Event posted by a {@link RequestFactory} when changes to an entity are
+ * detected. Provides a {@link WriteOperation} value describing the change, and
+ * the {@link EntityProxyId} of the entity in question.
+ * <p>
+ * EntityProxyChange events are posted with the relevant EntityProxy
+ * Class as their source, allowing handlers to register for changes only
+ * of the type they care about via
+ * {@link #registerForProxyType(EventBus, Class, Handler)}.
+ * 
+ * @param <P> the type of the proxy
+ * 
+ * @see RequestFactory#initialize(EventBus)
+ * @see RequestFactory#find(EntityProxyId)
+ */
+public class EntityProxyChange<P extends EntityProxy> extends
+    Event<EntityProxyChange.Handler<P>> {
+
+  /**
+   * Implemented by methods that handle EntityProxyChange events.
+   *
+   * @param <P> the proxy type
+   */
+  public interface Handler<P extends EntityProxy> {
+    /**
+     * Called when an {@link EntityProxyChange} event is fired.
+     *
+     * @param event an {@link EntityProxyChange} instance
+     */
+    void onProxyChange(EntityProxyChange<P> event);
+  }
+
+  private static final Type<EntityProxyChange.Handler<?>> TYPE = new Type<EntityProxyChange.Handler<?>>();
+
+  /**
+   * Register a handler for a EntityProxyChange events for a particular proxy
+   * class.
+   *
+   * @param eventBus the {@link EventBus}
+   * @param proxyType a Class instance of type P
+   * @param handler an {@link EntityProxyChange.Handler} instance of type P
+   * @return an {@link EntityProxy} instance
+   */
+  public static <P extends EntityProxy> HandlerRegistration registerForProxyType(
+      EventBus eventBus, Class<P> proxyType,
+      EntityProxyChange.Handler<P> handler) {
+    return eventBus.addHandlerToSource(TYPE, proxyType, handler);
+  }
+
+  private P proxy;
+
+  private WriteOperation writeOperation;
+
+  /**
+   * Constructs an EntityProxyChange object.
+   *
+   * @param proxy an {@link EntityProxy} instance of type P
+   * @param writeOperation a {@link WriteOperation} instance
+   */
+  public EntityProxyChange(P proxy, WriteOperation writeOperation) {
+    this.proxy = proxy;
+    this.writeOperation = writeOperation;
+  }
+
+  /**
+   * Returns the type associated with this instance.
+   *
+   * @return an instance of {@link Event.Type Type} of type Handler&lt;P&gt
+   */
+  @SuppressWarnings({"unchecked", "rawtypes"})
+  @Override
+  public Event.Type<Handler<P>> getAssociatedType() {
+    /*
+     * The instance knows its handler is of type P, but the TYPE field itself
+     * does not, so we have to do an unsafe cast here.
+     */
+
+    return (Type) TYPE;
+  }
+
+  /**
+   * Returns an unpopulated copy of the changed proxy &mdash; all properties are
+   * undefined except its id.
+   *
+   * @return an instance of {@link EntityProxyId}&lt;P&gt;
+   */
+  @SuppressWarnings("unchecked")
+  public EntityProxyId<P> getProxyId() {
+    return (EntityProxyId<P>) proxy.stableId();
+  }
+
+  /**
+   * Returns the {@link WriteOperation} associated with this instance.
+   *
+   * @return a {@link WriteOperation} instance
+   */
+  public WriteOperation getWriteOperation() {
+    return writeOperation;
+  }
+
+  @Override
+  protected void dispatch(Handler<P> handler) {
+    handler.onProxyChange(this);
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/EntityProxyId.java b/user/src/com/google/web/bindery/requestfactory/shared/EntityProxyId.java
new file mode 100644
index 0000000..5a5bd4c
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/EntityProxyId.java
@@ -0,0 +1,36 @@
+/*
+ * 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.requestfactory.shared;
+
+/**
+ * A stable, opaque id of an {@link EntityProxy} that remains stable across
+ * updates, creates, deletes on the client.
+ * <p>
+ * In particular, an {@link EntityProxy} foo that is yet to be persisted and a
+ * copy of foo after being persisted have equal {@link EntityProxyId}.
+ * 
+ * @param <P> the entity type
+ */
+@ProxyFor(Object.class)
+public interface EntityProxyId<P extends EntityProxy> {
+
+  /**
+   * Returns the class of the proxy identified.
+   * 
+   * @return a Class object of type P
+   */
+  Class<P> getProxyClass();
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/InstanceRequest.java b/user/src/com/google/web/bindery/requestfactory/shared/InstanceRequest.java
new file mode 100644
index 0000000..fff1a78
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/InstanceRequest.java
@@ -0,0 +1,35 @@
+/*
+ * 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.requestfactory.shared;
+
+/**
+ * Used to call instance methods. Note that this does not extend {@link Request}
+ * &mdash; rather it vends one. There is no way to get an instance method's
+ * {@link Request#fire} without assigning an instance by calling {@link #using}.
+ * 
+ * @param <P> the instance type of BaseProxy
+ * @param <T> the type eventually returned by the method invocation
+ */
+public interface InstanceRequest<P extends BaseProxy, T> {
+
+  /**
+   * Provide the instance on which the request will be invoked.
+   * 
+   * @param instanceObject an {@link BaseProxy} instance of type P
+   * @return an instance of {@link Request}&lt;T&gt;
+   */
+  Request<T> using(P instanceObject);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/JsonRpcContent.java b/user/src/com/google/web/bindery/requestfactory/shared/JsonRpcContent.java
new file mode 100644
index 0000000..ad0f7c9
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/JsonRpcContent.java
@@ -0,0 +1,32 @@
+/*
+ * 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.requestfactory.shared;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <b>Experimental API, subject to change.</b> Applied to a Request method
+ * declaration to indicate that a particular parameter is used as the
+ * {@code request} portion of the JSON-RPC request. This is analogous to the
+ * payload body in a REST-style request.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.PARAMETER)
+public @interface JsonRpcContent {
+}
\ No newline at end of file
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/JsonRpcProxy.java b/user/src/com/google/web/bindery/requestfactory/shared/JsonRpcProxy.java
new file mode 100644
index 0000000..00ed40c
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/JsonRpcProxy.java
@@ -0,0 +1,23 @@
+/*
+ * 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.requestfactory.shared;
+
+/**
+ * <b>Experimental API, subject to change</b> Used instead of the
+ * {@link ProxyFor} annotation.
+ */
+public @interface JsonRpcProxy {
+}
\ No newline at end of file
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/JsonRpcService.java b/user/src/com/google/web/bindery/requestfactory/shared/JsonRpcService.java
new file mode 100644
index 0000000..2847fe9
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/JsonRpcService.java
@@ -0,0 +1,30 @@
+/*
+ * 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.requestfactory.shared;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <b>Experimental API, subject to change</b> Indicates that a RequestContext
+ * should be encoded as a JSON-RPC request.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface JsonRpcService {
+}
\ No newline at end of file
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/JsonRpcWireName.java b/user/src/com/google/web/bindery/requestfactory/shared/JsonRpcWireName.java
new file mode 100644
index 0000000..34969ce
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/JsonRpcWireName.java
@@ -0,0 +1,30 @@
+/*
+ * 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.requestfactory.shared;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * <b>Experimental API, subject to change</b> Provides the method name for a
+ * JSON-RPC invocation.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+public @interface JsonRpcWireName {
+  String value();
+
+  String version() default "";
+}
\ No newline at end of file
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/Locator.java b/user/src/com/google/web/bindery/requestfactory/shared/Locator.java
new file mode 100644
index 0000000..e2c5723
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/Locator.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.requestfactory.shared;
+
+/**
+ * A Locator allows entity types that do not conform to the RequestFactory
+ * entity protocol to be used. Instead of attempting to use a {@code findFoo()},
+ * {@code getId()}, and {@code getVersion()} declared in the domain entity type,
+ * an instance of a Locator will be created to provide implementations of these
+ * methods.
+ * <p>
+ * Locator subtypes must be default instantiable (i.e. public static types with
+ * a no-arg constructor). Instances of Locators may be retained and reused by
+ * the RequestFactory service layer.
+ * 
+ * @param <T> the type of domain object the Locator will operate on
+ * @param <I> the type of object the Locator expects to use as an id for the
+ *          domain object
+ * @see ProxyFor#locator()
+ */
+public abstract class Locator<T, I> {
+  /**
+   * Create a new instance of the requested type.
+   * 
+   * @param clazz the type of object to create
+   * @return the new instance of the domain type
+   */
+  public abstract T create(Class<? extends T> clazz);
+
+  /**
+   * Retrieve an object. May return {@code null} to indicate that the requested
+   * object could not be found.
+   * 
+   * @param clazz the type of object to retrieve
+   * @param id an id previously returned from {@link #getId(Object)}
+   * @return the requested object or {@code null} if it could not be found
+   */
+  public abstract T find(Class<? extends T> clazz, I id);
+
+  /**
+   * Returns the {@code T} type.
+   */
+  public abstract Class<T> getDomainType();
+
+  /**
+   * Returns a domain object to be used as the id for the given object. This
+   * method may return {@code null} if the object has not been persisted or
+   * should be treated as irretrievable.
+   * 
+   * @param domainObject the object to obtain an id for
+   * @return the object's id or {@code null}
+   */
+  public abstract I getId(T domainObject);
+
+  /**
+   * Returns the {@code I} type.
+   */
+  public abstract Class<I> getIdType();
+
+  /**
+   * Returns a domain object to be used as the version for the given object.
+   * This method may return {@code null} if the object has not been persisted or
+   * should be treated as irretrievable.
+   * 
+   * @param domainObject the object to obtain an id for
+   * @return the object's version or {@code null}
+   */
+  public abstract Object getVersion(T domainObject);
+
+  /**
+   * Returns a value indicating if the domain object should no longer be
+   * considered accessible. This method might return false if the record
+   * underlying the domain object had been deleted as a side-effect of
+   * processing a request.
+   * <p>
+   * The default implementation of this method uses {@link #getId(Object)} and
+   * {@link #find(Class, Object)} to determine if an object can be retrieved.
+   */
+  public boolean isLive(T domainObject) {
+    // Can't us Class.asSubclass() in web-mode code
+    @SuppressWarnings("unchecked")
+    Class<T> clazz = (Class<T>) domainObject.getClass();
+    return find(clazz, getId(domainObject)) != null;
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/LoggingRequest.java b/user/src/com/google/web/bindery/requestfactory/shared/LoggingRequest.java
new file mode 100644
index 0000000..3cdfdb3
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/LoggingRequest.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.requestfactory.shared;
+
+import com.google.web.bindery.requestfactory.server.Logging;
+
+/**
+ * "API Generated" request selector interface implemented by objects that give
+ * client access to the methods of {@link Logging}.
+ */
+@Service(Logging.class)
+public interface LoggingRequest extends RequestContext {
+
+  // TODO(unnurg): Pass a SerializableLogRecord here rather than it's
+  // serialized string.
+  /**
+   * Log a message on the server.
+   *
+   * @param serializedLogRecordString a json serialized LogRecord, as provided by
+   * {@link com.google.gwt.logging.client.JsonLogRecordClientUtil#logRecordAsJsonObject(LogRecord)}
+   * @return a Void {@link Request}
+   */
+  Request<Void> logMessage(String serializedLogRecordString);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/ProxyFor.java b/user/src/com/google/web/bindery/requestfactory/shared/ProxyFor.java
new file mode 100644
index 0000000..77dc4c2
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/ProxyFor.java
@@ -0,0 +1,42 @@
+/*
+ * 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.requestfactory.shared;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation on EntityProxy and ValueProxy classes specifying the domain
+ * (server-side) object type.
+ * 
+ * @see ProxyForName
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface ProxyFor {
+  /**
+   * The domain type that the proxy is mapped to.
+   */
+  Class<?> value();
+
+  /**
+   * An optional {@link Locator} that provides instances of the domain objects.
+   */
+  @SuppressWarnings("rawtypes")
+  Class<? extends Locator> locator() default Locator.class;
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/ProxyForName.java b/user/src/com/google/web/bindery/requestfactory/shared/ProxyForName.java
new file mode 100644
index 0000000..226e4f4
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/ProxyForName.java
@@ -0,0 +1,41 @@
+/*
+ * 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.requestfactory.shared;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation on EntityProxy classes specifying the domain (server-side) object
+ * type. This annotation can be used in place of {@link ProxyFor} if the domain
+ * object is not available to the GWT compiler or DevMode runtime.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface ProxyForName {
+  /**
+   * The name of the domain type that the proxy is mapped to.
+   */
+  String value();
+
+  /**
+   * An optional name of a {@link Locator} that provides instances of the domain
+   * objects.
+   */
+  String locator() default "";
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/ProxySerializer.java b/user/src/com/google/web/bindery/requestfactory/shared/ProxySerializer.java
new file mode 100644
index 0000000..59da4c7
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/ProxySerializer.java
@@ -0,0 +1,87 @@
+/*
+ * 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.requestfactory.shared;
+
+/**
+ * Serializes graphs of EntityProxy objects. A ProxySerializer is associated
+ * with an instance of a {@link ProxyStore} when it is created via
+ * {@link RequestFactory#getSerializer(ProxyStore)}.
+ * <p>
+ * The {@link EntityProxy#stableId()} of non-persisted (i.e. newly
+ * {@link RequestContext#create(Class) created}) {@link EntityProxy} instances
+ * are not stable.
+ * <p>
+ * To create a self-contained message that encapsulates a proxy:
+ * 
+ * <pre>
+ * RequestFactory myFactory = ...;
+ * MyFooProxy someProxy = ...;
+ * 
+ * DefaultProxyStore store = new DefaultProxyStore();
+ * ProxySerializer ser = myFactory.getSerializer(store);
+ * // More than one proxy could be serialized
+ * String key = ser.serialize(someProxy);
+ * // Create the flattened representation
+ * String payload = store.encode();
+ * </pre>
+ * 
+ * To recreate the object:
+ * 
+ * <pre>
+ * ProxyStore store = new DefaultProxyStore(payload);
+ * ProxySerializer ser = myFactory.getSerializer(store);
+ * MyFooProxy someProxy = ser.deserialize(MyFooProxy.class, key);
+ * </pre>
+ * 
+ * If two objects refer to different EntityProxy instances that have the same
+ * stableId(), the last mutable proxy encountered will be preferred, otherwise
+ * the first immutable proxy will be used.
+ * 
+ * @see DefaultProxyStore
+ */
+public interface ProxySerializer {
+  /**
+   * Recreate a proxy instance that was previously passed to
+   * {@link #serialize(BaseProxy)}.
+   * 
+   * @param <T> the type of proxy object to create
+   * @param proxyType the type of proxy object to create
+   * @param key a value previously returned from {@link #serialize(BaseProxy)}
+   * @return a new, immutable instance of the proxy or {@code null} if the data
+   *         needed to deserialize the proxy is not present in the ProxyStore
+   */
+  <T extends BaseProxy> T deserialize(Class<T> proxyType, String key);
+
+  /**
+   * Recreate a {@link EntityProxy} instance that was previously passed to
+   * {@link #serialize(BaseProxy)}.
+   * 
+   * @param <T> the type of proxy object to create
+   * @param id the {@link EntityProxyId} of the desired entity
+   * @return a new, immutable instance of the proxy or {@code null} if the data
+   *         needed to deserialize the proxy is not present in the ProxyStore
+   */
+  <T extends EntityProxy> T deserialize(EntityProxyId<T> id);
+
+  /**
+   * Store a proxy into the backing store.
+   * 
+   * @param proxy the proxy to store
+   * @return a key value that can be passed to
+   *         {@link #deserialize(Class, String)}
+   */
+  String serialize(BaseProxy proxy);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/ProxyStore.java b/user/src/com/google/web/bindery/requestfactory/shared/ProxyStore.java
new file mode 100644
index 0000000..ffb3f87
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/ProxyStore.java
@@ -0,0 +1,53 @@
+/*
+ * 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.requestfactory.shared;
+
+import com.google.web.bindery.autobean.shared.Splittable;
+
+/**
+ * A ProxyStore provides a {@link ProxySerializer} with access to a low-level
+ * persistence mechanism. The ProxyStore does not need to be able to interpret
+ * the data sent to it by the ProxySerializer; it is merely a wrapper around a
+ * persistence mechanism.
+ * 
+ * @see DefaultProxyStore
+ */
+public interface ProxyStore {
+  /**
+   * Called by {@link ProxySerializer} to retrieve a value previously provided
+   * to {@link #put(String, Splittable)}.
+   * 
+   * @param key the key
+   * @return the associated value or {@code null} if {@code key} is unknown
+   */
+  Splittable get(String key);
+
+  /**
+   * Returns a non-negative sequence number. The actual sequence of values
+   * returned by this method is unimportant, as long as the numbers in the
+   * sequence are unique.
+   */
+  int nextId();
+
+  /**
+   * Called by {@link ProxySerializer} to store a value.
+   * 
+   * @param key a key value that will be passed to {@link #get(String)}
+   * @param value the data to store
+   * @see Splittable#getPayload()
+   */
+  void put(String key, Splittable value);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/Receiver.java b/user/src/com/google/web/bindery/requestfactory/shared/Receiver.java
new file mode 100644
index 0000000..1bd6fed
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/Receiver.java
@@ -0,0 +1,60 @@
+/*
+ * 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.requestfactory.shared;
+
+import java.util.Set;
+
+/**
+ * Callback object for {@link Request#fire(Receiver)} and
+ * {@link RequestContext#fire(Receiver)}.
+ * 
+ * @param <V> value type
+ */
+public abstract class Receiver<V> {
+  /**
+   * Receives general failure notifications. The default implementation looks at
+   * {@link ServerFailure#isFatal()}, and throws a runtime exception with the
+   * failure object's error message if it is true.
+   * 
+   * @param error a {@link ServerFailure} instance
+   */
+  public void onFailure(ServerFailure error) {
+    if (error.isFatal()) {
+      throw new RuntimeException(error.getMessage());
+    }
+  }
+
+  /**
+   * Called when a Request has been successfully executed on the server.
+   * 
+   * @param response a response of type V
+   */
+  public abstract void onSuccess(V response);
+
+  /**
+   * Called if an object sent to the server could not be validated. The default
+   * implementation calls {@link #onFailure(ServerFailure)} if <code>errors
+   * </code> is not empty.
+   * 
+   * @param errors a Set of {@link Violation} instances
+   */
+  public void onViolation(Set<Violation> errors) {
+    if (!errors.isEmpty()) {
+      onFailure(new ServerFailure(
+          "The call failed on the server due to a ConstraintViolation"));
+    }
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/Request.java b/user/src/com/google/web/bindery/requestfactory/shared/Request.java
new file mode 100644
index 0000000..7a2edbe
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/Request.java
@@ -0,0 +1,53 @@
+/*
+ * 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.requestfactory.shared;
+
+/**
+ * Implemented by the request objects created by this factory.
+ * 
+ * @param <T> The return type of objects in the corresponding response.
+ */
+public interface Request<T> {
+
+  /**
+   * Submit this request. Failures will be reported through the global uncaught
+   * exception handler, if any.
+   */
+  void fire();
+
+  /**
+   * Convenience method equivalent to calling <code>to(...).fire()</code>.
+   *
+   * @param receiver a {@link Receiver} instance
+   */
+  void fire(Receiver<? super T> receiver);
+
+  /**
+   * Specify the object that will receive the result of the method invocation.
+   *
+   * @param receiver a {@link Receiver} instance
+   * @return a {@link RequestContext} instance
+   */
+  RequestContext to(Receiver<? super T> receiver);
+
+  /**
+   * Request additional reference properties to fetch with the return value.
+   *
+   * @param propertyRefs a list of reference property names as Strings
+   * @return a {@link Request} instance of type T
+   */
+  Request<T> with(String... propertyRefs);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/RequestContext.java b/user/src/com/google/web/bindery/requestfactory/shared/RequestContext.java
new file mode 100644
index 0000000..91e45d0
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/RequestContext.java
@@ -0,0 +1,78 @@
+/*
+ * 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.requestfactory.shared;
+
+/**
+ * The base interface for RequestFactory service endpoints.
+ */
+public interface RequestContext {
+  /**
+   * Returns a new mutable proxy that this request can carry to the server,
+   * perhaps to be persisted. If the object is succesfully persisted, a PERSIST
+   * event will be posted including the EntityProxyId of this proxy.
+   * 
+   * @param clazz a Class object of type T
+   * @return an {@link BaseProxy} instance of type T
+   */
+  <T extends BaseProxy> T create(Class<T> clazz);
+
+  /**
+   * Returns a mutable version of the proxy, whose mutations will accumulate in
+   * this context. Proxies reached via getters on this mutable proxy will also
+   * be mutable.
+   * 
+   * @param object an instance of type T
+   * @return an {@link EntityProxy} or {@link ValueProxy} instance of type T
+   */
+  <T extends BaseProxy> T edit(T object);
+
+  /**
+   * Send the accumulated changes and method invocations associated with the
+   * RequestContext.
+   * <p>
+   * If {@link Request#to(Receiver)} has not been called, this method will
+   * install a default receiver that will throw a RuntimeException if there is a
+   * server failure.
+   */
+  void fire();
+
+  /**
+   * For receiving errors or validation failures only.
+   * 
+   * @param receiver a {@link Receiver} instance
+   * @throws IllegalArgumentException if <code>receiver</code> is
+   *           <code>null</code>
+   */
+  void fire(Receiver<Void> receiver);
+
+  /**
+   * Returns true if any changes have been made to proxies mutable under this
+   * context. Note that vacuous changes &mdash; e.g. foo.setName(foo.getName()
+   * &mdash; will not trip the changed flag. Similarly, "unmaking" a change will
+   * clear the isChanged flag
+   * 
+   * <pre>
+   * String name = bar.getName();
+   * bar.setName("something else");
+   * assertTrue(context.isChanged());
+   * bar.setName(name);
+   * assertFalse(context.isChanged());
+   * </pre>
+   * 
+   * @return {@code true} if any changes have been made
+   */
+  boolean isChanged();
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/RequestFactory.java b/user/src/com/google/web/bindery/requestfactory/shared/RequestFactory.java
new file mode 100644
index 0000000..c265cd9
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/RequestFactory.java
@@ -0,0 +1,142 @@
+/*
+ * 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.requestfactory.shared;
+
+import com.google.web.bindery.event.shared.EventBus;
+
+/**
+ * Marker interface for the RequestFactory code generator.
+ * <p>
+ * <b>Limitations on the transferrability of types.</b> <br>
+ * RequestFactory currently supports the transfer of basic value types, entity
+ * types, and collections, with limitations. The basic value types supported are
+ * {@link String}, {@link Enum}, {@link Boolean}, {@link Character}, subtypes of
+ * {@link Number}, and {@link java.util.Date}. Any value type not included in
+ * this list may not be declared in the type signature of a service method, or
+ * {@link EntityProxy}. {@link java.util.Collection} types supported are
+ * {@link java.util.List} and {@link java.util.Set} with the restriction that a
+ * collection must be homogeneous and only hold one type of value.
+ * </p>
+ * <p>
+ * Polymorphism is not supported at this time. RequestFactory encoding and
+ * decoding requires exact knowledge of the concrete type. If a method declares
+ * a given type <code>T</code> as a parameter or return type, only
+ * <code>T</code>'s transferrable properties will be sent over the wire if it is
+ * a proxy, even if the underlying domain value contains extra fields, in
+ * effect, treating it as an instance of the supertype. Returning abstract
+ * supertypes of value types is not supported (e.g. Object, Enum, Number).
+ * </p>
+ * 
+ * @see com.google.web.bindery.requestfactory.server.testing.InProcessRequestTransport
+ */
+public interface RequestFactory {
+  /**
+   * The JSON content type String.
+   */
+  String JSON_CONTENT_TYPE_UTF8 = "application/json; charset=utf-8";
+
+  /**
+   * Return a request to find a fresh instance of the referenced proxy.
+   * 
+   * @param proxyId an {@link EntityProxyId} instance of type P
+   * @return a {@link Request} object
+   */
+  <P extends EntityProxy> Request<P> find(EntityProxyId<P> proxyId);
+
+  /**
+   * Returns the event bus this factory's events are posted on, which was set
+   * via {@link #initialize}.
+   * 
+   * @return the {@link EventBus} associated with this instance
+   */
+  EventBus getEventBus();
+
+  /**
+   * Get a {@link com.google.gwt.user.client.History} compatible token that
+   * represents the given class. It can be processed by
+   * {@link #getProxyClass(String)}
+   * 
+   * @param clazz a Class object for an {@link EntityProxy} subclass
+   * @return a {@link com.google.gwt.user.client.History} compatible token
+   */
+  String getHistoryToken(Class<? extends EntityProxy> clazz);
+
+  /**
+   * Get a {@link com.google.gwt.user.client.History} compatible token that
+   * represents the given proxy class. It can be processed by
+   * {@link #getProxyClass(String)}.
+   * <p>
+   * The history token returned for an EntityProxyId associated with a
+   * newly-created (future) EntityProxy will differ from the token returned by
+   * this method after the EntityProxy has been persisted. Once an EntityProxy
+   * has been persisted, the return value for this method will always be stable,
+   * regardless of when the EntityProxyId was retrieved relative to the persist
+   * operation. In other words, the "future" history token returned for an
+   * as-yet-unpersisted EntityProxy is only valid for the duration of the
+   * RequestFactory's lifespan.
+   * 
+   * @param proxy an {@link EntityProxyId} instance
+   * @return a {@link com.google.gwt.user.client.History} compatible token
+   */
+  String getHistoryToken(EntityProxyId<?> proxy);
+
+  /**
+   * Return the class object which may be used to create new instances of the
+   * type of this token, via {@link RequestContext#create}. The token may
+   * represent either a proxy instance (see {@link #getHistoryToken}) or a proxy
+   * class (see {@link #getProxyClass}).
+   * 
+   * @param historyToken a String token
+   * @return a Class object for an {@link EntityProxy} subclass
+   */
+  Class<? extends EntityProxy> getProxyClass(String historyToken);
+
+  /**
+   * Return the appropriate {@link EntityProxyId} using a string returned from
+   * {@link #getHistoryToken(EntityProxyId)}.
+   * 
+   * @param historyToken a String token
+   * @return an {@link EntityProxyId}
+   */
+  <T extends EntityProxy> EntityProxyId<T> getProxyId(String historyToken);
+
+  /**
+   * Returns a ProxySerializer that can encode and decode the various
+   * EntityProxy and ValueProxy types reachable from the RequestFactory.
+   * 
+   * @param store a helper object for the ProxySerializer to provide low-level
+   *          storage access
+   * @return a new ProxySerializer
+   * @see DefaultProxyStore
+   */
+  ProxySerializer getSerializer(ProxyStore store);
+
+  /**
+   * Start this request factory with a
+   * {@link com.google.web.bindery.requestfactory.gwt.client.DefaultRequestTransport}.
+   * 
+   * @param eventBus an {@link EventBus}
+   */
+  void initialize(EventBus eventBus);
+
+  /**
+   * Start this request factory with a user-provided transport.
+   * 
+   * @param eventBus an {@link EventBus}
+   * @param transport a {@link RequestTransport} instance
+   */
+  void initialize(EventBus eventBus, RequestTransport transport);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/RequestTransport.java b/user/src/com/google/web/bindery/requestfactory/shared/RequestTransport.java
new file mode 100644
index 0000000..53ba20b
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/RequestTransport.java
@@ -0,0 +1,53 @@
+/*
+ * 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.requestfactory.shared;
+
+/**
+ * Abstracts the mechanism by which a RequestFactory instance transmits its
+ * payload to the backend.
+ * 
+ * @see com.google.web.bindery.requestfactory.gwt.client.DefaultRequestTransport
+ */
+public interface RequestTransport {
+  /**
+   * A callback interface.
+   */
+  public interface TransportReceiver {
+    /**
+     * Called when the transmission succeeds.
+     * 
+     * @param payload the String payload
+     */
+    void onTransportSuccess(String payload);
+
+    /**
+     * Called to report a transmission failure as a ServerFailure.
+     * 
+     * @param failure a ServerFailure instance indicating the nature
+     *     of the failure
+     */
+    void onTransportFailure(ServerFailure failure);
+  }
+
+  /**
+   * Called by the RequestFactory implementation.
+   * 
+   * @param payload the String payload
+   * @param receiver the {@link TransportReceiver} instance that will
+   *    receive the payload
+   */
+  void send(String payload, TransportReceiver receiver);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/ServerFailure.java b/user/src/com/google/web/bindery/requestfactory/shared/ServerFailure.java
new file mode 100644
index 0000000..a4721fe
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/ServerFailure.java
@@ -0,0 +1,96 @@
+/*
+ * 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.requestfactory.shared;
+
+/**
+ * Describes a request failure on the server.
+ * <p>
+ * This error reporting mechanism is adequate at best. When RequestFactory is
+ * extended to handle polymorphic types, this class will likely be replaced with
+ * something more expressive.
+ */
+public class ServerFailure {
+  private final String message;
+  private final String stackTraceString;
+  private final String exceptionType;
+  private final boolean fatal;
+
+  /**
+   * Constructs a ServerFailure with null properties.
+   */
+  public ServerFailure() {
+    this(null);
+  }
+
+  /**
+   * Constructs a fatal ServerFailure with null type and null stack trace.
+   */
+  public ServerFailure(String message) {
+    this(message, null, null, true);
+  }
+
+  /**
+   * Constructs a ServerFailure object.
+   * 
+   * @param message a String containing the failure message
+   * @param exceptionType a String containing the exception type
+   * @param stackTraceString a String containing the stack trace
+   */
+  public ServerFailure(String message, String exceptionType,
+      String stackTraceString, boolean fatal) {
+    this.message = message;
+    this.exceptionType = exceptionType;
+    this.stackTraceString = stackTraceString;
+    this.fatal = fatal;
+  }
+
+  /**
+   * Return the exception type.
+   * 
+   * @return the exception type as a String
+   */
+  public String getExceptionType() {
+    return exceptionType;
+  }
+
+  /**
+   * Return the failure message.
+   * 
+   * @return the message as a String
+   */
+  public String getMessage() {
+    return message;
+  }
+
+  /**
+   * Return the failure stack trace.
+   * 
+   * @return the stack trace as a String
+   */
+  public String getStackTraceString() {
+    return stackTraceString;
+  }
+
+  /**
+   * Return true if this is a fatal error. The default implementation of
+   * {@link Receiver#onFailure} throws a runtime exception for fatal failures.
+   * 
+   * @return whether this is a fatal failure
+   */
+  public boolean isFatal() {
+    return fatal;
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/Service.java b/user/src/com/google/web/bindery/requestfactory/shared/Service.java
new file mode 100644
index 0000000..27f8b5a
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/Service.java
@@ -0,0 +1,44 @@
+/*
+ * 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.requestfactory.shared;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation on Request classes specifying the server side implementations that
+ * back them.
+ * 
+ * @see ServiceName
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface Service {
+  /**
+   * The domain type that provides the implementations for the methods defined
+   * in the RequestContext.
+   */
+  Class<?> value();
+
+  /**
+   * An optional {@link ServiceLocator} that provides instances of service
+   * objects used when invoking instance methods on the type returned by
+   * {@link #value()}.
+   */
+  Class<? extends ServiceLocator> locator() default ServiceLocator.class;
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/ServiceLocator.java b/user/src/com/google/web/bindery/requestfactory/shared/ServiceLocator.java
new file mode 100644
index 0000000..0b53733
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/ServiceLocator.java
@@ -0,0 +1,37 @@
+/*
+ * 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.requestfactory.shared;
+
+/**
+ * A ServiceLocator provides instances of a type specified by a {@link Service}
+ * when {@link Request} methods declared in a {@link RequestContext}are mapped
+ * onto instance (non-static) methods.
+ * <p>
+ * ServiceLocator subtypes must be default instantiable (i.e. public static
+ * types with a no-arg constructor). Instances of ServiceLocators may be
+ * retained and reused by the RequestFactory service layer.
+ * 
+ * @see Service#locator()
+ */
+public interface ServiceLocator {
+  /**
+   * Returns an instance of the service object.
+   * 
+   * @param clazz the requested type of service object
+   * @return an instance of the service object
+   */
+  Object getInstance(Class<?> clazz);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/ServiceName.java b/user/src/com/google/web/bindery/requestfactory/shared/ServiceName.java
new file mode 100644
index 0000000..6ea2abe
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/ServiceName.java
@@ -0,0 +1,43 @@
+/*
+ * 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.requestfactory.shared;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation on Request classes specifying the server side implementations that
+ * back them.This annotation can be used in place of {@link Service} if the
+ * service type is not available to the GWT compiler or DevMode runtime.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface ServiceName {
+  /**
+   * The binary name of the domain type that provides the implementations for
+   * the methods defined in the RequestContext.
+   */
+  String value();
+
+  /**
+   * An optional binary name of a {@link ServiceLocator} that provides instances
+   * of service objects used when invoking instance methods on the type returned
+   * by {@link #value()}.
+   */
+  String locator() default "";
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/SkipInterfaceValidation.java b/user/src/com/google/web/bindery/requestfactory/shared/SkipInterfaceValidation.java
new file mode 100644
index 0000000..ad6575f
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/SkipInterfaceValidation.java
@@ -0,0 +1,35 @@
+/*
+ * 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.requestfactory.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;
+
+/**
+ * Annotation on methods of {@link RequestContext}, {@link EntityProxy}, or
+ * {@link ValueProxy} interfaces so that the
+ * {@link com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidator
+ * RequestFactoryInterfaceValidator} doesn't enforce the presence of a
+ * corresponding method on the domain type.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface SkipInterfaceValidation {
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/ValueProxy.java b/user/src/com/google/web/bindery/requestfactory/shared/ValueProxy.java
new file mode 100644
index 0000000..bbd7760
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/ValueProxy.java
@@ -0,0 +1,23 @@
+/*
+ * 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.requestfactory.shared;
+
+/**
+ * An analog to EntityProxy for domain types that do not have an identity
+ * concept.
+ */
+public interface ValueProxy extends BaseProxy {
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/Violation.java b/user/src/com/google/web/bindery/requestfactory/shared/Violation.java
new file mode 100644
index 0000000..3d68373
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/Violation.java
@@ -0,0 +1,63 @@
+/*
+ * 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.requestfactory.shared;
+
+/**
+ * A lightweight representation of a
+ * {@link javax.validation.ConstraintViolation}.
+ */
+public interface Violation {
+  /**
+   * If the ConstraintViolation occurred while validating a object, this method
+   * will return a BaseProxy that contains the invalid values.
+   * 
+   * @return the BaseProxy that caused the ConstraintViolation
+   */
+  BaseProxy getInvalidProxy();
+
+  /**
+   * Returns the message associated with this {@link Violation}.
+   * 
+   * @return a String message
+   */
+  String getMessage();
+
+  /**
+   * If the ConstraintViolation occurred while validating a value object that
+   * originated from the server, this method will return a BaseProxy that
+   * contains the original values.
+   * 
+   * @return the BaseProxy originally sent by the server or {@code null} if the
+   *         BaseProxy was created on the client.
+   */
+  BaseProxy getOriginalProxy();
+
+  /**
+   * Returns the path associated with this {@link Violation}.
+   * 
+   * @return a String path
+   */
+  String getPath();
+
+  /**
+   * Returns the proxy id associated with this {@link Violation} if the object
+   * associated with the violation is an {@link EntityProxy}.
+   * 
+   * @return an {@link EntityProxyId} instance or {@code null} if the object is
+   *         a ValueProxy.
+   */
+  EntityProxyId<?> getProxyId();
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/WriteOperation.java b/user/src/com/google/web/bindery/requestfactory/shared/WriteOperation.java
new file mode 100644
index 0000000..9f74a53
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/WriteOperation.java
@@ -0,0 +1,58 @@
+/*
+ * 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.requestfactory.shared;
+
+/**
+ * The values returned by {@link EntityProxyChange#getWriteOperation()} to
+ * describe the type of change being announced.
+ * 
+ * <dl>
+ * <dt>PERSIST
+ * <dd>An {@link EntityProxy} that was created on the client has been persisted
+ * on the server
+ * 
+ * <dt>UPDATE
+ * <dd>An {@link EntityProxy} has been encountered by the client for the first
+ * time, or its version value has changed
+ * 
+ * <dt>DELETE
+ * <dd>The server has confirmed the success of a client request to delete an
+ * {@link EntityProxy}
+ * </dl>
+ * 
+ * @see EntityProxyChange#getWriteOperation()
+ */
+public enum WriteOperation {
+  PERSIST("PERSIST"), UPDATE("UPDATE"), DELETE("DELETE");
+
+  // use an unObfuscatedEnumName field to bypass the implicit name() method,
+  // to be safe in the case enum name obfuscation is enabled.
+  private final String unObfuscatedEnumName;
+
+  private WriteOperation(String unObfuscatedEnumName) {
+    this.unObfuscatedEnumName = unObfuscatedEnumName;
+  }
+
+  /**
+   * Returns the unobfuscated name of the event associated with this
+   * {@link WriteOperation}.
+   * 
+   * @return one of "PERSIST", "UPDATE", or "DELETE"
+   */
+  public String getUnObfuscatedEnumName() {
+    return this.unObfuscatedEnumName;
+  }
+}
\ 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
new file mode 100644
index 0000000..f7faed5
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequest.java
@@ -0,0 +1,133 @@
+/*
+ * 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.requestfactory.shared.impl;
+
+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;
+import com.google.web.bindery.requestfactory.shared.Request;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.ServerFailure;
+import com.google.web.bindery.requestfactory.shared.Violation;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Abstract implementation of {@link Request}. Each request stores a
+ * {@link DeltaValueStoreJsonImpl}.
+ * 
+ * @param <T> return type
+ */
+public abstract class AbstractRequest<T> implements Request<T>,
+    InstanceRequest<BaseProxy, T> {
+
+  /**
+   * Used by generated subtypes.
+   */
+  protected final Set<String> propertyRefs = new HashSet<String>();
+  protected final AbstractRequestContext requestContext;
+  private Receiver<? super T> receiver;
+  private RequestData requestData;
+
+  protected AbstractRequest(AbstractRequestContext requestContext) {
+    this.requestContext = requestContext;
+  }
+
+  public void fire() {
+    requestContext.fire();
+  }
+
+  public void fire(Receiver<? super T> receiver) {
+    to(receiver);
+    fire();
+  }
+
+  /**
+   * Returns the properties.
+   */
+  public Set<String> getPropertyRefs() {
+    return Collections.unmodifiableSet(propertyRefs);
+  }
+
+  public RequestData getRequestData() {
+    if (requestData == null) {
+      requestData = makeRequestData();
+    }
+    return requestData;
+  }
+
+  public RequestContext to(Receiver<? super T> receiver) {
+    this.receiver = receiver;
+    return requestContext;
+  }
+
+  /**
+   * This method comes from the {@link InstanceRequest} interface. Instance
+   * methods place the instance in the first parameter slot.
+   */
+  public Request<T> using(BaseProxy instanceObject) {
+    getRequestData().getOrderedParameters()[0] = instanceObject;
+    /*
+     * Instance methods enqueue themselves when their using() method is called.
+     * This ensures that the instance parameter will have been set when
+     * AbstractRequestContext.retainArg() is called.
+     */
+    requestContext.addInvocation(this);
+    return this;
+  }
+
+  public Request<T> with(String... propertyRefs) {
+    this.propertyRefs.addAll(Arrays.asList(propertyRefs));
+    return this;
+  }
+
+  protected abstract RequestData makeRequestData();
+
+  Receiver<? super T> getReceiver() {
+    return receiver;
+  }
+
+  boolean hasReceiver() {
+    return receiver != null;
+  }
+
+  void onFail(ServerFailure failure) {
+    if (receiver != null) {
+      receiver.onFailure(failure);
+    }
+  }
+
+  void onSuccess(Splittable split) {
+    // The user may not have called to()
+    if (receiver != null) {
+      @SuppressWarnings("unchecked")
+      T result = (T) EntityCodex.decode(requestContext,
+          requestData.getReturnType(), requestData.getElementType(), split);
+      receiver.onSuccess(result);
+    }
+  }
+
+  void onViolation(Set<Violation> errors) {
+    // The user may not have called to()
+    if (receiver != null) {
+      receiver.onViolation(errors);
+    }
+  }
+}
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
new file mode 100644
index 0000000..0e10a3a
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequestContext.java
@@ -0,0 +1,1093 @@
+/*
+ * 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.requestfactory.shared.impl;
+
+import static com.google.web.bindery.requestfactory.shared.impl.BaseProxyCategory.stableId;
+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.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.web.bindery.event.shared.UmbrellaException;
+import com.google.web.bindery.requestfactory.shared.BaseProxy;
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.EntityProxyChange;
+import com.google.web.bindery.requestfactory.shared.EntityProxyId;
+import com.google.web.bindery.requestfactory.shared.Receiver;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.RequestTransport.TransportReceiver;
+import com.google.web.bindery.requestfactory.shared.ServerFailure;
+import com.google.web.bindery.requestfactory.shared.Violation;
+import com.google.web.bindery.requestfactory.shared.WriteOperation;
+import com.google.web.bindery.requestfactory.shared.impl.posers.DatePoser;
+import com.google.web.bindery.requestfactory.shared.messages.IdMessage;
+import com.google.web.bindery.requestfactory.shared.messages.IdMessage.Strength;
+import com.google.web.bindery.requestfactory.shared.messages.InvocationMessage;
+import com.google.web.bindery.requestfactory.shared.messages.JsonRpcRequest;
+import com.google.web.bindery.requestfactory.shared.messages.MessageFactory;
+import com.google.web.bindery.requestfactory.shared.messages.OperationMessage;
+import com.google.web.bindery.requestfactory.shared.messages.RequestMessage;
+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 java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Base implementations for RequestContext services.
+ */
+public abstract class AbstractRequestContext implements RequestContext, EntityCodex.EntitySource {
+  /**
+   * Allows the payload dialect to be injected into the AbstractRequestContext
+   * without the caller needing to be concerned with how the implementation
+   * object is instantiated.
+   */
+  public enum Dialect {
+    STANDARD {
+      @Override
+      DialectImpl create(AbstractRequestContext context) {
+        return context.new StandardPayloadDialect();
+      }
+    },
+    JSON_RPC {
+      @Override
+      DialectImpl create(AbstractRequestContext context) {
+        return context.new JsonRpcPayloadDialect();
+      }
+    };
+    abstract DialectImpl create(AbstractRequestContext context);
+  }
+
+  interface DialectImpl {
+
+    void addInvocation(AbstractRequest<?> request);
+
+    String makePayload();
+
+    void processPayload(Receiver<Void> receiver, String payload);
+  }
+
+  class JsonRpcPayloadDialect implements DialectImpl {
+    /**
+     * Called by generated subclasses to enqueue a method invocation.
+     */
+    public void addInvocation(AbstractRequest<?> request) {
+      /*
+       * TODO(bobv): Support for multiple invocations per request needs to be
+       * ironed out. Once this is done, addInvocation() can be removed from the
+       * DialectImpl interface and restored to to AbstractRequestContext.
+       */
+      if (!invocations.isEmpty()) {
+        throw new RuntimeException("Only one invocation per request, pending backend support");
+      }
+      invocations.add(request);
+      for (Object arg : request.getRequestData().getOrderedParameters()) {
+        retainArg(arg);
+      }
+    }
+
+    public String makePayload() {
+      RequestData data = invocations.get(0).getRequestData();
+
+      AutoBean<JsonRpcRequest> bean = MessageFactoryHolder.FACTORY.jsonRpcRequest();
+      JsonRpcRequest request = bean.as();
+
+      request.setVersion("2.0");
+      request.setApiVersion(data.getApiVersion());
+      request.setId(payloadId++);
+
+      Map<String, Splittable> params = new HashMap<String, Splittable>();
+      for (Map.Entry<String, Object> entry : data.getNamedParameters().entrySet()) {
+        Object obj = entry.getValue();
+        Splittable value = encode(obj);
+        params.put(entry.getKey(), value);
+      }
+      if (data.getRequestResource() != null) {
+        params.put("resource", encode(data.getRequestResource()));
+      }
+      request.setParams(params);
+      request.setMethod(data.getOperation());
+
+      return AutoBeanCodex.encode(bean).getPayload();
+    }
+
+    public void processPayload(Receiver<Void> receiver, String payload) {
+      Splittable raw = StringQuoter.split(payload);
+
+      @SuppressWarnings("unchecked")
+      Receiver<Object> callback = (Receiver<Object>) invocations.get(0).getReceiver();
+
+      if (!raw.isNull("error")) {
+        Splittable error = raw.get("error");
+        ServerFailure failure =
+            new ServerFailure(error.get("message").asString(), error.get("code").asString(),
+                payload, true);
+        fail(receiver, failure);
+        return;
+      }
+
+      Splittable result = raw.get("result");
+      @SuppressWarnings("unchecked")
+      Class<BaseProxy> target =
+          (Class<BaseProxy>) invocations.get(0).getRequestData().getReturnType();
+
+      SimpleProxyId<BaseProxy> id = getRequestFactory().allocateId(target);
+      AutoBean<BaseProxy> bean = createProxy(target, id);
+      // XXX expose this as a proper API
+      ((AbstractAutoBean<?>) bean).setData(result);
+      // AutoBeanCodex.decodeInto(result, bean);
+
+      if (callback != null) {
+        callback.onSuccess(bean.as());
+      }
+      if (receiver != null) {
+        receiver.onSuccess(null);
+      }
+    }
+
+    Splittable encode(Object obj) {
+      Splittable value;
+      if (obj == null) {
+        return Splittable.NULL;
+      } else if (obj.getClass().isEnum() && getAutoBeanFactory() instanceof EnumMap) {
+        value = ValueCodex.encode(((EnumMap) getAutoBeanFactory()).getToken((Enum<?>) obj));
+      } else if (ValueCodex.canDecode(obj.getClass())) {
+        value = ValueCodex.encode(obj);
+      } else {
+        // XXX user-provided implementation of interface?
+        value = AutoBeanCodex.encode(AutoBeanUtils.getAutoBean(obj));
+      }
+      return value;
+    }
+  }
+
+  class StandardPayloadDialect implements DialectImpl {
+
+    /**
+     * Called by generated subclasses to enqueue a method invocation.
+     */
+    public void addInvocation(AbstractRequest<?> request) {
+      invocations.add(request);
+      for (Object arg : request.getRequestData().getOrderedParameters()) {
+        retainArg(arg);
+      }
+    }
+
+    /**
+     * Assemble all of the state that has been accumulated in this context. This
+     * includes:
+     * <ul>
+     * <li>Diffs accumulated on objects passed to {@link #edit}.
+     * <li>Invocations accumulated as Request subtypes passed to
+     * {@link #addInvocation}.
+     * </ul>
+     */
+    public String makePayload() {
+      // Get the factory from the runtime-specific holder.
+      MessageFactory f = MessageFactoryHolder.FACTORY;
+
+      List<OperationMessage> operations = makePayloadOperations();
+      List<InvocationMessage> invocationMessages = makePayloadInvocations();
+
+      // Create the outer envelope message
+      AutoBean<RequestMessage> bean = f.request();
+      RequestMessage requestMessage = bean.as();
+      if (!invocationMessages.isEmpty()) {
+        requestMessage.setInvocations(invocationMessages);
+      }
+      if (!operations.isEmpty()) {
+        requestMessage.setOperations(operations);
+      }
+      return AutoBeanCodex.encode(bean).getPayload();
+    }
+
+    public void processPayload(final Receiver<Void> receiver, String payload) {
+      ResponseMessage response =
+          AutoBeanCodex.decode(MessageFactoryHolder.FACTORY, ResponseMessage.class, payload).as();
+      if (response.getGeneralFailure() != null) {
+        ServerFailureMessage failure = response.getGeneralFailure();
+        ServerFailure fail =
+            new ServerFailure(failure.getMessage(), failure.getExceptionType(), failure
+                .getStackTrace(), failure.isFatal());
+
+        fail(receiver, fail);
+        return;
+      }
+
+      // Process violations and then stop
+      if (response.getViolations() != null) {
+        Set<Violation> errors = new HashSet<Violation>();
+        for (ViolationMessage message : response.getViolations()) {
+          errors.add(new MyViolation(message));
+        }
+
+        violation(receiver, errors);
+        return;
+      }
+
+      // Process operations
+      processReturnOperations(response);
+
+      // Send return values
+      Set<Throwable> causes = null;
+      for (int i = 0, j = invocations.size(); i < j; i++) {
+        try {
+          if (response.getStatusCodes().get(i)) {
+            invocations.get(i).onSuccess(response.getInvocationResults().get(i));
+          } else {
+            ServerFailureMessage failure =
+                AutoBeanCodex.decode(MessageFactoryHolder.FACTORY, ServerFailureMessage.class,
+                    response.getInvocationResults().get(i)).as();
+            invocations.get(i).onFail(
+                new ServerFailure(failure.getMessage(), failure.getExceptionType(), failure
+                    .getStackTrace(), failure.isFatal()));
+          }
+        } catch (Throwable t) {
+          if (causes == null) {
+            causes = new HashSet<Throwable>();
+          }
+          causes.add(t);
+        }
+      }
+
+      if (receiver != null) {
+        try {
+          receiver.onSuccess(null);
+        } catch (Throwable t) {
+          if (causes == null) {
+            causes = new HashSet<Throwable>();
+          }
+          causes.add(t);
+        }
+      }
+      // After success, shut down the context
+      editedProxies.clear();
+      invocations.clear();
+      returnedProxies.clear();
+
+      if (causes != null) {
+        throw new UmbrellaException(causes);
+      }
+    }
+  }
+
+  private class MyViolation implements Violation {
+
+    private final BaseProxy currentProxy;
+    private final EntityProxyId<?> id;
+    private final String message;
+    private final String path;
+    private final BaseProxy parentProxy;
+
+    public MyViolation(ViolationMessage message) {
+      // Support violations for value objects.
+      SimpleProxyId<BaseProxy> baseId = getId(message);
+      if (baseId instanceof EntityProxyId<?>) {
+        id = (EntityProxyId<?>) baseId;
+      } else {
+        id = null;
+      }
+      // The stub is empty, since we don't process any OperationMessages
+      AutoBean<BaseProxy> stub = getProxyForReturnPayloadGraph(baseId);
+
+      // So pick up the instance that we just sent to the server
+      AutoBean<?> edited = editedProxies.get(BaseProxyCategory.stableId(stub));
+      currentProxy = (BaseProxy) edited.as();
+
+      // Try to find the original, immutable version.
+      AutoBean<BaseProxy> parentBean = edited.getTag(Constants.PARENT_OBJECT);
+      parentProxy = parentBean == null ? null : parentBean.as();
+      path = message.getPath();
+      this.message = message.getMessage();
+    }
+
+    public BaseProxy getInvalidProxy() {
+      return currentProxy;
+    }
+
+    public String getMessage() {
+      return message;
+    }
+
+    public BaseProxy getOriginalProxy() {
+      return parentProxy;
+    }
+
+    public String getPath() {
+      return path;
+    }
+
+    public EntityProxyId<?> getProxyId() {
+      return id;
+    }
+  }
+
+  private static final WriteOperation[] DELETE_ONLY = {WriteOperation.DELETE};
+  private static final WriteOperation[] PERSIST_AND_UPDATE = {
+      WriteOperation.PERSIST, WriteOperation.UPDATE};
+  private static final WriteOperation[] UPDATE_ONLY = {WriteOperation.UPDATE};
+  private static int payloadId = 100;
+  protected final List<AbstractRequest<?>> invocations = new ArrayList<AbstractRequest<?>>();
+  private boolean locked;
+
+  private final AbstractRequestFactory requestFactory;
+  /**
+   * A map of all EntityProxies that the RequestContext has interacted with.
+   * Objects are placed into this map by being passed into {@link #edit} or as
+   * an invocation argument.
+   */
+  private final Map<SimpleProxyId<?>, AutoBean<? extends BaseProxy>> editedProxies =
+      new LinkedHashMap<SimpleProxyId<?>, AutoBean<? extends BaseProxy>>();
+  /**
+   * A map that contains the canonical instance of an entity to return in the
+   * return graph, since this is built from scratch.
+   */
+  private final Map<SimpleProxyId<?>, AutoBean<?>> returnedProxies =
+      new HashMap<SimpleProxyId<?>, AutoBean<?>>();
+
+  /**
+   * A map that allows us to handle the case where the server has sent back an
+   * unpersisted entity. Because we assume that the server is stateless, the
+   * client will need to swap out the request-local ids with a regular
+   * client-allocated id.
+   */
+  private final Map<Integer, SimpleProxyId<?>> syntheticIds =
+      new HashMap<Integer, SimpleProxyId<?>>();
+
+  private final DialectImpl dialect;
+
+  protected AbstractRequestContext(AbstractRequestFactory factory, Dialect dialect) {
+    this.requestFactory = factory;
+    this.dialect = dialect.create(this);
+  }
+
+  /**
+   * Create a new object, with an ephemeral id.
+   */
+  public <T extends BaseProxy> T create(Class<T> clazz) {
+    checkLocked();
+
+    SimpleProxyId<T> id = requestFactory.allocateId(clazz);
+    AutoBean<T> created = createProxy(clazz, id);
+    return takeOwnership(created);
+  }
+
+  /**
+   * Creates a new proxy with an assigned ID.
+   */
+  public <T extends BaseProxy> AutoBean<T> createProxy(Class<T> clazz, SimpleProxyId<T> id) {
+    AutoBean<T> created = getAutoBeanFactory().create(clazz);
+    if (created == null) {
+      throw new IllegalArgumentException("Unknown proxy type " + clazz.getName());
+    }
+    created.setTag(STABLE_ID, id);
+    return created;
+  }
+
+  public <T extends BaseProxy> T edit(T object) {
+    return editProxy(object);
+  }
+
+  /**
+   * Take ownership of a proxy instance and make it editable.
+   */
+  public <T extends BaseProxy> T editProxy(T object) {
+    AutoBean<T> bean = checkStreamsNotCrossed(object);
+    checkLocked();
+
+    @SuppressWarnings("unchecked")
+    AutoBean<T> previouslySeen = (AutoBean<T>) editedProxies.get(BaseProxyCategory.stableId(bean));
+    if (previouslySeen != null && !previouslySeen.isFrozen()) {
+      /*
+       * If we've seen the object before, it might be because it was passed in
+       * as a method argument. This does not guarantee its mutability, so check
+       * that here before returning the cached object.
+       */
+      return previouslySeen.as();
+    }
+
+    // Create editable copies
+    AutoBean<T> parent = bean;
+    bean = cloneBeanAndCollections(bean);
+    bean.setTag(Constants.PARENT_OBJECT, parent);
+    return bean.as();
+  }
+
+  /**
+   * Make sure there's a default receiver so errors don't get dropped. This
+   * behavior should be revisited when chaining is supported, depending on
+   * whether or not chained invocations can fail independently.
+   */
+  public void fire() {
+    boolean needsReceiver = true;
+    for (AbstractRequest<?> request : invocations) {
+      if (request.hasReceiver()) {
+        needsReceiver = false;
+        break;
+      }
+    }
+
+    if (needsReceiver) {
+      doFire(new Receiver<Void>() {
+        @Override
+        public void onSuccess(Void response) {
+          // Don't care
+        }
+      });
+    } else {
+      doFire(null);
+    }
+  }
+
+  public void fire(final Receiver<Void> receiver) {
+    if (receiver == null) {
+      throw new IllegalArgumentException();
+    }
+    doFire(receiver);
+  }
+
+  /**
+   * EntityCodex support.
+   */
+  public <Q extends BaseProxy> AutoBean<Q> getBeanForPayload(Splittable serializedProxyId) {
+    IdMessage ref =
+        AutoBeanCodex.decode(MessageFactoryHolder.FACTORY, IdMessage.class, serializedProxyId).as();
+    @SuppressWarnings("unchecked")
+    SimpleProxyId<Q> id = (SimpleProxyId<Q>) getId(ref);
+    return getProxyForReturnPayloadGraph(id);
+  }
+
+  public AbstractRequestFactory getRequestFactory() {
+    return requestFactory;
+  }
+
+  /**
+   * EntityCodex support.
+   */
+  public Splittable getSerializedProxyId(SimpleProxyId<?> stableId) {
+    AutoBean<IdMessage> bean = MessageFactoryHolder.FACTORY.id();
+    IdMessage ref = bean.as();
+    ref.setServerId(stableId.getServerId());
+    ref.setTypeToken(getRequestFactory().getTypeToken(stableId.getProxyClass()));
+    if (stableId.isSynthetic()) {
+      ref.setStrength(Strength.SYNTHETIC);
+      ref.setSyntheticId(stableId.getSyntheticId());
+    } else if (stableId.isEphemeral()) {
+      ref.setStrength(Strength.EPHEMERAL);
+      ref.setClientId(stableId.getClientId());
+    }
+    return AutoBeanCodex.encode(bean);
+  }
+
+  public boolean isChanged() {
+    /*
+     * NB: Don't use the presence of ephemeral objects for this test.
+     * 
+     * Diff the objects until one is found to be different. It's not just a
+     * simple flag-check because of the possibility of "unmaking" a change, per
+     * the JavaDoc.
+     */
+    for (AutoBean<? extends BaseProxy> bean : editedProxies.values()) {
+      AutoBean<?> previous = bean.getTag(Constants.PARENT_OBJECT);
+      if (previous == null) {
+        // Compare to empty object
+        Class<?> proxyClass = stableId(bean).getProxyClass();
+        previous = getAutoBeanFactory().create(proxyClass);
+      }
+      if (!AutoBeanUtils.diff(previous, bean).isEmpty()) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * EntityCodex support.
+   */
+  public boolean isEntityType(Class<?> clazz) {
+    return requestFactory.isEntityType(clazz);
+  }
+
+  public boolean isLocked() {
+    return locked;
+  }
+
+  /**
+   * EntityCodex support.
+   */
+  public boolean isValueType(Class<?> clazz) {
+    return requestFactory.isValueType(clazz);
+  }
+
+  /**
+   * Called by generated subclasses to enqueue a method invocation.
+   */
+  protected void addInvocation(AbstractRequest<?> request) {
+    dialect.addInvocation(request);
+  };
+
+  /**
+   * Invoke the appropriate {@code onFailure} callbacks, possibly throwing an
+   * {@link UmbrellaException} if one or more callbacks fails.
+   */
+  protected void fail(Receiver<Void> receiver, ServerFailure failure) {
+    reuse();
+    Set<Throwable> causes = null;
+    for (AbstractRequest<?> request : new ArrayList<AbstractRequest<?>>(invocations)) {
+      try {
+        request.onFail(failure);
+      } catch (Throwable t) {
+        if (causes == null) {
+          causes = new HashSet<Throwable>();
+        }
+        causes.add(t);
+      }
+    }
+    if (receiver != null) {
+      try {
+        receiver.onFailure(failure);
+      } catch (Throwable t) {
+        if (causes == null) {
+          causes = new HashSet<Throwable>();
+        }
+        causes.add(t);
+      }
+    }
+
+    if (causes != null) {
+      throw new UmbrellaException(causes);
+    }
+  }
+
+  /**
+   * Returns an AutoBeanFactory that can produce the types reachable only from
+   * this RequestContext.
+   */
+  protected abstract AutoBeanFactory getAutoBeanFactory();
+
+  /**
+   * Invoke the appropriate {@code onViolation} callbacks, possibly throwing an
+   * {@link UmbrellaException} if one or more callbacks fails.
+   */
+  protected void violation(final Receiver<Void> receiver, Set<Violation> errors) {
+    reuse();
+    Set<Throwable> causes = null;
+    for (AbstractRequest<?> request : new ArrayList<AbstractRequest<?>>(invocations)) {
+      try {
+        request.onViolation(errors);
+      } catch (Throwable t) {
+        if (causes == null) {
+          causes = new HashSet<Throwable>();
+        }
+        causes.add(t);
+      }
+    }
+    if (receiver != null) {
+      try {
+        receiver.onViolation(errors);
+      } catch (Throwable t) {
+        if (causes == null) {
+          causes = new HashSet<Throwable>();
+        }
+        causes.add(t);
+      }
+    }
+
+    if (causes != null) {
+      throw new UmbrellaException(causes);
+    }
+  }
+
+  /**
+   * Resolves an IdMessage into an SimpleProxyId.
+   */
+  SimpleProxyId<BaseProxy> getId(IdMessage op) {
+    if (Strength.SYNTHETIC.equals(op.getStrength())) {
+      return allocateSyntheticId(op.getTypeToken(), op.getSyntheticId());
+    }
+    return requestFactory.getId(op.getTypeToken(), op.getServerId(), op.getClientId());
+  }
+
+  /**
+   * Creates or retrieves a new canonical AutoBean to represent the given id in
+   * the returned payload.
+   */
+  <Q extends BaseProxy> AutoBean<Q> getProxyForReturnPayloadGraph(SimpleProxyId<Q> id) {
+    @SuppressWarnings("unchecked")
+    AutoBean<Q> bean = (AutoBean<Q>) returnedProxies.get(id);
+    if (bean == null) {
+      Class<Q> proxyClass = id.getProxyClass();
+      bean = createProxy(proxyClass, id);
+      returnedProxies.put(id, bean);
+    }
+
+    return bean;
+  }
+
+  /**
+   * Create a single OperationMessage that encapsulates the state of a proxy
+   * AutoBean.
+   */
+  AutoBean<OperationMessage> makeOperationMessage(SimpleProxyId<BaseProxy> stableId,
+      AutoBean<?> proxyBean, boolean useDelta) {
+
+    // The OperationMessages describes operations on exactly one entity
+    AutoBean<OperationMessage> toReturn = MessageFactoryHolder.FACTORY.operation();
+    OperationMessage operation = toReturn.as();
+    operation.setTypeToken(requestFactory.getTypeToken(stableId.getProxyClass()));
+
+    // Find the object to compare against
+    AutoBean<?> parent;
+    if (stableId.isEphemeral()) {
+      // Newly-created object, use a blank object to compare against
+      parent = createProxy(stableId.getProxyClass(), stableId);
+
+      // Newly-created objects go into the persist operation bucket
+      operation.setOperation(WriteOperation.PERSIST);
+      // The ephemeral id is passed to the server
+      operation.setClientId(stableId.getClientId());
+      operation.setStrength(Strength.EPHEMERAL);
+    } else if (stableId.isSynthetic()) {
+      // Newly-created object, use a blank object to compare against
+      parent = createProxy(stableId.getProxyClass(), stableId);
+
+      // Newly-created objects go into the persist operation bucket
+      operation.setOperation(WriteOperation.PERSIST);
+      // The ephemeral id is passed to the server
+      operation.setSyntheticId(stableId.getSyntheticId());
+      operation.setStrength(Strength.SYNTHETIC);
+    } else {
+      parent = proxyBean.getTag(Constants.PARENT_OBJECT);
+      // Requests involving existing objects use the persisted id
+      operation.setServerId(stableId.getServerId());
+      operation.setOperation(WriteOperation.UPDATE);
+    }
+    assert !useDelta || parent != null;
+
+    // Send our version number to the server to cut down on future payloads
+    String version = proxyBean.getTag(Constants.VERSION_PROPERTY_B64);
+    if (version != null) {
+      operation.setVersion(version);
+    }
+
+    Map<String, Object> diff = Collections.emptyMap();
+    if (isEntityType(stableId.getProxyClass())) {
+      // Compute what's changed on the client
+      diff =
+          useDelta ? AutoBeanUtils.diff(parent, proxyBean) : AutoBeanUtils
+              .getAllProperties(proxyBean);
+    } else if (isValueType(stableId.getProxyClass())) {
+      // Send everything
+      diff = AutoBeanUtils.getAllProperties(proxyBean);
+    }
+
+    if (!diff.isEmpty()) {
+      Map<String, Splittable> propertyMap = new HashMap<String, Splittable>();
+      for (Map.Entry<String, Object> entry : diff.entrySet()) {
+        propertyMap.put(entry.getKey(), EntityCodex.encode(this, entry.getValue()));
+      }
+      operation.setPropertyMap(propertyMap);
+    }
+    return toReturn;
+  }
+
+  /**
+   * Create a new EntityProxy from a snapshot in the return payload.
+   * 
+   * @param id the EntityProxyId of the object
+   * @param returnRecord the JSON map containing property/value pairs
+   * @param operations the WriteOperation eventns to broadcast over the EventBus
+   */
+  <Q extends BaseProxy> Q processReturnOperation(SimpleProxyId<Q> id, OperationMessage op,
+      WriteOperation... operations) {
+
+    AutoBean<Q> toMutate = getProxyForReturnPayloadGraph(id);
+    toMutate.setTag(Constants.VERSION_PROPERTY_B64, op.getVersion());
+
+    final Map<String, Splittable> properties = op.getPropertyMap();
+    if (properties != null) {
+      // Apply updates
+      toMutate.accept(new AutoBeanVisitor() {
+        @Override
+        public boolean visitReferenceProperty(String propertyName, AutoBean<?> value,
+            PropertyContext ctx) {
+          if (ctx.canSet()) {
+            if (properties.containsKey(propertyName)) {
+              Splittable raw = properties.get(propertyName);
+              Class<?> elementType =
+                  ctx instanceof CollectionPropertyContext ? ((CollectionPropertyContext) ctx)
+                      .getElementType() : null;
+              Object decoded =
+                  EntityCodex.decode(AbstractRequestContext.this, ctx.getType(), elementType, raw);
+              ctx.set(decoded);
+            }
+          }
+          return false;
+        }
+
+        @Override
+        public boolean visitValueProperty(String propertyName, Object value, PropertyContext ctx) {
+          if (ctx.canSet()) {
+            if (properties.containsKey(propertyName)) {
+              Splittable raw = properties.get(propertyName);
+              Object decoded = ValueCodex.decode(ctx.getType(), raw);
+              /*
+               * Hack for Date subtypes, consider generalizing for
+               * "custom serializers"
+               */
+              if (decoded != null && Date.class.equals(ctx.getType())) {
+                decoded = new DatePoser((Date) decoded);
+              }
+              ctx.set(decoded);
+            }
+          }
+          return false;
+        }
+      });
+    }
+
+    // Finished applying updates, freeze the bean
+    makeImmutable(toMutate);
+    Q proxy = toMutate.as();
+
+    /*
+     * Notify subscribers if the object differs from when it first came into the
+     * RequestContext.
+     */
+    if (operations != null && requestFactory.isEntityType(id.getProxyClass())) {
+      for (WriteOperation writeOperation : operations) {
+        if (writeOperation.equals(WriteOperation.UPDATE)
+            && !requestFactory.hasVersionChanged(id, op.getVersion())) {
+          // No updates if the server reports no change
+          continue;
+        }
+        requestFactory.getEventBus().fireEventFromSource(
+            new EntityProxyChange<EntityProxy>((EntityProxy) proxy, writeOperation),
+            id.getProxyClass());
+      }
+    }
+    return proxy;
+  }
+
+  /**
+   * Get-or-create method for synthetic ids.
+   * 
+   * @see #syntheticIds
+   */
+  private <Q extends BaseProxy> SimpleProxyId<Q> allocateSyntheticId(String typeToken,
+      int syntheticId) {
+    @SuppressWarnings("unchecked")
+    SimpleProxyId<Q> toReturn = (SimpleProxyId<Q>) syntheticIds.get(syntheticId);
+    if (toReturn == null) {
+      toReturn = requestFactory.allocateId(requestFactory.<Q> getTypeFromToken(typeToken));
+      syntheticIds.put(syntheticId, toReturn);
+    }
+    return toReturn;
+  }
+
+  private void checkLocked() {
+    if (locked) {
+      throw new IllegalStateException("A request is already in progress");
+    }
+  }
+
+  /**
+   * This method checks that a proxy object is either immutable, or already
+   * edited by this context.
+   */
+  private <T> AutoBean<T> checkStreamsNotCrossed(T object) {
+    AutoBean<T> bean = AutoBeanUtils.getAutoBean(object);
+    if (bean == null) {
+      // Unexpected; some kind of foreign implementation?
+      throw new IllegalArgumentException(object.getClass().getName());
+    }
+
+    RequestContext context = bean.getTag(REQUEST_CONTEXT);
+    if (!bean.isFrozen() && context != this) {
+      /*
+       * This means something is way off in the weeds. If a bean is editable,
+       * it's supposed to be associated with a RequestContext.
+       */
+      assert context != null : "Unfrozen bean with null RequestContext";
+
+      /*
+       * Already editing the object in another context or it would have been in
+       * the editing map.
+       */
+      throw new IllegalArgumentException("Attempting to edit an EntityProxy"
+          + " previously edited by another RequestContext");
+    }
+    return bean;
+  }
+
+  /**
+   * Shallow-clones an autobean and makes duplicates of the collection types. A
+   * regular {@link AutoBean#clone} won't duplicate reference properties.
+   */
+  private <T extends BaseProxy> AutoBean<T> cloneBeanAndCollections(final AutoBean<T> toClone) {
+    AutoBean<T> clone = toClone.getFactory().create(toClone.getType());
+    clone.setTag(STABLE_ID, toClone.getTag(STABLE_ID));
+    clone.setTag(Constants.VERSION_PROPERTY_B64, toClone.getTag(Constants.VERSION_PROPERTY_B64));
+    /*
+     * Take ownership here to prevent cycles in value objects from overflowing
+     * the stack.
+     */
+    takeOwnership(clone);
+    clone.accept(new AutoBeanVisitor() {
+      final Map<String, Object> values = AutoBeanUtils.getAllProperties(toClone);
+
+      @Override
+      public boolean visitCollectionProperty(String propertyName, AutoBean<Collection<?>> value,
+          CollectionPropertyContext ctx) {
+        // javac generics bug
+        value =
+            AutoBeanUtils.<Collection<?>, Collection<?>> getAutoBean((Collection<?>) values
+                .get(propertyName));
+        if (value != null) {
+          Collection<Object> collection;
+          if (List.class == ctx.getType()) {
+            collection = new ArrayList<Object>();
+          } else if (Set.class == ctx.getType()) {
+            collection = new HashSet<Object>();
+          } else {
+            // Should not get here if the validator works correctly
+            throw new IllegalArgumentException(ctx.getType().getName());
+          }
+
+          if (isValueType(ctx.getElementType()) || isEntityType(ctx.getElementType())) {
+            /*
+             * Proxies must be edited up-front so that the elements in the
+             * collection have stable identity.
+             */
+            for (Object o : value.as()) {
+              if (o == null) {
+                collection.add(null);
+              } else {
+                collection.add(editProxy((BaseProxy) o));
+              }
+            }
+          } else {
+            // For simple values, just copy the values
+            collection.addAll(value.as());
+          }
+
+          ctx.set(collection);
+        }
+        return false;
+      }
+
+      @Override
+      public boolean visitReferenceProperty(String propertyName, AutoBean<?> value,
+          PropertyContext ctx) {
+        value = AutoBeanUtils.getAutoBean(values.get(propertyName));
+        if (value != null) {
+          if (isValueType(ctx.getType()) || isEntityType(ctx.getType())) {
+            /*
+             * Value proxies must be cloned upfront, since the value is replaced
+             * outright.
+             */
+            @SuppressWarnings("unchecked")
+            AutoBean<BaseProxy> valueBean = (AutoBean<BaseProxy>) value;
+            ctx.set(editProxy(valueBean.as()));
+          } else {
+            ctx.set(value.as());
+          }
+        }
+        return false;
+      }
+
+      @Override
+      public boolean visitValueProperty(String propertyName, Object value, PropertyContext ctx) {
+        ctx.set(values.get(propertyName));
+        return false;
+      }
+    });
+    return clone;
+  }
+
+  private void doFire(final Receiver<Void> receiver) {
+    checkLocked();
+    locked = true;
+
+    freezeEntities(true);
+
+    String payload = dialect.makePayload();
+    requestFactory.getRequestTransport().send(payload, new TransportReceiver() {
+      public void onTransportFailure(ServerFailure failure) {
+        fail(receiver, failure);
+      }
+
+      public void onTransportSuccess(String payload) {
+        dialect.processPayload(receiver, payload);
+      }
+    });
+  }
+
+  /**
+   * Set the frozen status of all EntityProxies owned by this context.
+   */
+  private void freezeEntities(boolean frozen) {
+    for (AutoBean<?> bean : editedProxies.values()) {
+      bean.setFrozen(frozen);
+    }
+  }
+
+  /**
+   * Make an EntityProxy immutable.
+   */
+  private void makeImmutable(final AutoBean<? extends BaseProxy> toMutate) {
+    // Always diff'ed against itself, producing a no-op
+    toMutate.setTag(Constants.PARENT_OBJECT, toMutate);
+    // Act with entity-identity semantics
+    toMutate.setTag(REQUEST_CONTEXT, null);
+    toMutate.setFrozen(true);
+  }
+
+  /**
+   * Create an InvocationMessage for each remote method call being made by the
+   * context.
+   */
+  private List<InvocationMessage> makePayloadInvocations() {
+    MessageFactory f = MessageFactoryHolder.FACTORY;
+
+    List<InvocationMessage> invocationMessages = new ArrayList<InvocationMessage>();
+    for (AbstractRequest<?> invocation : invocations) {
+      // RequestData is produced by the generated subclass
+      RequestData data = invocation.getRequestData();
+      InvocationMessage message = f.invocation().as();
+
+      // Operation; essentially a method descriptor
+      message.setOperation(data.getOperation());
+
+      // The arguments to the with() calls
+      Set<String> refsToSend = data.getPropertyRefs();
+      if (!refsToSend.isEmpty()) {
+        message.setPropertyRefs(refsToSend);
+      }
+
+      // Parameter values or references
+      List<Splittable> parameters = new ArrayList<Splittable>(data.getOrderedParameters().length);
+      for (Object param : data.getOrderedParameters()) {
+        parameters.add(EntityCodex.encode(this, param));
+      }
+      if (!parameters.isEmpty()) {
+        message.setParameters(parameters);
+      }
+
+      invocationMessages.add(message);
+    }
+    return invocationMessages;
+  }
+
+  /**
+   * Compute deltas for each entity seen by the context.
+   */
+  private List<OperationMessage> makePayloadOperations() {
+    List<OperationMessage> operations = new ArrayList<OperationMessage>();
+    for (AutoBean<? extends BaseProxy> currentView : editedProxies.values()) {
+      OperationMessage operation =
+          makeOperationMessage(BaseProxyCategory.stableId(currentView), currentView, true).as();
+      operations.add(operation);
+    }
+    return operations;
+  }
+
+  /**
+   * Process an array of OperationMessages.
+   */
+  private void processReturnOperations(ResponseMessage response) {
+    List<OperationMessage> ops = response.getOperations();
+
+    // If there are no observable effects, this will be null
+    if (ops == null) {
+      return;
+    }
+
+    for (OperationMessage op : ops) {
+      SimpleProxyId<?> id = getId(op);
+      WriteOperation[] toPropagate = null;
+
+      // May be null if the server is returning an unpersisted object
+      WriteOperation effect = op.getOperation();
+      if (effect != null) {
+        switch (effect) {
+          case DELETE:
+            toPropagate = DELETE_ONLY;
+            break;
+          case PERSIST:
+            toPropagate = PERSIST_AND_UPDATE;
+            break;
+          case UPDATE:
+            toPropagate = UPDATE_ONLY;
+            break;
+          default:
+            // Should never reach here
+            throw new RuntimeException(effect.toString());
+        }
+      }
+      processReturnOperation(id, op, toPropagate);
+    }
+  }
+
+  /**
+   * Ensures that any method arguments are retained in the context's sphere of
+   * influence.
+   */
+  private void retainArg(Object arg) {
+    if (arg instanceof Iterable<?>) {
+      for (Object o : (Iterable<?>) arg) {
+        retainArg(o);
+      }
+    } else if (arg instanceof BaseProxy) {
+      // Calling edit will validate and set up the tracking we need
+      edit((BaseProxy) arg);
+    }
+  }
+
+  /**
+   * Returns the requests that were dequeued as part of reusing the context.
+   */
+  private void reuse() {
+    freezeEntities(false);
+    locked = false;
+  }
+
+  /**
+   * Make the EnityProxy bean edited and owned by this RequestContext.
+   */
+  private <T extends BaseProxy> T takeOwnership(AutoBean<T> bean) {
+    editedProxies.put(stableId(bean), bean);
+    bean.setTag(REQUEST_CONTEXT, this);
+    return bean.as();
+  }
+}
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
new file mode 100644
index 0000000..4b3d69e
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequestFactory.java
@@ -0,0 +1,148 @@
+/*
+ * 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.requestfactory.shared.impl;
+
+import com.google.web.bindery.autobean.shared.AutoBeanFactory;
+import com.google.web.bindery.event.shared.EventBus;
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.EntityProxyId;
+import com.google.web.bindery.requestfactory.shared.ProxySerializer;
+import com.google.web.bindery.requestfactory.shared.ProxyStore;
+import com.google.web.bindery.requestfactory.shared.Request;
+import com.google.web.bindery.requestfactory.shared.RequestFactory;
+import com.google.web.bindery.requestfactory.shared.RequestTransport;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Base type for generated RF interfaces.
+ */
+public abstract class AbstractRequestFactory extends IdFactory implements
+    RequestFactory {
+  private static final int MAX_VERSION_ENTRIES = 10000;
+
+  private EventBus eventBus;
+
+  @SuppressWarnings("serial")
+  private final Map<String, String> version = new LinkedHashMap<String, String>(
+      16, 0.75f, true) {
+    @Override
+    protected boolean removeEldestEntry(Entry<String, String> eldest) {
+      return size() > MAX_VERSION_ENTRIES;
+    }
+  };
+  private RequestTransport transport;
+
+  public <P extends EntityProxy> Request<P> find(final EntityProxyId<P> proxyId) {
+    if (((SimpleEntityProxyId<P>) proxyId).isEphemeral()) {
+      throw new IllegalArgumentException("Cannot fetch unpersisted entity");
+    }
+
+    AbstractRequestContext context = new AbstractRequestContext(
+        AbstractRequestFactory.this, AbstractRequestContext.Dialect.STANDARD) {
+      @Override
+      protected AutoBeanFactory getAutoBeanFactory() {
+        return AbstractRequestFactory.this.getAutoBeanFactory();
+      }
+    };
+    return new AbstractRequest<P>(context) {
+      {
+        requestContext.addInvocation(this);
+      }
+
+      @Override
+      protected RequestData makeRequestData() {
+        return new RequestData(
+            "com.google.web.bindery.requestfactory.shared.impl.FindRequest::find",
+            new Object[] {proxyId}, propertyRefs, proxyId.getProxyClass(), null);
+      }
+    };
+  }
+
+  public EventBus getEventBus() {
+    return eventBus;
+  }
+
+  public String getHistoryToken(Class<? extends EntityProxy> clazz) {
+    return getTypeToken(clazz);
+  }
+
+  public String getHistoryToken(EntityProxyId<?> proxy) {
+    return getHistoryToken((SimpleProxyId<?>) proxy);
+  }
+
+  public Class<? extends EntityProxy> getProxyClass(String historyToken) {
+    String typeToken = IdUtil.getTypeToken(historyToken);
+    if (typeToken != null) {
+      return getTypeFromToken(typeToken);
+    }
+    return getTypeFromToken(historyToken);
+  }
+
+  @SuppressWarnings("unchecked")
+  public <T extends EntityProxy> EntityProxyId<T> getProxyId(String historyToken) {
+    return (EntityProxyId<T>) getBaseProxyId(historyToken);
+  }
+
+  public RequestTransport getRequestTransport() {
+    return transport;
+  }
+
+  public ProxySerializer getSerializer(ProxyStore store) {
+    return new ProxySerializerImpl(this, store);
+  }
+
+  /**
+   * The choice of a default request transport is runtime-specific.
+   */
+  public abstract void initialize(EventBus eventBus);
+
+  public void initialize(EventBus eventBus, RequestTransport transport) {
+    this.eventBus = eventBus;
+    this.transport = transport;
+  }
+
+  /**
+   * Implementations of EntityProxies are provided by an AutoBeanFactory, which
+   * is itself a generated type. This method knows about all proxy types used in
+   * the RequestFactory interface, which prevents pruning of any proxy type. If
+   * the {@link #find(EntityProxyId)} and {@link #getSerializer(ProxyStore)}
+   * were provided by {@link AbstractRequestContext}, this method could be
+   * removed.
+   */
+  protected abstract AutoBeanFactory getAutoBeanFactory();
+
+  /**
+   * Used by {@link AbstractRequestContext} to quiesce update events for objects
+   * that haven't truly changed.
+   */
+  protected boolean hasVersionChanged(SimpleProxyId<?> id,
+      String observedVersion) {
+    assert id != null : "id";
+    assert observedVersion != null : "observedVersion";
+    String key = getHistoryToken(id);
+    String existingVersion = version.get(key);
+    // Return true if we haven't seen this before or the versions differ
+    boolean toReturn = existingVersion == null
+        || !existingVersion.equals(observedVersion);
+    if (toReturn) {
+      version.put(key, observedVersion);
+    }
+    return toReturn;
+  }
+}
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
new file mode 100644
index 0000000..481f4c8
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/BaseProxyCategory.java
@@ -0,0 +1,81 @@
+/*
+ * 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.requestfactory.shared.impl;
+
+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.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanUtils;
+import com.google.web.bindery.requestfactory.shared.BaseProxy;
+
+/**
+ * Contains behaviors common to all proxy instances.
+ */
+public class BaseProxyCategory {
+  /**
+   * Sniff all return values and ensure that if the current bean is a mutable
+   * EntityProxy, that its return values are mutable.
+   */
+  // CHECKSTYLE_OFF
+  public static <T> T __intercept(AutoBean<?> bean, T returnValue) {
+    // CHECKSTYLE_ON
+
+    AbstractRequestContext context = requestContext(bean);
+
+    /*
+     * The context will be null if the bean is immutable. If the context is
+     * locked, don't try to edit.
+     */
+    if (context == null || context.isLocked()) {
+      return returnValue;
+    }
+
+    /*
+     * EntityProxies need to be recorded specially by the RequestContext, so
+     * delegate to the edit() method for wiring up the context.
+     */
+    if (returnValue instanceof BaseProxy) {
+      @SuppressWarnings("unchecked")
+      T toReturn = (T) context.editProxy((BaseProxy) returnValue);
+      return toReturn;
+    }
+
+    if (returnValue instanceof Poser<?>) {
+      ((Poser<?>) returnValue).setFrozen(false);
+    }
+
+    /*
+     * We're returning some object that's not an EntityProxy, most likely a
+     * Collection type. At the very least, propagate the current RequestContext
+     * so that editable chains can be constructed.
+     */
+    AutoBean<T> otherBean = AutoBeanUtils.getAutoBean(returnValue);
+    if (otherBean != null) {
+      otherBean.setTag(REQUEST_CONTEXT, bean.getTag(REQUEST_CONTEXT));
+    }
+    return returnValue;
+  }
+
+  public static AbstractRequestContext requestContext(AutoBean<?> bean) {
+    return bean.getTag(REQUEST_CONTEXT);
+  }
+
+  public static <T extends BaseProxy> SimpleProxyId<T> stableId(
+      AutoBean<? extends T> bean) {
+    return bean.getTag(STABLE_ID);
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/Constants.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/Constants.java
new file mode 100644
index 0000000..33d914e
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/Constants.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.
+ */
+package com.google.web.bindery.requestfactory.shared.impl;
+
+/**
+ * Contains a variety of AutoBean tag constants to prevent typos.
+ */
+public interface Constants {
+  String DOMAIN_OBJECT = "domainObject";
+  String IN_RESPONSE = "inResponse";
+  String PARENT_OBJECT = "parentObject";
+  String REQUEST_CONTEXT = "requestContext";
+  String STABLE_ID = "stableId";
+  String VERSION_PROPERTY_B64 = "version";
+}
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
new file mode 100644
index 0000000..f498ac3
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/EntityCodex.java
@@ -0,0 +1,160 @@
+/*
+ * 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.requestfactory.shared.impl;
+
+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;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Analogous to {@link ValueCodex}, but for object types.
+ */
+public class EntityCodex {
+  /**
+   * Abstracts the process by which EntityProxies are created.
+   */
+  public interface EntitySource {
+    /**
+     * Expects an encoded
+     * {@link com.google.web.bindery.requestfactory.shared.messages.IdMessage}.
+     */
+    <Q extends BaseProxy> AutoBean<Q> getBeanForPayload(Splittable serializedIdMessage);
+
+    /**
+     * Should return an encoded
+     * {@link com.google.web.bindery.requestfactory.shared.messages.IdMessage}.
+     */
+    Splittable getSerializedProxyId(SimpleProxyId<?> stableId);
+
+    boolean isEntityType(Class<?> clazz);
+
+    boolean isValueType(Class<?> clazz);
+  }
+
+  /**
+   * Collection support is limited to value types and resolving ids.
+   */
+  public static Object decode(EntitySource source, Class<?> type, Class<?> elementType,
+      Splittable split) {
+    if (split == null || split == Splittable.NULL) {
+      return null;
+    }
+
+    // Collection support
+    if (elementType != null) {
+      Collection<Object> collection = null;
+      if (List.class.equals(type)) {
+        collection = new ArrayList<Object>();
+      } else if (Set.class.equals(type)) {
+        collection = new HashSet<Object>();
+      } else {
+        throw new UnsupportedOperationException();
+      }
+
+      // Decode values
+      if (ValueCodex.canDecode(elementType)) {
+        for (int i = 0, j = split.size(); i < j; i++) {
+          if (split.isNull(i)) {
+            collection.add(null);
+          } else {
+            Object element = ValueCodex.decode(elementType, split.get(i));
+            collection.add(element);
+          }
+        }
+      } else {
+        for (int i = 0, j = split.size(); i < j; i++) {
+          if (split.isNull(i)) {
+            collection.add(null);
+          } else {
+            Object element = decode(source, elementType, null, split.get(i));
+            collection.add(element);
+          }
+        }
+      }
+      return collection;
+    }
+
+    if (source.isEntityType(type) || source.isValueType(type) || EntityProxyId.class.equals(type)) {
+      return source.getBeanForPayload(split).as();
+    }
+
+    // Fall back to values
+    return ValueCodex.decode(type, split);
+  }
+
+  /**
+   * Collection support is limited to value types and resolving ids.
+   */
+  public static Object decode(EntitySource source, Class<?> type, Class<?> elementType,
+      String jsonPayload) {
+    Splittable split = StringQuoter.split(jsonPayload);
+    return decode(source, type, elementType, split);
+  }
+
+  /**
+   * Create a wire-format representation of an object.
+   */
+  public static Splittable encode(EntitySource source, Object value) {
+    if (value == null) {
+      return Splittable.NULL;
+    }
+
+    if (value instanceof Poser<?>) {
+      value = ((Poser<?>) value).getPosedValue();
+    }
+
+    if (value instanceof Iterable<?>) {
+      StringBuffer toReturn = new StringBuffer();
+      toReturn.append('[');
+      boolean first = true;
+      for (Object val : ((Iterable<?>) value)) {
+        if (!first) {
+          toReturn.append(',');
+        } else {
+          first = false;
+        }
+        if (val == null) {
+          toReturn.append("null");
+        } else {
+          toReturn.append(encode(source, val).getPayload());
+        }
+      }
+      toReturn.append(']');
+      return StringQuoter.split(toReturn.toString());
+    }
+
+    if (value instanceof BaseProxy) {
+      AutoBean<BaseProxy> autoBean = AutoBeanUtils.getAutoBean((BaseProxy) value);
+      value = BaseProxyCategory.stableId(autoBean);
+    }
+
+    if (value instanceof SimpleProxyId<?>) {
+      return source.getSerializedProxyId((SimpleProxyId<?>) value);
+    }
+
+    return ValueCodex.encode(value);
+  }
+}
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
new file mode 100644
index 0000000..4349d23
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/EntityProxyCategory.java
@@ -0,0 +1,64 @@
+/*
+ * 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.requestfactory.shared.impl;
+
+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.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanUtils;
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+
+/**
+ * Contains static implementation of EntityProxy-specific methods.
+ */
+public class EntityProxyCategory {
+
+  /**
+   * EntityProxies are equal if they are from the same RequestContext and their
+   * stableIds are equal.
+   */
+  public static boolean equals(AutoBean<? extends EntityProxy> bean, Object o) {
+    if (!(o instanceof EntityProxy)) {
+      return false;
+    }
+    AutoBean<EntityProxy> other = AutoBeanUtils.getAutoBean((EntityProxy) o);
+    if (other == null) {
+      // Unexpected, could be an user-provided implementation?
+      return false;
+    }
+
+    // Object comparison intentional. True if both null or both the same
+    return stableId(bean).equals(stableId(other))
+        && requestContext(bean) == requestContext(other);
+  }
+
+  /**
+   * Hashcode is always that of the stableId, since it's stable across time.
+   */
+  public static int hashCode(AutoBean<? extends EntityProxy> bean) {
+    return stableId(bean).hashCode();
+  }
+
+  /**
+   * Effectively overrides {@link BaseProxyCategory#stableId(AutoBean)} to
+   * return a narrower bound.
+   */
+  public static <T extends EntityProxy> SimpleEntityProxyId<T> stableId(
+      AutoBean<? extends T> bean) {
+    return bean.getTag(STABLE_ID);
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/FindRequest.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/FindRequest.java
new file mode 100644
index 0000000..5061b7f
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/FindRequest.java
@@ -0,0 +1,35 @@
+/*
+ * 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.requestfactory.shared.impl;
+
+import com.google.web.bindery.requestfactory.server.impl.FindService;
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.EntityProxyId;
+import com.google.web.bindery.requestfactory.shared.Request;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.Service;
+
+/**
+ * Request selector interface for implementing a find method.
+ */
+@Service(FindService.class)
+public interface FindRequest extends RequestContext {
+  /**
+   * Use the implicit lookup in passing EntityProxy types to service methods.
+   */
+  Request<EntityProxy> find(EntityProxyId<?> proxy);
+}
\ No newline at end of file
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/IdFactory.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/IdFactory.java
new file mode 100644
index 0000000..c93d893
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/IdFactory.java
@@ -0,0 +1,249 @@
+/*
+ * 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.requestfactory.shared.impl;
+
+import com.google.web.bindery.requestfactory.shared.BaseProxy;
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.ValueProxy;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Handles common code for creating SimpleProxyIds.
+ */
+public abstract class IdFactory {
+  /**
+   * Maps ephemeral history tokens to an id object. This canonicalizing mapping
+   * resolves the problem of EntityProxyIds hashcodes changing after persist.
+   * Only ids that are created in the RequestFactory are stored here.
+   */
+  private final Map<String, SimpleProxyId<?>> ephemeralIds = new HashMap<String, SimpleProxyId<?>>();
+
+  /**
+   * Allocates an ephemeral proxy id. This object is only valid for the lifetime
+   * of the RequestFactory.
+   */
+  public <P extends BaseProxy> SimpleProxyId<P> allocateId(Class<P> clazz) {
+    SimpleProxyId<P> toReturn = createId(clazz, ephemeralIds.size() + 1);
+    ephemeralIds.put(getHistoryToken(toReturn), toReturn);
+    return toReturn;
+  }
+
+  /**
+   * Allocates a synthetic proxy id. This object is only valid for the lifetime
+   * of a request.
+   */
+  public <P extends BaseProxy> SimpleProxyId<P> allocateSyntheticId(
+      Class<P> clazz, int syntheticId) {
+    assert syntheticId > 0;
+    SimpleProxyId<P> toReturn = createId(clazz, "%" + syntheticId);
+    toReturn.setSyntheticId(syntheticId);
+    return toReturn;
+  }
+
+  /**
+   * A utility function to handle generic type conversion. This method will also
+   * assert that {@code clazz} is actually an EntityProxy type.
+   */
+  @SuppressWarnings("unchecked")
+  public <P extends EntityProxy> Class<P> asEntityProxy(
+      Class<? extends BaseProxy> clazz) {
+    assert isEntityType(clazz) : clazz.getName()
+        + " is not an EntityProxy type";
+    return (Class<P>) clazz;
+  }
+
+  /**
+   * A utility function to handle generic type conversion. This method will also
+   * assert that {@code clazz} is actually a ValueProxy type.
+   */
+  @SuppressWarnings("unchecked")
+  public <P extends ValueProxy> Class<P> asValueProxy(
+      Class<? extends BaseProxy> clazz) {
+    assert isValueType(clazz) : clazz.getName() + " is not a ValueProxy type";
+    return (Class<P>) clazz;
+  }
+
+  public <P extends BaseProxy> SimpleProxyId<P> getBaseProxyId(
+      String historyToken) {
+    assert !IdUtil.isSynthetic(historyToken) : "Synthetic id resolution"
+        + " should be handled by AbstractRequestContext";
+    if (IdUtil.isPersisted(historyToken)) {
+      return getId(IdUtil.getTypeToken(historyToken),
+          IdUtil.getServerId(historyToken));
+    }
+    if (IdUtil.isEphemeral(historyToken)) {
+      @SuppressWarnings("unchecked")
+      SimpleProxyId<P> toReturn = (SimpleProxyId<P>) ephemeralIds.get(historyToken);
+
+      /*
+       * This is tested in FindServiceTest.testFetchUnpersistedFutureId. In
+       * order to get here, the user would have to get an unpersisted history
+       * token and attempt to use it with a different RequestFactory instance.
+       * This could occur if an ephemeral token were bookmarked. In this case,
+       * we'll create a token, however it will never match anything.
+       */
+      if (toReturn == null) {
+        Class<P> clazz = checkTypeToken(IdUtil.getTypeToken(historyToken));
+        toReturn = createId(clazz, -1 * ephemeralIds.size());
+        ephemeralIds.put(historyToken, toReturn);
+      }
+
+      return toReturn;
+    }
+    throw new IllegalArgumentException(historyToken);
+  }
+
+  public String getHistoryToken(SimpleProxyId<?> proxy) {
+    SimpleProxyId<?> id = (SimpleProxyId<?>) proxy;
+    String token = getTypeToken(proxy.getProxyClass());
+    if (id.isEphemeral()) {
+      return IdUtil.ephemeralId(id.getClientId(), token);
+    } else if (id.isSynthetic()) {
+      return IdUtil.syntheticId(id.getSyntheticId(), token);
+    } else {
+      return IdUtil.persistedId(id.getServerId(), token);
+    }
+  }
+
+  /**
+   * Create or retrieve a SimpleProxyId. If both the serverId and clientId are
+   * specified and the id is ephemeral, it will be updated with the server id.
+   */
+  public <P extends BaseProxy> SimpleProxyId<P> getId(Class<P> clazz,
+      String serverId, int clientId) {
+    return getId(getTypeToken(clazz), serverId, clientId);
+  }
+
+  /**
+   * Create or retrieve a SimpleProxyId.
+   */
+  public <P extends BaseProxy> SimpleProxyId<P> getId(String typeToken,
+      String serverId) {
+    return getId(typeToken, serverId, 0);
+  }
+
+  /**
+   * Create or retrieve a SimpleEntityProxyId. If both the serverId and clientId
+   * are specified and the id is ephemeral, it will be updated with the server
+   * id.
+   */
+  public <P extends BaseProxy> SimpleProxyId<P> getId(String typeToken,
+      String serverId, int clientId) {
+    /*
+     * If there's a clientId, that probably means we've just created a brand-new
+     * EntityProxy or have just persisted something on the server.
+     */
+    if (clientId > 0) {
+      // Try a cache lookup for the ephemeral key
+      String ephemeralKey = IdUtil.ephemeralId(clientId, typeToken);
+      @SuppressWarnings("unchecked")
+      SimpleProxyId<P> toReturn = (SimpleProxyId<P>) ephemeralIds.get(ephemeralKey);
+
+      // Do we need to allocate an ephemeral id?
+      if (toReturn == null) {
+        Class<P> clazz = getTypeFromToken(typeToken);
+        toReturn = createId(clazz, clientId);
+        ephemeralIds.put(ephemeralKey, toReturn);
+      }
+
+      // If it's ephemeral, see if we have a serverId and save it
+      if (toReturn.isEphemeral()) {
+        // Sanity check
+        assert toReturn.getProxyClass().equals(getTypeFromToken(typeToken));
+
+        if (serverId != null) {
+          /*
+           * Record the server id so a later "find" operation will have an equal
+           * stableId.
+           */
+          toReturn.setServerId(serverId);
+          String serverKey = IdUtil.persistedId(serverId, typeToken);
+          ephemeralIds.put(serverKey, toReturn);
+        }
+      }
+      return toReturn;
+    }
+
+    // Should never get this far without a server id
+    assert serverId != null : "serverId";
+
+    String serverKey = IdUtil.persistedId(serverId, typeToken);
+    @SuppressWarnings("unchecked")
+    SimpleProxyId<P> toReturn = (SimpleProxyId<P>) ephemeralIds.get(serverKey);
+    if (toReturn != null) {
+      // A cache hit for a locally-created object that has been persisted
+      return toReturn;
+    }
+
+    /*
+     * No existing id, so it was never an ephemeral id created by this
+     * RequestFactory, so we don't need to record it. This should be the normal
+     * case for read-dominated applications.
+     */
+    Class<P> clazz = getTypeFromToken(typeToken);
+    assert clazz != null : "No class literal for " + typeToken;
+    return createId(clazz, serverId);
+  }
+
+  public abstract boolean isEntityType(Class<?> clazz);
+
+  public abstract boolean isValueType(Class<?> clazz);
+
+  protected abstract <P extends BaseProxy> Class<P> getTypeFromToken(
+      String typeToken);
+
+  protected abstract String getTypeToken(Class<? extends BaseProxy> clazz);
+
+  private <P> Class<P> checkTypeToken(String token) {
+    @SuppressWarnings("unchecked")
+    Class<P> clazz = (Class<P>) getTypeFromToken(token);
+    if (clazz == null) {
+      throw new IllegalArgumentException("Unknnown type");
+    }
+    return clazz;
+  }
+
+  private <P extends BaseProxy> SimpleProxyId<P> createId(Class<P> clazz,
+      int clientId) {
+    SimpleProxyId<P> toReturn;
+    if (isValueType(clazz)) {
+      toReturn = new SimpleProxyId<P>(clazz, clientId);
+    } else {
+      @SuppressWarnings("unchecked")
+      SimpleProxyId<P> temp = (SimpleProxyId<P>) new SimpleEntityProxyId<EntityProxy>(
+          asEntityProxy(clazz), clientId);
+      toReturn = (SimpleProxyId<P>) temp;
+    }
+    return toReturn;
+  }
+
+  private <P extends BaseProxy> SimpleProxyId<P> createId(Class<P> clazz,
+      String serverId) {
+    SimpleProxyId<P> toReturn;
+    if (isValueType(clazz)) {
+      toReturn = new SimpleProxyId<P>(clazz, serverId);
+    } else {
+      @SuppressWarnings("unchecked")
+      SimpleProxyId<P> temp = (SimpleProxyId<P>) new SimpleEntityProxyId<EntityProxy>(
+          asEntityProxy(clazz), serverId);
+      toReturn = (SimpleProxyId<P>) temp;
+    }
+    return toReturn;
+  }
+
+}
\ No newline at end of file
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/IdUtil.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/IdUtil.java
new file mode 100644
index 0000000..148d076
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/IdUtil.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.requestfactory.shared.impl;
+
+/**
+ * Common functions for slicing and dicing EntityProxy ids.
+ */
+class IdUtil {
+  private static final String ANY_SEPARATOR_PATTERN = "@[012]@";
+  private static final String EPHEMERAL_SEPARATOR = "@1@";
+  private static final String TOKEN_SEPARATOR = "@0@";
+  private static final String SYNTHETIC_SEPARATOR = "@2@";
+
+  private static final int ID_TOKEN_INDEX = 0;
+  private static final int TYPE_TOKEN_INDEX = 1;
+
+  public static String ephemeralId(int clientId, String typeToken) {
+    return clientId + EPHEMERAL_SEPARATOR + typeToken;
+  }
+
+  public static int getClientId(String encodedId) {
+    return Integer.valueOf(asEphemeral(encodedId)[ID_TOKEN_INDEX]);
+  }
+
+  public static String getServerId(String encodedId) {
+    return asPersisted(encodedId)[ID_TOKEN_INDEX];
+  }
+
+  public static int getSyntheticId(String encodedId) {
+    return Integer.valueOf(asSynthetic(encodedId)[ID_TOKEN_INDEX]);
+  }
+
+  public static String getTypeToken(String encodedId) {
+    String[] split = asAny(encodedId);
+    if (split.length == 2) {
+      return split[TYPE_TOKEN_INDEX];
+    }
+    return null;
+  }
+
+  public static boolean isEphemeral(String encodedId) {
+    return encodedId.contains(EPHEMERAL_SEPARATOR);
+  }
+
+  public static boolean isPersisted(String encodedId) {
+    return encodedId.contains(TOKEN_SEPARATOR);
+  }
+
+  public static boolean isSynthetic(String encodedId) {
+    return encodedId.contains(SYNTHETIC_SEPARATOR);
+  }
+
+  public static String persistedId(String serverId, String typeToken) {
+    return serverId + TOKEN_SEPARATOR + typeToken;
+  }
+
+  public static String syntheticId(int syntheticId, String historyToken) {
+    return syntheticId + SYNTHETIC_SEPARATOR + historyToken;
+  }
+
+  private static String[] asAny(String encodedId) {
+    return encodedId.split(ANY_SEPARATOR_PATTERN);
+  }
+
+  private static String[] asEphemeral(String encodedId) {
+    return encodedId.split(EPHEMERAL_SEPARATOR);
+  }
+
+  private static String[] asPersisted(String encodedId) {
+    return encodedId.split(TOKEN_SEPARATOR);
+  }
+
+  private static String[] asSynthetic(String encodedId) {
+    return encodedId.split(SYNTHETIC_SEPARATOR);
+  }
+
+  /**
+   * Utility class.
+   */
+  private IdUtil() {
+  }
+}
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
new file mode 100644
index 0000000..9dc6b4a
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/MessageFactoryHolder.java
@@ -0,0 +1,26 @@
+/*
+ * 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.requestfactory.shared.impl;
+
+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 = AutoBeanFactorySource.create(MessageFactory.class);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/Poser.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/Poser.java
new file mode 100644
index 0000000..07ba283
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/Poser.java
@@ -0,0 +1,30 @@
+/*
+ * 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.requestfactory.shared.impl;
+
+/**
+ * Used to lock down mutable, non-proxy, value objects when their owning proxy
+ * is frozen.
+ * 
+ * @param <T> the type of simple value the Poser is standing in for
+ */
+public interface Poser<T> {
+  T getPosedValue();
+
+  boolean isFrozen();
+
+  void setFrozen(boolean frozen);
+}
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
new file mode 100644
index 0000000..f13942b
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/ProxySerializerImpl.java
@@ -0,0 +1,240 @@
+/*
+ * 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.requestfactory.shared.impl;
+
+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;
+import com.google.web.bindery.requestfactory.shared.ProxySerializer;
+import com.google.web.bindery.requestfactory.shared.ProxyStore;
+import com.google.web.bindery.requestfactory.shared.messages.IdMessage;
+import com.google.web.bindery.requestfactory.shared.messages.OperationMessage;
+import com.google.web.bindery.requestfactory.shared.messages.IdMessage.Strength;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * The default implementation of ProxySerializer.
+ */
+class ProxySerializerImpl extends AbstractRequestContext implements
+    ProxySerializer {
+
+  /**
+   * Used internally to unwind the stack if data cannot be found in the backing
+   * store.
+   */
+  private static class NoDataException extends RuntimeException {
+  }
+
+  private final ProxyStore store;
+  /**
+   * If the user wants to serialize a proxy with a non-persistent id (including
+   * ValueProxy), we'll assign a synthetic id that is local to the store being
+   * used.
+   */
+  private final Map<SimpleProxyId<?>, SimpleProxyId<?>> syntheticIds = new HashMap<SimpleProxyId<?>, SimpleProxyId<?>>();
+
+  /**
+   * The ids of proxies whose content has been reloaded.
+   */
+  private final Set<SimpleProxyId<?>> restored = new HashSet<SimpleProxyId<?>>();
+  private final Map<SimpleProxyId<?>, AutoBean<?>> serialized = new HashMap<SimpleProxyId<?>, AutoBean<?>>();
+
+  public ProxySerializerImpl(AbstractRequestFactory factory, ProxyStore store) {
+    super(factory, Dialect.STANDARD);
+    this.store = store;
+  }
+
+  public <T extends BaseProxy> T deserialize(Class<T> proxyType, String key) {
+    // Fast exit to prevent getOperation from throwing an exception
+    if (store.get(key) == null) {
+      return null;
+    }
+    OperationMessage op = getOperation(proxyType, key);
+    @SuppressWarnings("unchecked")
+    SimpleProxyId<T> id = (SimpleProxyId<T>) getId(op);
+    return doDeserialize(id);
+  }
+
+  public <T extends EntityProxy> T deserialize(EntityProxyId<T> id) {
+    return doDeserialize((SimpleEntityProxyId<T>) id);
+  }
+
+  /**
+   * Replace non-persistent ids with store-local ids.
+   */
+  @Override
+  public Splittable getSerializedProxyId(SimpleProxyId<?> stableId) {
+    return super.getSerializedProxyId(serializedId(stableId));
+  }
+
+  public String serialize(BaseProxy rootObject) {
+    final AutoBean<? extends BaseProxy> root = AutoBeanUtils.getAutoBean(rootObject);
+    if (root == null) {
+      // Unexpected, some kind of foreign implementation of the BaseProxy?
+      throw new IllegalArgumentException();
+    }
+
+    final SimpleProxyId<?> id = serializedId(BaseProxyCategory.stableId(root));
+    // Only persistent and synthetic ids expected
+    assert !id.isEphemeral() : "Unexpected ephemeral id " + id.toString();
+
+    /*
+     * Don't repeatedly serialize the same proxy, unless we're looking at a
+     * mutable instance.
+     */
+    AutoBean<?> previous = serialized.get(id);
+    if (previous == null || !previous.isFrozen()) {
+      serialized.put(id, root);
+      serializeOneProxy(id, root);
+      root.accept(new AutoBeanVisitor() {
+        @Override
+        public void endVisit(AutoBean<?> bean, Context ctx) {
+          // Avoid unnecessary method call
+          if (bean == root) {
+            return;
+          }
+          if (isEntityType(bean.getType()) || isValueType(bean.getType())) {
+            serialize((BaseProxy) bean.as());
+          }
+        }
+
+        @Override
+        public void endVisitCollectionProperty(String propertyName,
+            AutoBean<Collection<?>> value, CollectionPropertyContext ctx) {
+          if (value == null) {
+            return;
+          }
+          if (isEntityType(ctx.getElementType())
+              || isValueType(ctx.getElementType())) {
+            for (Object o : value.as()) {
+              serialize((BaseProxy) o);
+            }
+          }
+        }
+      });
+    }
+
+    return getRequestFactory().getHistoryToken(id);
+  }
+
+  @Override
+  protected AutoBeanFactory getAutoBeanFactory() {
+    return getRequestFactory().getAutoBeanFactory();
+  }
+
+  @Override
+  SimpleProxyId<BaseProxy> getId(IdMessage op) {
+    if (Strength.SYNTHETIC.equals(op.getStrength())) {
+      return getRequestFactory().allocateSyntheticId(
+          getRequestFactory().getTypeFromToken(op.getTypeToken()),
+          op.getSyntheticId());
+    }
+    return super.getId(op);
+  }
+
+  @Override
+  <Q extends BaseProxy> AutoBean<Q> getProxyForReturnPayloadGraph(
+      SimpleProxyId<Q> id) {
+    AutoBean<Q> toReturn = super.getProxyForReturnPayloadGraph(id);
+    if (restored.add(id)) {
+      /*
+       * If we haven't seen the id before, use the data in the OperationMessage
+       * to repopulate the properties of the canonical bean for this id.
+       */
+      OperationMessage op = getOperation(id.getProxyClass(),
+          getRequestFactory().getHistoryToken(id));
+      this.processReturnOperation(id, op);
+      toReturn.setTag(Constants.STABLE_ID, super.getId(op));
+    }
+    return toReturn;
+  }
+
+  /**
+   * Reset all temporary state.
+   */
+  private void clear() {
+    syntheticIds.clear();
+    restored.clear();
+    serialized.clear();
+  }
+
+  private <T extends BaseProxy> T doDeserialize(SimpleProxyId<T> id) {
+    try {
+      return getProxyForReturnPayloadGraph(id).as();
+    } catch (NoDataException e) {
+      return null;
+    } finally {
+      clear();
+    }
+  }
+
+  /**
+   * Load the OperationMessage containing the object state from the backing
+   * store.
+   */
+  private <T> OperationMessage getOperation(Class<T> proxyType, String key) {
+    Splittable data = store.get(key);
+    if (data == null) {
+      throw new NoDataException();
+    }
+
+    OperationMessage op = AutoBeanCodex.decode(MessageFactoryHolder.FACTORY,
+        OperationMessage.class, data).as();
+    return op;
+  }
+
+  /**
+   * Convert any non-persistent ids into store-local synthetic ids.
+   */
+  private <T extends BaseProxy> SimpleProxyId<T> serializedId(
+      SimpleProxyId<T> stableId) {
+    assert !stableId.isSynthetic();
+    if (stableId.isEphemeral()) {
+      @SuppressWarnings("unchecked")
+      SimpleProxyId<T> syntheticId = (SimpleProxyId<T>) syntheticIds.get(stableId);
+      if (syntheticId == null) {
+        int nextId = store.nextId();
+        assert nextId >= 0 : "ProxyStore.nextId() returned a negative number "
+            + nextId;
+        syntheticId = getRequestFactory().allocateSyntheticId(
+            stableId.getProxyClass(), nextId + 1);
+        syntheticIds.put(stableId, syntheticId);
+      }
+      return syntheticId;
+    }
+    return stableId;
+  }
+
+  private void serializeOneProxy(SimpleProxyId<?> idForSerialization,
+      AutoBean<? extends BaseProxy> bean) {
+    AutoBean<OperationMessage> op = makeOperationMessage(
+        serializedId(BaseProxyCategory.stableId(bean)), bean, false);
+
+    store.put(getRequestFactory().getHistoryToken(idForSerialization),
+        AutoBeanCodex.encode(op));
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/RequestData.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/RequestData.java
new file mode 100644
index 0000000..e8b0959
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/RequestData.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.requestfactory.shared.impl;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A class that encapsulates the parameters and method name to be invoked on the
+ * server.
+ */
+public class RequestData {
+  private final Class<?> elementType;
+  private final String operation;
+  private final Object[] parameters;
+  private Set<String> propertyRefs;
+  private final Class<?> returnType;
+  private Map<String, Object> requestParameters;
+  private Object requestContent;
+  private String apiVersion;
+
+  public RequestData(String operation, Object[] parameters,
+      Class<?> returnType, Class<?> elementType) {
+    this.operation = operation;
+    this.parameters = parameters;
+    this.returnType = returnType;
+    this.elementType = elementType;
+  }
+
+  /**
+   * Used by generated code.
+   */
+  public RequestData(String operation, Object[] parameters,
+      Set<String> propertyRefs, Class<?> returnType, Class<?> elementType) {
+    this(operation, parameters, returnType, elementType);
+    setPropertyRefs(propertyRefs);
+  }
+
+  public String getApiVersion() {
+    return apiVersion;
+  }
+
+  /**
+   * Used to interpret the returned payload.
+   */
+  public Class<?> getElementType() {
+    return elementType;
+  }
+
+  public Map<String, Object> getNamedParameters() {
+    return requestParameters == null ? Collections.<String, Object> emptyMap()
+        : requestParameters;
+  }
+
+  public String getOperation() {
+    return operation;
+  }
+
+  /**
+   * Used by standard-mode payloads and InstanceRequest subtypes to reset the
+   * instance object in the <code>using</code> method.
+   */
+  public Object[] getOrderedParameters() {
+    return parameters;
+  }
+
+  public Set<String> getPropertyRefs() {
+    return propertyRefs;
+  }
+
+  public Object getRequestResource() {
+    return requestContent;
+  }
+
+  /**
+   * Used to interpret the returned payload.
+   */
+  public Class<?> getReturnType() {
+    return returnType;
+  }
+
+  public void setApiVersion(String apiVersion) {
+    this.apiVersion = apiVersion;
+  }
+
+  public void setNamedParameter(String key, Object value) {
+    if (requestParameters == null) {
+      requestParameters = new HashMap<String, Object>();
+    }
+    requestParameters.put(key, value);
+  }
+
+  public void setPropertyRefs(Set<String> propertyRefs) {
+    this.propertyRefs = propertyRefs;
+  }
+
+  /**
+   * Represents the {@code request} object in a JSON-RPC request.
+   * 
+   * @see com.google.web.bindery.requestfactory.shared.JsonRpcContent
+   */
+  public void setRequestContent(Object requestContent) {
+    this.requestContent = requestContent;
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/SimpleEntityProxyId.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/SimpleEntityProxyId.java
new file mode 100644
index 0000000..77d7451
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/SimpleEntityProxyId.java
@@ -0,0 +1,44 @@
+/*
+ * 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.requestfactory.shared.impl;
+
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.EntityProxyId;
+
+/**
+ * Extends SimpleProxyId with the correct parameterization to implement
+ * EntityProxyId.
+ * 
+ * @param <P> the type of EntityProxy object the id describes
+ */
+public class SimpleEntityProxyId<P extends EntityProxy> extends
+    SimpleProxyId<P> implements EntityProxyId<P> {
+
+  /**
+   * Construct an ephemeral id. May be called only from
+   * {@link IdFactory#getId()}.
+   */
+  SimpleEntityProxyId(Class<P> proxyClass, int clientId) {
+    super(proxyClass, clientId);
+  }
+
+  /**
+   * Construct a stable id. May only be called from {@link IdFactory#getId()}
+   */
+  SimpleEntityProxyId(Class<P> proxyClass, String encodedAddress) {
+    super(proxyClass, encodedAddress);
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/SimpleProxyId.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/SimpleProxyId.java
new file mode 100644
index 0000000..a1946d0
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/SimpleProxyId.java
@@ -0,0 +1,190 @@
+/*
+ * 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.requestfactory.shared.impl;
+
+import com.google.web.bindery.requestfactory.shared.BaseProxy;
+
+/**
+ * The base implementation of id objects in the RequestFactory system. This type
+ * exists to allow ValueProxies to be implemented in the same manner as an
+ * EntityProxy as far as metadata maintenance is concerned. There is a specific
+ * subtype {@link SimpleEntityProxyId} which implements the requisite public
+ * interface for EntityProxy types.
+ * 
+ * @param <P> the type of BaseProxy object the id describes
+ */
+public class SimpleProxyId<P extends BaseProxy> {
+  /**
+   * A placeholder value for {@link #clientId} to indicate the id was not
+   * created locally.
+   */
+  public static final int NEVER_EPHEMERAL = -1;
+
+  /**
+   * The client-side id is ephemeral, and is valid only during the lifetime of a
+   * module. Any use of the client-side id except to send to the server as a
+   * bookkeeping exercise is wrong.
+   */
+  private final int clientId;
+
+  /**
+   * The encodedAddress is totally opaque to the client. It's probably a
+   * base64-encoded string, but it could be digits of pi. Any code that does
+   * anything other than send the contents of this field back to the server is
+   * wrong.
+   */
+  private String encodedAddress;
+
+  /**
+   * The hashcode of the id must remain stable, even if the server id is later
+   * assigned.
+   */
+  private final int hashCode;
+
+  /**
+   * The EntityProxy type.
+   */
+  private final Class<P> proxyClass;
+
+  /**
+   * A flag to indicate that the id is synthetic, that it is not valid beyond
+   * the duration of the request.
+   */
+  private int syntheticId;
+
+  /**
+   * Construct an ephemeral id. May be called only from
+   * {@link IdFactory#createId()}.
+   */
+  SimpleProxyId(Class<P> proxyClass, int clientId) {
+    assert proxyClass != null;
+    this.clientId = clientId;
+    this.proxyClass = proxyClass;
+    hashCode = clientId;
+  }
+
+  /**
+   * Construct a stable id. May only be called from {@link IdFactory#createId()}
+   */
+  SimpleProxyId(Class<P> proxyClass, String encodedAddress) {
+    assert proxyClass != null;
+    assert encodedAddress != null;
+    setServerId(encodedAddress);
+    clientId = NEVER_EPHEMERAL;
+    hashCode = encodedAddress.hashCode();
+    this.proxyClass = proxyClass;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof SimpleProxyId<?>)) {
+      return false;
+    }
+    SimpleProxyId<?> other = (SimpleProxyId<?>) o;
+    if (!proxyClass.equals(other.proxyClass)) {
+      return false;
+    }
+
+    if (clientId != NEVER_EPHEMERAL && clientId == other.clientId) {
+      /*
+       * Unexpected: It should be the case that locally-created ids are never
+       * aliased and will be caught by the first if statement.
+       */
+      return true;
+    }
+
+    if (encodedAddress != null && encodedAddress.equals(other.encodedAddress)) {
+      return true;
+    }
+    return false;
+  }
+
+  public int getClientId() {
+    return clientId;
+  }
+
+  public Class<P> getProxyClass() {
+    return proxyClass;
+  }
+
+  /**
+   * TODO: Rename to getAddress().
+   */
+  public String getServerId() {
+    return encodedAddress;
+  }
+
+  /**
+   * A flag to indicate that the id is synthetic, that it is not valid beyond
+   * the duration of the request.
+   */
+  public int getSyntheticId() {
+    return syntheticId;
+  }
+
+  @Override
+  public int hashCode() {
+    return hashCode;
+  }
+
+  public boolean isEphemeral() {
+    return encodedAddress == null;
+  }
+
+  public boolean isSynthetic() {
+    return syntheticId > 0;
+  }
+
+  /**
+   * Allows the server address token to be set. This method may be called
+   * exactly once over the lifetime of an id.
+   */
+  public void setServerId(String encodedAddress) {
+    if (this.encodedAddress != null) {
+      throw new IllegalStateException();
+    }
+    assert !"null".equals(encodedAddress);
+    this.encodedAddress = encodedAddress;
+  }
+
+  public void setSyntheticId(int syntheticId) {
+    this.syntheticId = syntheticId;
+  }
+
+  /**
+   * For debugging use only.
+   */
+  @Override
+  public String toString() {
+    if (isEphemeral()) {
+      return IdUtil.ephemeralId(clientId, proxyClass.getName());
+    } else if (isSynthetic()) {
+      return IdUtil.syntheticId(syntheticId, proxyClass.getName());
+    } else {
+      return IdUtil.persistedId(encodedAddress, proxyClass.getName());
+    }
+  }
+
+  /**
+   * Returns <code>true</code> if the id was created as an ephemeral id.
+   */
+  public boolean wasEphemeral() {
+    return clientId != NEVER_EPHEMERAL;
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/TypeLibrary.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/TypeLibrary.java
new file mode 100644
index 0000000..1d3d969
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/TypeLibrary.java
@@ -0,0 +1,64 @@
+/*
+ * 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.requestfactory.shared.impl;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Utility methods for querying, encoding, and decoding typed
+ * payload data.
+ */
+public class TypeLibrary {
+
+  public static final Collection<Class<?>> VALUE_TYPES;
+
+  static {
+    HashSet<Class<?>> valueTypes = new HashSet<Class<?>>();
+    valueTypes.add(BigDecimal.class);
+    valueTypes.add(BigInteger.class);
+    valueTypes.add(Boolean.class);
+    valueTypes.add(Byte.class);
+    valueTypes.add(Character.class);
+    valueTypes.add(Date.class);
+    valueTypes.add(Double.class);
+    valueTypes.add(Enum.class);
+    valueTypes.add(Float.class);
+    valueTypes.add(Integer.class);
+    valueTypes.add(Long.class);
+    valueTypes.add(Short.class);
+    valueTypes.add(String.class);
+    VALUE_TYPES = Collections.unmodifiableSet(valueTypes);
+  }
+
+  public static boolean isCollectionType(Class<?> type) {
+    return type == List.class || type == Set.class;
+  }
+
+  public static boolean isProxyType(Class<?> type) {
+    return !isValueType(type) && !isCollectionType(type);
+  }
+
+  public static boolean isValueType(Class<?> type) {
+    return VALUE_TYPES.contains(type);
+  }  
+}
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
new file mode 100644
index 0000000..95c5ca2
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/ValueProxyCategory.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.requestfactory.shared.impl;
+
+import static com.google.web.bindery.requestfactory.shared.impl.BaseProxyCategory.stableId;
+
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanUtils;
+import com.google.web.bindery.requestfactory.shared.ValueProxy;
+
+/**
+ * Contains static implementation of ValueProxy-specific methods.
+ */
+public class ValueProxyCategory {
+
+  /**
+   * ValueProxies are equal if they are from the same RequestContext and all of
+   * their properties are equal.
+   */
+  public static boolean equals(AutoBean<? extends ValueProxy> bean, Object o) {
+    if (!(o instanceof ValueProxy)) {
+      return false;
+    }
+    AutoBean<ValueProxy> other = AutoBeanUtils.getAutoBean((ValueProxy) o);
+    if (other == null) {
+      // Unexpected, could be an user-provided implementation?
+      return false;
+    }
+    if (!stableId(bean).getProxyClass().equals(stableId(other).getProxyClass())) {
+      // Compare AppleProxies to AppleProxies
+      return false;
+    }
+
+    // Compare the entire object graph
+    return AutoBeanUtils.deepEquals(bean, other);
+  }
+
+  /**
+   * Hashcode depends on property values.
+   */
+  public static int hashCode(AutoBean<? extends ValueProxy> bean) {
+    return AutoBeanUtils.getAllProperties(bean).hashCode();
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/posers/DatePoser.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/posers/DatePoser.java
new file mode 100644
index 0000000..3d71032
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/posers/DatePoser.java
@@ -0,0 +1,93 @@
+/*
+ * 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.requestfactory.shared.impl.posers;
+
+import com.google.web.bindery.requestfactory.shared.impl.Poser;
+
+import java.util.Date;
+
+/**
+ * A sometimes-mutable implementation of {@link Date}.
+ */
+@SuppressWarnings("deprecation")
+public class DatePoser extends Date implements Poser<Date> {
+  private boolean frozen;
+
+  public DatePoser(Date copy) {
+    super(copy.getTime());
+    setFrozen(true);
+  }
+
+  public Date getPosedValue() {
+    return new Date(getTime());
+  }
+
+  public boolean isFrozen() {
+    return frozen;
+  }
+
+  @Override
+  public void setDate(int date) {
+    checkFrozen();
+    super.setDate(date);
+  }
+
+  public void setFrozen(boolean frozen) {
+    this.frozen = frozen;
+  }
+
+  @Override
+  public void setHours(int hours) {
+    checkFrozen();
+    super.setHours(hours);
+  }
+
+  @Override
+  public void setMinutes(int minutes) {
+    checkFrozen();
+    super.setMinutes(minutes);
+  }
+
+  @Override
+  public void setMonth(int month) {
+    checkFrozen();
+    super.setMonth(month);
+  }
+
+  @Override
+  public void setSeconds(int seconds) {
+    checkFrozen();
+    super.setSeconds(seconds);
+  }
+
+  @Override
+  public void setTime(long time) {
+    checkFrozen();
+    super.setTime(time);
+  }
+
+  @Override
+  public void setYear(int year) {
+    checkFrozen();
+    super.setYear(year);
+  }
+
+  private void checkFrozen() {
+    if (frozen) {
+      throw new IllegalStateException("The Date has been frozen");
+    }
+  }
+}
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
new file mode 100644
index 0000000..6d1d6c1
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/IdMessage.java
@@ -0,0 +1,84 @@
+/*
+ * 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.requestfactory.shared.messages;
+
+import com.google.web.bindery.autobean.shared.AutoBean.PropertyName;
+
+/**
+ * Used as a base type for messages that are about a particular id.
+ */
+public interface IdMessage {
+  /**
+   * Describes the longevity of the id.
+   */
+  public enum Strength {
+    /**
+     * The id is indefinitely persistent and can be freely reused by the client
+     * and the server.
+     */
+    @PropertyName("0")
+    PERSISTED,
+
+    /**
+     * The id is managed by the client and is generally unknown to the server.
+     */
+    @PropertyName("1")
+    EPHEMERAL,
+
+    /**
+     * The id not not managed by the client or server and is valid only for the
+     * duration of a single request or response.
+     */
+    @PropertyName("2")
+    SYNTHETIC;
+  }
+
+  String CLIENT_ID = "C";
+  String SERVER_ID = "S";
+  String TYPE_TOKEN = "T";
+  String STRENGTH = "R";
+  String SYNTHETIC_ID = "Y";
+
+  @PropertyName(CLIENT_ID)
+  int getClientId();
+
+  @PropertyName(SERVER_ID)
+  String getServerId();
+
+  @PropertyName(STRENGTH)
+  Strength getStrength();
+
+  @PropertyName(SYNTHETIC_ID)
+  int getSyntheticId();
+
+  @PropertyName(TYPE_TOKEN)
+  String getTypeToken();
+
+  @PropertyName(CLIENT_ID)
+  void setClientId(int value);
+
+  @PropertyName(SERVER_ID)
+  void setServerId(String value);
+
+  @PropertyName(STRENGTH)
+  void setStrength(Strength value);
+
+  @PropertyName(SYNTHETIC_ID)
+  void setSyntheticId(int value);
+
+  @PropertyName(TYPE_TOKEN)
+  void setTypeToken(String value);
+}
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
new file mode 100644
index 0000000..00fcac3
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/InvocationMessage.java
@@ -0,0 +1,49 @@
+/*
+ * 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.requestfactory.shared.messages;
+
+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;
+
+/**
+ * Describes a method invocation.
+ */
+public interface InvocationMessage {
+  String OPERATIONS = "O";
+  String PARAMETERS = "P";
+  String PROPERTY_REFS = "R";
+
+  @PropertyName(OPERATIONS)
+  String getOperation();
+
+  @PropertyName(PARAMETERS)
+  List<Splittable> getParameters();
+
+  @PropertyName(PROPERTY_REFS)
+  Set<String> getPropertyRefs();
+
+  @PropertyName(OPERATIONS)
+  void setOperation(String value);
+
+  @PropertyName(PARAMETERS)
+  void setParameters(List<Splittable> value);
+
+  @PropertyName(PROPERTY_REFS)
+  void setPropertyRefs(Set<String> value);
+}
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
new file mode 100644
index 0000000..275c7d5
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/JsonRpcRequest.java
@@ -0,0 +1,48 @@
+/*
+ * 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.requestfactory.shared.messages;
+
+import com.google.web.bindery.autobean.shared.AutoBean.PropertyName;
+import com.google.web.bindery.autobean.shared.Splittable;
+
+import java.util.Map;
+
+/**
+ * A JSON-RPC request payload.
+ */
+public interface JsonRpcRequest {
+  String getApiVersion();
+
+  int getId();
+
+  String getMethod();
+
+  Map<String, Splittable> getParams();
+
+  @PropertyName("jsonrpc")
+  String getVersion();
+
+  void setApiVersion(String version);
+
+  void setId(int id);
+
+  void setMethod(String method);
+
+  void setParams(Map<String, Splittable> params);
+
+  @PropertyName("jsonrpc")
+  void setVersion(String version);
+}
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
new file mode 100644
index 0000000..e22048b
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/MessageFactory.java
@@ -0,0 +1,40 @@
+/*
+ * 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.requestfactory.shared.messages;
+
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory;
+
+/**
+ * The factory for creating RequestFactory wire messages.
+ */
+public interface MessageFactory extends AutoBeanFactory {
+  AutoBean<ServerFailureMessage> failure();
+
+  AutoBean<IdMessage> id();
+
+  AutoBean<InvocationMessage> invocation();
+  
+  AutoBean<JsonRpcRequest> jsonRpcRequest();
+
+  AutoBean<OperationMessage> operation();
+
+  AutoBean<RequestMessage> request();
+
+  AutoBean<ResponseMessage> response();
+
+  AutoBean<ViolationMessage> violation();
+}
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
new file mode 100644
index 0000000..9ed7e67
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/OperationMessage.java
@@ -0,0 +1,42 @@
+/*
+ * 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.requestfactory.shared.messages;
+
+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;
+
+/**
+ * Represents an operation to be carried out on a single entity on the server.
+ */
+public interface OperationMessage extends IdMessage, VersionedMessage {
+  String OPERATION = "O";
+  String PROPERTY_MAP = "P";
+
+  @PropertyName(OPERATION)
+  WriteOperation getOperation();
+
+  @PropertyName(PROPERTY_MAP)
+  Map<String, Splittable> getPropertyMap();
+
+  @PropertyName(OPERATION)
+  void setOperation(WriteOperation value);
+
+  @PropertyName(PROPERTY_MAP)
+  void setPropertyMap(Map<String, Splittable> 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
new file mode 100644
index 0000000..8f35588
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/RequestMessage.java
@@ -0,0 +1,40 @@
+/*
+ * 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.requestfactory.shared.messages;
+
+import com.google.web.bindery.autobean.shared.AutoBean.PropertyName;
+
+import java.util.List;
+
+/**
+ * The message sent from the client to the server.
+ */
+public interface RequestMessage extends VersionedMessage {
+  String INVOCATION = "I";
+  String OPERATIONS = "O";
+
+  @PropertyName(INVOCATION)
+  List<InvocationMessage> getInvocations();
+
+  @PropertyName(OPERATIONS)
+  List<OperationMessage> getOperations();
+
+  @PropertyName(INVOCATION)
+  void setInvocations(List<InvocationMessage> value);
+
+  @PropertyName(OPERATIONS)
+  void setOperations(List<OperationMessage> value);
+}
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
new file mode 100644
index 0000000..47efd4d
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/ResponseMessage.java
@@ -0,0 +1,63 @@
+/*
+ * 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.requestfactory.shared.messages;
+
+import com.google.web.bindery.autobean.shared.AutoBean.PropertyName;
+import com.google.web.bindery.autobean.shared.Splittable;
+
+import java.util.List;
+
+/**
+ * The result of fulfilling a request on the server.
+ */
+public interface ResponseMessage extends VersionedMessage {
+  String GENERAL_FAILURE = "F";
+  String INVOCATION_RESULTS = "I";
+  String OPERATIONS = "O";
+  String STATUS_CODES = "S";
+  // V would conflict with versionedMessage
+  String VIOLATIONS = "X";
+
+  @PropertyName(GENERAL_FAILURE)
+  ServerFailureMessage getGeneralFailure();
+
+  @PropertyName(INVOCATION_RESULTS)
+  List<Splittable> getInvocationResults();
+
+  @PropertyName(OPERATIONS)
+  List<OperationMessage> getOperations();
+
+  @PropertyName(STATUS_CODES)
+  List<Boolean> getStatusCodes();
+
+  @PropertyName(VIOLATIONS)
+  List<ViolationMessage> getViolations();
+
+  @PropertyName(GENERAL_FAILURE)
+  void setGeneralFailure(ServerFailureMessage failure);
+
+  @PropertyName(INVOCATION_RESULTS)
+  void setInvocationResults(List<Splittable> value);
+
+  @PropertyName(OPERATIONS)
+  void setOperations(List<OperationMessage> value);
+
+  @PropertyName(STATUS_CODES)
+  void setStatusCodes(List<Boolean> value);
+
+  @PropertyName(VIOLATIONS)
+  void setViolations(List<ViolationMessage> value);
+}
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
new file mode 100644
index 0000000..7e101e3
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/ServerFailureMessage.java
@@ -0,0 +1,52 @@
+/*
+ * 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.requestfactory.shared.messages;
+
+import com.google.web.bindery.autobean.shared.AutoBean.PropertyName;
+
+/**
+ * Encapsulates a ServerFailure object.
+ */
+public interface ServerFailureMessage {
+  String EXCEPTION_TYPE = "X";
+  String MESSAGE = "M";
+  String STACK_TRACE = "S";
+  String FATAL = "F";
+
+  @PropertyName(EXCEPTION_TYPE)
+  String getExceptionType();
+
+  @PropertyName(MESSAGE)
+  String getMessage();
+
+  @PropertyName(STACK_TRACE)
+  String getStackTrace();
+  
+  @PropertyName(FATAL)
+  boolean isFatal();
+
+  @PropertyName(EXCEPTION_TYPE)
+  void setExceptionType(String exceptionType);
+
+  @PropertyName(FATAL)
+  void setFatal(boolean significant);
+
+  @PropertyName(MESSAGE)
+  void setMessage(String message);
+  
+  @PropertyName(STACK_TRACE)
+  void setStackTrace(String stackTrace);
+}
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
new file mode 100644
index 0000000..51fe40e
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/VersionedMessage.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.
+ */
+package com.google.web.bindery.requestfactory.shared.messages;
+
+import com.google.web.bindery.autobean.shared.AutoBean.PropertyName;
+
+/**
+ * Describes a message that contains version information.
+ */
+public interface VersionedMessage {
+  String VERSION = "V";
+
+  @PropertyName(VERSION)
+  String getVersion();
+
+  @PropertyName(VERSION)
+  void setVersion(String version);
+}
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
new file mode 100644
index 0000000..5b7725f
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/ViolationMessage.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.requestfactory.shared.messages;
+
+import com.google.web.bindery.autobean.shared.AutoBean.PropertyName;
+
+/**
+ * Represents a ConstraintViolation.
+ */
+public interface ViolationMessage extends IdMessage {
+  String MESSAGE = "M";
+  String PATH = "P";
+
+  @PropertyName(MESSAGE)
+  String getMessage();
+
+  @PropertyName(PATH)
+  String getPath();
+
+  @PropertyName(MESSAGE)
+  void setMessage(String value);
+
+  @PropertyName(PATH)
+  void setPath(String value);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/messages/package-info.java b/user/src/com/google/web/bindery/requestfactory/shared/messages/package-info.java
new file mode 100644
index 0000000..80b060d
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * 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 classes that define the RequestFactory wire format.
+ * 
+ * @since GWT 2.1.1
+ */
+@com.google.gwt.util.PreventSpuriousRebuilds
+package com.google.web.bindery.requestfactory.shared.messages;
+
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/package-info.java b/user/src/com/google/web/bindery/requestfactory/shared/package-info.java
new file mode 100644
index 0000000..837f728
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+/**
+ * Shared classes used on both the client and the server side for transmitting data between the
+ * server and the client in JSON format.
+ *
+ * @since GWT 2.1
+ */
+@com.google.gwt.util.PreventSpuriousRebuilds
+package com.google.web.bindery.requestfactory.shared;
diff --git a/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestContext.java b/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestContext.java
new file mode 100644
index 0000000..bcdb434
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestContext.java
@@ -0,0 +1,192 @@
+/*
+ * 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.requestfactory.vm;
+
+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;
+import com.google.web.bindery.requestfactory.shared.Request;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.impl.AbstractRequest;
+import com.google.web.bindery.requestfactory.shared.impl.AbstractRequestContext;
+import com.google.web.bindery.requestfactory.shared.impl.AbstractRequestFactory;
+import com.google.web.bindery.requestfactory.shared.impl.RequestData;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.Type;
+import java.util.Collection;
+
+/**
+ * An in-process implementation of RequestContext.
+ */
+class InProcessRequestContext extends AbstractRequestContext {
+  class RequestContextHandler implements InvocationHandler {
+    public Object invoke(Object proxy, Method method, final Object[] args)
+        throws Throwable {
+      // Maybe delegate to superclass
+      Class<?> owner = method.getDeclaringClass();
+      if (Object.class.equals(owner) || RequestContext.class.equals(owner)
+          || AbstractRequestContext.class.equals(owner)) {
+        try {
+          return method.invoke(InProcessRequestContext.this, args);
+        } catch (InvocationTargetException e) {
+          throw e.getCause();
+        }
+      }
+
+      /*
+       * Instance methods treat the 0-th argument as the instance on which to
+       * invoke the method.
+       */
+      final Object[] actualArgs;
+      Type returnGenericType;
+      boolean isInstance = InstanceRequest.class.isAssignableFrom(method.getReturnType());
+      if (isInstance) {
+        returnGenericType = TypeUtils.getParameterization(
+            InstanceRequest.class, method.getGenericReturnType(),
+            method.getReturnType())[1];
+        if (args == null) {
+          actualArgs = new Object[1];
+        } else {
+          // Save a slot for the this argument
+          actualArgs = new Object[args.length + 1];
+          System.arraycopy(args, 0, actualArgs, 1, args.length);
+        }
+      } else {
+        returnGenericType = TypeUtils.getSingleParameterization(Request.class,
+            method.getGenericReturnType(), method.getReturnType());
+        if (args == null) {
+          actualArgs = NO_ARGS;
+        } else {
+          actualArgs = args;
+        }
+      }
+
+      Class<?> returnType = TypeUtils.ensureBaseType(returnGenericType);
+      Class<?> elementType = Collection.class.isAssignableFrom(returnType)
+          ? TypeUtils.ensureBaseType(TypeUtils.getSingleParameterization(
+              Collection.class, returnGenericType)) : null;
+
+      final RequestData data;
+      if (dialect.equals(Dialect.STANDARD)) {
+        String operation = method.getDeclaringClass().getName() + "::"
+            + method.getName();
+
+        data = new RequestData(operation, actualArgs, returnType, elementType);
+      } else {
+        // Calculate request metadata
+        JsonRpcWireName wireInfo = method.getReturnType().getAnnotation(
+            JsonRpcWireName.class);
+        String apiVersion = wireInfo.version();
+        String operation = wireInfo.value();
+
+        int foundContent = -1;
+        final String[] parameterNames = args == null ? new String[0]
+            : new String[args.length];
+        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
+        parameter : for (int i = 0, j = parameterAnnotations.length; i < j; i++) {
+          for (Annotation annotation : parameterAnnotations[i]) {
+            if (PropertyName.class.equals(annotation.annotationType())) {
+              parameterNames[i] = ((PropertyName) annotation).value();
+              continue parameter;
+            } else if (JsonRpcContent.class.equals(annotation.annotationType())) {
+              foundContent = i;
+              continue parameter;
+            }
+          }
+          throw new UnsupportedOperationException("No "
+              + PropertyName.class.getCanonicalName()
+              + " annotation on parameter " + i + " of method "
+              + method.toString());
+        }
+        final int contentIdx = foundContent;
+
+        data = new RequestData(operation, actualArgs, returnType, elementType);
+        for (int i = 0, j = args.length; i < j; i++) {
+          if (i != contentIdx) {
+            data.setNamedParameter(parameterNames[i], args[i]);
+          } else {
+            data.setRequestContent(args[i]);
+          }
+          data.setApiVersion(apiVersion);
+        }
+      }
+
+      // Create the request, just filling in the RequestData details
+      final AbstractRequest<Object> req = new AbstractRequest<Object>(
+          InProcessRequestContext.this) {
+        @Override
+        protected RequestData makeRequestData() {
+          data.setPropertyRefs(propertyRefs);
+          return data;
+        }
+      };
+
+      if (!isInstance) {
+        // Instance invocations are enqueued when using() is called
+        addInvocation(req);
+      }
+
+      if (dialect.equals(Dialect.STANDARD)) {
+        return req;
+      } else if (dialect.equals(Dialect.JSON_RPC)) {
+        // Support optional parameters for JSON-RPC payloads
+        Class<?> requestType = method.getReturnType().asSubclass(Request.class);
+        return Proxy.newProxyInstance(requestType.getClassLoader(),
+            new Class<?>[] {requestType}, new InvocationHandler() {
+              public Object invoke(Object proxy, Method method, Object[] args)
+                  throws Throwable {
+                if (Object.class.equals(method.getDeclaringClass())
+                    || Request.class.equals(method.getDeclaringClass())) {
+                  return method.invoke(req, args);
+                } else if (BeanMethod.SET.matches(method)
+                    || BeanMethod.SET_BUILDER.matches(method)) {
+                  req.getRequestData().setNamedParameter(
+                      BeanMethod.SET.inferName(method), args[0]);
+                  return Void.TYPE.equals(method.getReturnType()) ? null
+                      : proxy;
+                }
+                throw new UnsupportedOperationException(method.toString());
+              }
+            });
+      } else {
+        throw new RuntimeException("Should not reach here");
+      }
+    }
+  }
+
+  static final Object[] NO_ARGS = new Object[0];
+  private final Dialect dialect;
+
+  protected InProcessRequestContext(AbstractRequestFactory factory,
+      Dialect dialect) {
+    super(factory, dialect);
+    this.dialect = dialect;
+  }
+
+  @Override
+  protected AutoBeanFactory getAutoBeanFactory() {
+    return ((InProcessRequestFactory) getRequestFactory()).getAutoBeanFactory();
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestFactory.java b/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestFactory.java
new file mode 100644
index 0000000..d9129c3
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestFactory.java
@@ -0,0 +1,115 @@
+/*
+ * 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.requestfactory.vm;
+
+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.web.bindery.event.shared.EventBus;
+import com.google.web.bindery.requestfactory.shared.BaseProxy;
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.EntityProxyId;
+import com.google.web.bindery.requestfactory.shared.JsonRpcService;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.RequestFactory;
+import com.google.web.bindery.requestfactory.shared.ValueProxy;
+import com.google.web.bindery.requestfactory.shared.impl.AbstractRequestContext.Dialect;
+import com.google.web.bindery.requestfactory.shared.impl.AbstractRequestFactory;
+import com.google.web.bindery.requestfactory.shared.impl.BaseProxyCategory;
+import com.google.web.bindery.requestfactory.shared.impl.EntityProxyCategory;
+import com.google.web.bindery.requestfactory.shared.impl.ValueProxyCategory;
+import com.google.web.bindery.requestfactory.vm.InProcessRequestContext.RequestContextHandler;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+/**
+ * A JRE-compatible implementation of RequestFactory.
+ */
+class InProcessRequestFactory extends AbstractRequestFactory {
+  @Category(value = {
+      EntityProxyCategory.class, ValueProxyCategory.class,
+      BaseProxyCategory.class})
+  @NoWrap(EntityProxyId.class)
+  interface Factory extends AutoBeanFactory {
+  }
+
+  class RequestFactoryHandler implements InvocationHandler {
+    public Object invoke(Object proxy, Method method, Object[] args)
+        throws Throwable {
+      if (Object.class.equals(method.getDeclaringClass())
+          || RequestFactory.class.equals(method.getDeclaringClass())) {
+        try {
+          return method.invoke(InProcessRequestFactory.this, args);
+        } catch (InvocationTargetException e) {
+          throw e.getCause();
+        }
+      }
+
+      Class<? extends RequestContext> context = method.getReturnType().asSubclass(
+          RequestContext.class);
+      Dialect dialect = method.getReturnType().isAnnotationPresent(
+          JsonRpcService.class) ? Dialect.JSON_RPC : Dialect.STANDARD;
+      RequestContextHandler handler = new InProcessRequestContext(
+          InProcessRequestFactory.this, dialect).new RequestContextHandler();
+      return context.cast(Proxy.newProxyInstance(
+          Thread.currentThread().getContextClassLoader(),
+          new Class<?>[] {context}, handler));
+    }
+  }
+
+  @Override
+  public void initialize(EventBus eventBus) {
+    throw new UnsupportedOperationException(
+        "An explicit RequestTransport must be provided");
+  }
+
+  @Override
+  public boolean isEntityType(Class<?> clazz) {
+    return EntityProxy.class.isAssignableFrom(clazz);
+  }
+
+  @Override
+  public boolean isValueType(Class<?> clazz) {
+    return ValueProxy.class.isAssignableFrom(clazz);
+  }
+
+  @Override
+  protected AutoBeanFactory getAutoBeanFactory() {
+    return AutoBeanFactorySource.create(Factory.class);
+  }
+
+  @Override
+  @SuppressWarnings("unchecked")
+  protected <P extends BaseProxy> Class<P> getTypeFromToken(String typeToken) {
+    try {
+      Class<? extends BaseProxy> found = Class.forName(typeToken, false,
+          Thread.currentThread().getContextClassLoader()).asSubclass(
+          BaseProxy.class);
+      return (Class<P>) found;
+    } catch (ClassNotFoundException e) {
+      return null;
+    }
+  }
+
+  @Override
+  protected String getTypeToken(Class<? extends BaseProxy> clazz) {
+    return isEntityType(clazz) || isValueType(clazz) ? clazz.getName() : null;
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/vm/RequestFactorySource.java b/user/src/com/google/web/bindery/requestfactory/vm/RequestFactorySource.java
new file mode 100644
index 0000000..6ac518e
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/vm/RequestFactorySource.java
@@ -0,0 +1,51 @@
+/*
+ * 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.requestfactory.vm;
+
+import com.google.web.bindery.requestfactory.shared.RequestFactory;
+import com.google.web.bindery.requestfactory.vm.InProcessRequestFactory.RequestFactoryHandler;
+
+import java.lang.reflect.Proxy;
+
+/**
+ * Create JRE-compatible instances of a RequestFactory interface.
+ * 
+ * <span style='color: red'>This is experimental, unsupported code.</span>
+ */
+public class RequestFactorySource {
+  /**
+   * Create an instance of a RequestFactory. The returned RequestFactory must be
+   * initialized with an explicit
+   * {@link com.google.web.bindery.requestfactory.shared.RequestTransport
+   * RequestTransport} via the
+   * {@link RequestFactory#initialize(com.google.gwt.event.shared.EventBus, com.google.web.bindery.requestfactory.shared.RequestTransport)
+   * initialize(EventBus, RequestTransport} method.
+   * 
+   * @param <T> the RequestFactory type
+   * @param requestFactory the RequestFactory type
+   * @return an instance of the RequestFactory type
+   * @see InProcessRequestTransport
+   */
+  public static <T extends RequestFactory> T create(Class<T> requestFactory) {
+    RequestFactoryHandler handler = new InProcessRequestFactory().new RequestFactoryHandler();
+    return requestFactory.cast(Proxy.newProxyInstance(
+        Thread.currentThread().getContextClassLoader(),
+        new Class<?>[] {requestFactory}, handler));
+  }
+
+  private RequestFactorySource() {
+  }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/vm/package-info.java b/user/src/com/google/web/bindery/requestfactory/vm/package-info.java
new file mode 100644
index 0000000..f78d89f
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/vm/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+/**
+ * Classes to use RequestFactory in a non-GWT (e.g., Android or desktop) JRE environment. 
+ *
+ * @since GWT 2.3
+ */
+@com.google.gwt.util.PreventSpuriousRebuilds
+package com.google.web.bindery.requestfactory.vm;
diff --git a/user/super/com/google/web/bindery/autobean/super/com/google/web/bindery/autobean/shared/ValueCodexHelper.java b/user/super/com/google/web/bindery/autobean/super/com/google/web/bindery/autobean/shared/ValueCodexHelper.java
new file mode 100644
index 0000000..bab08af
--- /dev/null
+++ b/user/super/com/google/web/bindery/autobean/super/com/google/web/bindery/autobean/shared/ValueCodexHelper.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.
+ */
+package com.google.web.bindery.autobean.shared;
+
+/**
+ * A no-op super-source version of ValueCodexHelper for web-mode compilations.
+ */
+class ValueCodexHelper {
+  /**
+   * Returns {@code false}.
+   */
+  static boolean canDecode(Class<?> clazz) {
+    return false;
+  }
+}
diff --git a/user/super/com/google/web/bindery/autobean/super/com/google/web/bindery/autobean/shared/impl/StringQuoter.java b/user/super/com/google/web/bindery/autobean/super/com/google/web/bindery/autobean/shared/impl/StringQuoter.java
new file mode 100644
index 0000000..d92b3bf
--- /dev/null
+++ b/user/super/com/google/web/bindery/autobean/super/com/google/web/bindery/autobean/shared/impl/StringQuoter.java
@@ -0,0 +1,87 @@
+/*
+ * 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.gwt.client.impl.JsoSplittable;
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.GwtScriptOnly;
+import com.google.gwt.core.client.JavaScriptException;
+import com.google.gwt.core.client.JsDate;
+import com.google.gwt.core.client.JsonUtils;
+import com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter;
+
+import java.util.Date;
+
+/**
+ * This a super-source version with a client-only implementation.
+ */
+@GwtScriptOnly
+public class StringQuoter {
+  public static Splittable create(boolean value) {
+    return JsoSplittable.create(value);
+  }
+
+  public static Splittable create(double value) {
+    return JsoSplittable.create(value);
+  }
+
+  public static Splittable create(String value) {
+    return JsoSplittable.create(value);
+  }
+
+  public static Splittable createIndexed() {
+    return JsoSplittable.createIndexed();
+  }
+
+  public static Splittable createSplittable() {
+    return JsoSplittable.create();
+  }
+
+  public static Splittable nullValue() {
+    return JsoSplittable.nullValue();
+  }
+
+  public static String quote(String raw) {
+    return JsonUtils.escapeValue(raw);
+  }
+
+  public static Splittable split(String payload) {
+    char c = payload.charAt(0);
+    boolean isSimple = c != '{' && c != '[';
+    if (isSimple) {
+      payload = "[" + payload + "]";
+    }
+    Splittable toReturn = JsonUtils.safeEval(payload).<JsoSplittable> cast();
+    if (isSimple) {
+      toReturn = toReturn.get(0);
+    }
+    return toReturn;
+  }
+
+  public static Date tryParseDate(String date) {
+    try {
+      return new Date(Long.parseLong(date));
+    } catch (NumberFormatException ignored) {
+    }
+    try {
+      JsDate js = JsDate.create(date);
+      return new Date((long) js.getTime());
+    } catch (JavaScriptException ignored) {
+    }
+    return null;
+  }
+}
diff --git a/user/super/com/google/web/bindery/requestfactory/super/com/google/web/bindery/requestfactory/shared/impl/MessageFactoryHolder.java b/user/super/com/google/web/bindery/requestfactory/super/com/google/web/bindery/requestfactory/shared/impl/MessageFactoryHolder.java
new file mode 100644
index 0000000..1745d79
--- /dev/null
+++ b/user/super/com/google/web/bindery/requestfactory/super/com/google/web/bindery/requestfactory/shared/impl/MessageFactoryHolder.java
@@ -0,0 +1,26 @@
+/*
+ * 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.requestfactory.shared.impl;
+
+import com.google.gwt.core.client.GWT;
+import com.google.web.bindery.requestfactory.shared.messages.MessageFactory;
+
+/**
+ * This a super-source version with a client-only implementation.
+ */
+public interface MessageFactoryHolder {
+  MessageFactory FACTORY = GWT.create(MessageFactory.class);
+}
diff --git a/user/test/com/google/gwt/event/EventSuite.java b/user/test/com/google/gwt/event/EventSuite.java
index 008e88c..b21cc0d 100644
--- a/user/test/com/google/gwt/event/EventSuite.java
+++ b/user/test/com/google/gwt/event/EventSuite.java
@@ -17,9 +17,10 @@
 
 import com.google.gwt.event.dom.client.DomEventTest;
 import com.google.gwt.event.logical.shared.LogicalEventsTest;
+import com.google.gwt.event.shared.EventBusTest;
 import com.google.gwt.event.shared.HandlerManagerTest;
-import com.google.gwt.event.shared.SimpleEventBusTest;
 import com.google.gwt.event.shared.ResettableEventBusTest;
+import com.google.gwt.event.shared.SimpleEventBusTest;
 import com.google.gwt.junit.tools.GWTTestSuite;
 
 import junit.framework.Test;
@@ -32,11 +33,12 @@
     GWTTestSuite suite = new GWTTestSuite(
         "Test for suite for the com.google.gwt.event module.");
 
-    suite.addTestSuite(LogicalEventsTest.class);
     suite.addTestSuite(DomEventTest.class);
+    suite.addTestSuite(EventBusTest.class);
     suite.addTestSuite(HandlerManagerTest.class);
-    suite.addTestSuite(SimpleEventBusTest.class);
+    suite.addTestSuite(LogicalEventsTest.class);
     suite.addTestSuite(ResettableEventBusTest.class);
+    suite.addTestSuite(SimpleEventBusTest.class);
 
     return suite;
   }
diff --git a/user/test/com/google/gwt/event/shared/EventBusTest.java b/user/test/com/google/gwt/event/shared/EventBusTest.java
new file mode 100644
index 0000000..16d03d3
--- /dev/null
+++ b/user/test/com/google/gwt/event/shared/EventBusTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.event.shared;
+
+import com.google.gwt.event.shared.GwtEvent.Type;
+
+import junit.framework.TestCase;
+
+/**
+ * Test that EventBus is api compatible after its retrofit to extend
+ * {@link com.google.web.bindery.event.shared.EventBus}.
+ */
+public class EventBusTest extends TestCase {
+  EventBus bus = new EventBus() {
+
+    @Override
+    public <H extends EventHandler> HandlerRegistration addHandler(Type<H> type, H handler) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public <H extends EventHandler> HandlerRegistration addHandlerToSource(Type<H> type,
+        Object source, H handler) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void fireEvent(GwtEvent<?> event) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void fireEventFromSource(GwtEvent<?> event, Object source) {
+      throw new UnsupportedOperationException();
+    }
+  };
+  
+  public void testOne() {
+    // Nothing to test, really, just make sure it still compiles.
+    assertNotNull(bus);
+  }
+}
diff --git a/user/test/com/google/gwt/event/shared/SimpleEventBusTest.java b/user/test/com/google/gwt/event/shared/SimpleEventBusTest.java
index 3221e83..9b5ae7c 100644
--- a/user/test/com/google/gwt/event/shared/SimpleEventBusTest.java
+++ b/user/test/com/google/gwt/event/shared/SimpleEventBusTest.java
@@ -17,82 +17,21 @@
 package com.google.gwt.event.shared;
 
 import com.google.gwt.core.client.Scheduler.ScheduledCommand;
-import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.DomEvent.Type;
 import com.google.gwt.event.dom.client.MouseDownEvent;
 import com.google.gwt.event.dom.client.MouseDownHandler;
-import com.google.gwt.event.shared.testing.CountingEventBus;
 
 import junit.framework.AssertionFailedError;
 
 import java.util.Set;
 
 /**
- * Eponymous unit test.
+ * Eponymous unit test. Redundant with
+ * {@link com.google.web.bindery.event.shared.SimpleEventBusTest}, here to
+ * ensure legacy compatibility.
  */
 public class SimpleEventBusTest extends HandlerTestBase {
 
-  public void testAddAndRemoveHandlers() {
-    CountingEventBus eventBus = new CountingEventBus(new SimpleEventBus());
-    eventBus.addHandler(MouseDownEvent.getType(), mouse1);
-    eventBus.addHandler(MouseDownEvent.getType(), mouse2);
-    HandlerRegistration reg1 = eventBus.addHandler(MouseDownEvent.getType(),
-        adaptor1);
-    eventBus.fireEvent(new MouseDownEvent() {
-    });
-    assertEquals(3, eventBus.getCount(MouseDownEvent.getType()));
-    assertFired(mouse1, mouse2, adaptor1);
-    eventBus.addHandler(MouseDownEvent.getType(), mouse3);
-    assertEquals(4, eventBus.getCount(MouseDownEvent.getType()));
-
-    eventBus.addHandler(MouseDownEvent.getType(), mouse1);
-    eventBus.addHandler(MouseDownEvent.getType(), mouse2);
-    HandlerRegistration reg2 = eventBus.addHandler(MouseDownEvent.getType(),
-        adaptor1);
-
-    /*
-     * You can indeed add handlers twice, they will only be removed one at a
-     * time though.
-     */
-    assertEquals(7, eventBus.getCount(MouseDownEvent.getType()));
-    eventBus.addHandler(ClickEvent.getType(), adaptor1);
-    eventBus.addHandler(ClickEvent.getType(), click1);
-    eventBus.addHandler(ClickEvent.getType(), click2);
-
-    assertEquals(7, eventBus.getCount(MouseDownEvent.getType()));
-    assertEquals(3, eventBus.getCount(ClickEvent.getType()));
-
-    reset();
-    eventBus.fireEvent(new MouseDownEvent() {
-    });
-    assertFired(mouse1, mouse2, mouse3, adaptor1);
-    assertNotFired(click1, click2);
-
-    // Gets rid of first instance.
-    reg1.removeHandler();
-    eventBus.fireEvent(new MouseDownEvent() {
-    });
-    assertFired(mouse1, mouse2, mouse3, adaptor1);
-    assertNotFired(click1, click2);
-
-    // Gets rid of second instance.
-    reg2.removeHandler();
-    reset();
-    eventBus.fireEvent(new MouseDownEvent() {
-    });
-
-    assertFired(mouse1, mouse2, mouse3);
-    assertNotFired(adaptor1, click1, click2);
-
-    // Checks to see if click events are still working.
-    reset();
-    eventBus.fireEvent(new ClickEvent() {
-    });
-
-    assertNotFired(mouse1, mouse2, mouse3);
-    assertFired(click1, click2, adaptor1);
-  }
-
   public void testConcurrentAdd() {
     final SimpleEventBus eventBus = new SimpleEventBus();
     final MouseDownHandler two = new MouseDownHandler() {
@@ -130,26 +69,6 @@
     }
   }
 
-  public void testConcurrentRemove() {
-    final SimpleEventBus eventBus = new SimpleEventBus();
-
-    ShyHandler h = new ShyHandler();
-
-    eventBus.addHandler(MouseDownEvent.getType(), mouse1);
-    h.r = eventBus.addHandler(MouseDownEvent.getType(), h);
-    eventBus.addHandler(MouseDownEvent.getType(), mouse2);
-    eventBus.addHandler(MouseDownEvent.getType(), mouse3);
-
-    eventBus.fireEvent(new MouseDownEvent() {
-    });
-    assertFired(h, mouse1, mouse2, mouse3);
-    reset();
-    eventBus.fireEvent(new MouseDownEvent() {
-    });
-    assertFired(mouse1, mouse2, mouse3);
-    assertNotFired(h);
-  }
-
   class SourcedHandler implements MouseDownHandler {
     final String expectedSource;
 
@@ -311,109 +230,6 @@
     assertNotFired(two);
   }
 
-  public void testRemoveSelf() {
-    final SimpleEventBus eventBus = new SimpleEventBus();
-
-    MouseDownHandler h = new MouseDownHandler() {
-      HandlerRegistration reg = eventBus.addHandler(MouseDownEvent.getType(),
-          this);
-
-      public void onMouseDown(MouseDownEvent event) {
-        add(this);
-        reg.removeHandler();
-      }
-    };
-
-    eventBus.fireEvent(new MouseDownEvent() {
-    });
-    assertFired(h);
-
-    reset();
-
-    eventBus.fireEvent(new MouseDownEvent() {
-    });
-    assertNotFired(h);
-  }
-
-  public void testNoDoubleRemove() {
-    final SimpleEventBus eventBus = new SimpleEventBus();
-    HandlerRegistration reg = eventBus.addHandler(MouseDownEvent.getType(),
-        mouse1);
-    reg.removeHandler();
-
-    boolean assertsOn = getClass().desiredAssertionStatus();
-
-    if (assertsOn) {
-      try {
-        reg.removeHandler();
-        fail("Should have thrown on remove");
-      } catch (AssertionError e) { /* pass */
-      }
-    } else {
-      reg.removeHandler();
-      // Succeed on no assert failure
-    }
-  }
-
-  public void testConcurrentAddAfterRemoveIsNotClobbered() {
-    final SimpleEventBus eventBus = new SimpleEventBus();
-
-    MouseDownHandler one = new MouseDownHandler() {
-      HandlerRegistration reg = addIt();
-
-      public void onMouseDown(MouseDownEvent event) {
-        reg.removeHandler();
-        addIt();
-        add(this);
-      }
-
-      private HandlerRegistration addIt() {
-        return eventBus.addHandler(MouseDownEvent.getType(), mouse1);
-      }
-    };
-
-    eventBus.addHandler(MouseDownEvent.getType(), one);
-
-    eventBus.fireEvent(new MouseDownEvent() {
-    });
-    assertFired(one);
-
-    reset();
-
-    eventBus.fireEvent(new MouseDownEvent() {
-    });
-    assertFired(one, mouse1);
-  }
-
-  public void testReverseOrder() {
-    @SuppressWarnings("deprecation")
-    final SimpleEventBus eventBus = new SimpleEventBus(true);
-    final MouseDownHandler handler0 = new MouseDownHandler() {
-      public void onMouseDown(MouseDownEvent event) {
-        add(this);
-      }
-    };
-    final MouseDownHandler handler1 = new MouseDownHandler() {
-      public void onMouseDown(MouseDownEvent event) {
-        assertNotFired(handler0);
-        add(this);
-      }
-    };
-    final MouseDownHandler handler2 = new MouseDownHandler() {
-      public void onMouseDown(MouseDownEvent event) {
-        assertNotFired(handler0, handler1);
-        add(this);
-      }
-    };
-    eventBus.addHandler(MouseDownEvent.getType(), handler0);
-    eventBus.addHandler(MouseDownEvent.getType(), handler1);
-    eventBus.addHandler(MouseDownEvent.getType(), handler2);
-
-    reset();
-    eventBus.fireEvent(new MouseDownEvent() {
-    });
-    assertFired(handler0, handler1, handler2);
-  }
 
   static class ThrowingHandler implements MouseDownHandler {
     private final RuntimeException e;
diff --git a/user/test/com/google/web/bindery/autobean/AutoBeanSuite.java b/user/test/com/google/web/bindery/autobean/AutoBeanSuite.java
new file mode 100644
index 0000000..387dc9d
--- /dev/null
+++ b/user/test/com/google/web/bindery/autobean/AutoBeanSuite.java
@@ -0,0 +1,43 @@
+/*
+ * 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;
+
+import com.google.web.bindery.autobean.gwt.client.AutoBeanTest;
+import com.google.web.bindery.autobean.shared.AutoBeanCodexTest;
+import com.google.web.bindery.autobean.shared.SplittableTest;
+import com.google.web.bindery.autobean.vm.AutoBeanCodexJreTest;
+import com.google.web.bindery.autobean.vm.AutoBeanJreTest;
+import com.google.web.bindery.autobean.vm.SplittableJreTest;
+import com.google.gwt.junit.tools.GWTTestSuite;
+
+import junit.framework.Test;
+
+/**
+ * Tests of the Editor framework. These tests focus on core Editor behaviors,
+ * rather than on integration with backing stores.
+ */
+public class AutoBeanSuite {
+  public static Test suite() {
+    GWTTestSuite suite = new GWTTestSuite("Test suite for AutoBean functions");
+    suite.addTestSuite(AutoBeanCodexJreTest.class);
+    suite.addTestSuite(AutoBeanCodexTest.class);
+    suite.addTestSuite(AutoBeanJreTest.class);
+    suite.addTestSuite(AutoBeanTest.class);
+    suite.addTestSuite(SplittableJreTest.class);
+    suite.addTestSuite(SplittableTest.class);
+    return suite;
+  }
+}
diff --git a/user/test/com/google/web/bindery/autobean/gwt/client/AutoBeanTest.java b/user/test/com/google/web/bindery/autobean/gwt/client/AutoBeanTest.java
new file mode 100644
index 0000000..3124590
--- /dev/null
+++ b/user/test/com/google/web/bindery/autobean/gwt/client/AutoBeanTest.java
@@ -0,0 +1,568 @@
+/*
+ * 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;
+
+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.AutoBeanUtils;
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor;
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor.ParameterizationVisitor;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+
+/**
+ * Tests runtime behavior of AutoBean framework.
+ */
+public class AutoBeanTest extends GWTTestCase {
+
+  /**
+   * Static implementation of {@link HasCall}.
+   */
+  public static class CallImpl {
+    public static Object seen;
+
+    public static <T> T __intercept(AutoBean<HasCall> bean, T value) {
+      seen = value;
+      return value;
+    }
+
+    public static int add(AutoBean<HasCall> bean, int a, int b) {
+      assertNotNull(bean);
+      return ((Integer) bean.getTag("offset")) + a + b;
+    }
+  }
+
+  /**
+   * The factory being tested.
+   */
+  @Category(CallImpl.class)
+  protected interface Factory extends AutoBeanFactory {
+    AutoBean<HasBoolean> hasBoolean();
+
+    AutoBean<HasCall> hasCall();
+
+    AutoBean<HasChainedSetters> hasChainedSetters();
+
+    AutoBean<HasList> hasList();
+
+    AutoBean<HasComplexTypes> hasListOfList();
+
+    AutoBean<HasMoreChainedSetters> hasMoreChainedSetters();
+
+    AutoBean<Intf> intf();
+
+    AutoBean<Intf> intf(RealIntf wrapped);
+
+    AutoBean<OtherIntf> otherIntf();
+  }
+
+  interface HasBoolean {
+    boolean getGet();
+
+    boolean hasHas();
+
+    boolean isIs();
+
+    void setGet(boolean value);
+
+    void setHas(boolean value);
+
+    void setIs(boolean value);
+  }
+
+  interface HasCall {
+    int add(int a, int b);
+  }
+
+  interface HasChainedSetters {
+    int getInt();
+
+    String getString();
+
+    HasChainedSetters setInt(int value);
+
+    HasChainedSetters setString(String value);
+  }
+
+  interface HasComplexTypes {
+    List<List<Intf>> getList();
+
+    List<Map<String, Intf>> getListOfMap();
+
+    Map<Map<String, String>, List<List<Intf>>> getMap();
+  }
+
+  interface HasList {
+    List<Intf> getList();
+
+    void setList(List<Intf> list);
+  }
+
+  interface HasMoreChainedSetters extends HasChainedSetters {
+    boolean isBoolean();
+
+    HasMoreChainedSetters setBoolean(boolean value);
+
+    HasMoreChainedSetters setInt(int value);
+  }
+
+  interface Intf {
+    int getInt();
+
+    String getString();
+
+    void setInt(int number);
+
+    void setString(String value);
+  }
+
+  interface OtherIntf {
+    HasBoolean getHasBoolean();
+
+    Intf getIntf();
+
+    UnreferencedInFactory getUnreferenced();
+
+    void setHasBoolean(HasBoolean value);
+
+    void setIntf(Intf intf);
+  }
+
+  static class RealIntf implements Intf {
+    int i;
+    String string;
+
+    @Override
+    public boolean equals(Object o) {
+      return (o instanceof Intf) && (((Intf) o).getInt() == getInt());
+    }
+
+    public int getInt() {
+      return i;
+    }
+
+    public String getString() {
+      return string;
+    }
+
+    @Override
+    public int hashCode() {
+      return i;
+    }
+
+    public void setInt(int number) {
+      this.i = number;
+    }
+
+    public void setString(String value) {
+      this.string = value;
+    }
+
+    public String toString() {
+      return "toString";
+    }
+  }
+
+  interface UnreferencedInFactory {
+  }
+
+  private static class ParameterizationTester extends ParameterizationVisitor {
+    private final StringBuilder sb;
+    private Stack<Boolean> isOpen = new Stack<Boolean>();
+
+    private ParameterizationTester(StringBuilder sb) {
+      this.sb = sb;
+    }
+
+    @Override
+    public void endVisitType(Class<?> type) {
+      if (isOpen.pop()) {
+        sb.append(">");
+      }
+    }
+
+    @Override
+    public boolean visitParameter() {
+      if (isOpen.peek()) {
+        sb.append(",");
+      } else {
+        sb.append("<");
+        isOpen.pop();
+        isOpen.push(true);
+      }
+      return true;
+    }
+
+    @Override
+    public boolean visitType(Class<?> type) {
+      sb.append(type.getName());
+      isOpen.push(false);
+      return true;
+    }
+  }
+
+  protected Factory factory;
+
+  @Override
+  public String getModuleName() {
+    return "com.google.web.bindery.autobean.AutoBean";
+  }
+
+  public void testBooleanIsHasMethods() {
+    HasBoolean b = factory.hasBoolean().as();
+    assertFalse(b.getGet());
+    assertFalse(b.hasHas());
+    assertFalse(b.isIs());
+
+    b.setGet(true);
+    b.setHas(true);
+    b.setIs(true);
+
+    assertTrue(b.getGet());
+    assertTrue(b.hasHas());
+    assertTrue(b.isIs());
+  }
+
+  public void testCategory() {
+    AutoBean<HasCall> call = factory.hasCall();
+    call.setTag("offset", 1);
+    assertEquals(6, call.as().add(2, 3));
+    assertEquals(6, CallImpl.seen);
+  }
+
+  public void testChainedSetters() {
+    AutoBean<HasChainedSetters> bean = factory.hasChainedSetters();
+    bean.as().setInt(42).setString("Blah");
+    assertEquals(42, bean.as().getInt());
+    assertEquals("Blah", bean.as().getString());
+
+    AutoBean<HasMoreChainedSetters> more = factory.hasMoreChainedSetters();
+    more.as().setInt(42).setBoolean(true).setString("Blah");
+    assertEquals(42, more.as().getInt());
+    assertTrue(more.as().isBoolean());
+    assertEquals("Blah", more.as().getString());
+  }
+
+  public void testDiff() {
+    AutoBean<Intf> a1 = factory.intf();
+    AutoBean<Intf> a2 = factory.intf();
+
+    assertTrue(AutoBeanUtils.diff(a1, a2).isEmpty());
+
+    a2.as().setInt(42);
+    Map<String, Object> diff = AutoBeanUtils.diff(a1, a2);
+    assertEquals(1, diff.size());
+    assertEquals(42, diff.get("int"));
+  }
+
+  public void testDiffWithListPropertyAssignment() {
+    AutoBean<HasList> a1 = factory.hasList();
+    AutoBean<HasList> a2 = factory.hasList();
+
+    assertTrue(AutoBeanUtils.diff(a1, a2).isEmpty());
+
+    List<Intf> l1 = new ArrayList<Intf>();
+    a1.as().setList(l1);
+    List<Intf> l2 = new ArrayList<Intf>();
+    a2.as().setList(l2);
+
+    assertTrue(AutoBeanUtils.diff(a1, a2).isEmpty());
+
+    l2.add(factory.intf().as());
+    Map<String, Object> diff = AutoBeanUtils.diff(a1, a2);
+    assertEquals(1, diff.size());
+    assertEquals(l2, diff.get("list"));
+
+    l1.add(l2.get(0));
+    assertTrue(AutoBeanUtils.diff(a1, a2).isEmpty());
+  }
+
+  public void testDynamicMethods() {
+    AutoBean<Intf> intf = factory.create(Intf.class);
+    assertNotNull(intf);
+
+    RealIntf real = new RealIntf();
+    real.i = 42;
+    intf = factory.create(Intf.class, real);
+    assertNotNull(intf);
+    assertEquals(42, intf.as().getInt());
+  }
+
+  public void testEquality() {
+    AutoBean<Intf> a1 = factory.intf();
+    AutoBean<Intf> a2 = factory.intf();
+
+    assertNotSame(a1, a2);
+    assertFalse(a1.equals(a2));
+
+    // Make sure as() is stable
+    assertSame(a1.as(), a1.as());
+    assertEquals(a1.as(), a1.as());
+
+    // When wrapping, use underlying object's equality
+    RealIntf real = new RealIntf();
+    real.i = 42;
+    AutoBean<Intf> w = factory.intf(real);
+    // AutoBean interface never equals wrapped object
+    assertFalse(w.equals(real));
+    // Wrapper interface should delegate hashCode(), equals(), and toString()
+    assertEquals(real.hashCode(), w.as().hashCode());
+    assertEquals(real, w.as());
+    assertEquals(real.toString(), w.as().toString());
+    assertEquals(w.as(), real);
+  }
+
+  public void testFactory() {
+    AutoBean<Intf> auto = factory.intf();
+    assertSame(factory, auto.getFactory());
+  }
+
+  public void testFreezing() {
+    AutoBean<Intf> auto = factory.intf();
+    Intf intf = auto.as();
+    intf.setInt(42);
+    auto.setFrozen(true);
+    try {
+      intf.setInt(55);
+      fail("Should have thrown an exception");
+    } catch (IllegalStateException expected) {
+    }
+
+    assertTrue(auto.isFrozen());
+    assertEquals(42, intf.getInt());
+  }
+
+  public void testNested() {
+    AutoBean<OtherIntf> auto = factory.otherIntf();
+    OtherIntf other = auto.as();
+
+    assertNull(other.getIntf());
+
+    Intf intf = new RealIntf();
+    intf.setString("Hello world!");
+    other.setIntf(intf);
+    Intf retrieved = other.getIntf();
+    assertEquals("Hello world!", retrieved.getString());
+    assertNotNull(AutoBeanUtils.getAutoBean(retrieved));
+  }
+
+  public void testParameterizationVisitor() {
+    AutoBean<HasComplexTypes> auto = factory.hasListOfList();
+    auto.accept(new AutoBeanVisitor() {
+      int count = 0;
+
+      @Override
+      public void endVisit(AutoBean<?> bean, Context ctx) {
+        assertEquals(3, count);
+      }
+
+      @Override
+      public void endVisitCollectionProperty(String propertyName, AutoBean<Collection<?>> value,
+          CollectionPropertyContext ctx) {
+        check(propertyName, ctx);
+      }
+
+      @Override
+      public void endVisitMapProperty(String propertyName, AutoBean<Map<?, ?>> value,
+          MapPropertyContext ctx) {
+        check(propertyName, ctx);
+      }
+
+      private void check(String propertyName, PropertyContext ctx) {
+        count++;
+        StringBuilder sb = new StringBuilder();
+        ctx.accept(new ParameterizationTester(sb));
+
+        if ("list".equals(propertyName)) {
+          // List<List<Intf>>
+          assertEquals(List.class.getName() + "<" + List.class.getName() + "<"
+              + Intf.class.getName() + ">>", sb.toString());
+        } else if ("listOfMap".equals(propertyName)) {
+          // List<Map<String, Intf>>
+          assertEquals(List.class.getName() + "<" + Map.class.getName() + "<"
+              + String.class.getName() + "," + Intf.class.getName() + ">>", sb.toString());
+        } else if ("map".equals(propertyName)) {
+          // Map<Map<String, String>, List<List<Intf>>>
+          assertEquals(Map.class.getName() + "<" + Map.class.getName() + "<"
+              + String.class.getName() + "," + String.class.getName() + ">," + List.class.getName()
+              + "<" + List.class.getName() + "<" + Intf.class.getName() + ">>>", sb.toString());
+        } else {
+          throw new RuntimeException(propertyName);
+        }
+      }
+    });
+  }
+
+  /**
+   * Make sure primitive properties can be returned.
+   */
+  public void testPrimitiveProperty() {
+    AutoBean<Intf> auto = factory.intf();
+    Intf intf = auto.as();
+
+    assertNull(intf.getString());
+    intf.setString("Hello world!");
+    assertEquals("Hello world!", intf.getString());
+
+    assertEquals(0, intf.getInt());
+    intf.setInt(42);
+    assertEquals(42, intf.getInt());
+  }
+
+  public void testTags() {
+    AutoBean<Intf> auto = factory.intf();
+    auto.setTag("test", 42);
+    assertEquals(42, auto.getTag("test"));
+  }
+
+  public void testTraversal() {
+    final AutoBean<OtherIntf> other = factory.otherIntf();
+    final AutoBean<Intf> intf = factory.intf();
+    final AutoBean<HasBoolean> hasBoolean = factory.hasBoolean();
+    other.as().setIntf(intf.as());
+    other.as().setHasBoolean(hasBoolean.as());
+    intf.as().setInt(42);
+    hasBoolean.as().setGet(true);
+    hasBoolean.as().setHas(true);
+    hasBoolean.as().setIs(true);
+
+    class Checker extends AutoBeanVisitor {
+      boolean seenHasBoolean;
+      boolean seenIntf;
+      boolean seenOther;
+
+      @Override
+      public void endVisitReferenceProperty(String propertyName, AutoBean<?> value,
+          PropertyContext ctx) {
+        if ("hasBoolean".equals(propertyName)) {
+          assertSame(hasBoolean, value);
+          assertEquals(HasBoolean.class, ctx.getType());
+        } else if ("intf".equals(propertyName)) {
+          assertSame(intf, value);
+          assertEquals(Intf.class, ctx.getType());
+        } else if ("unreferenced".equals(propertyName)) {
+          assertNull(value);
+          assertEquals(UnreferencedInFactory.class, ctx.getType());
+        } else {
+          fail("Unexpecetd property " + propertyName);
+        }
+      }
+
+      @Override
+      public void endVisitValueProperty(String propertyName, Object value, PropertyContext ctx) {
+        if ("int".equals(propertyName)) {
+          assertEquals(42, value);
+          assertEquals(int.class, ctx.getType());
+        } else if ("string".equals(propertyName)) {
+          assertNull(value);
+          assertEquals(String.class, ctx.getType());
+        } else if ("get".equals(propertyName) || "has".equals(propertyName)
+            || "is".equals(propertyName)) {
+          assertEquals(boolean.class, ctx.getType());
+          assertTrue((Boolean) value);
+        } else {
+          fail("Unknown value property " + propertyName);
+        }
+      }
+
+      @Override
+      public boolean visit(AutoBean<?> bean, Context ctx) {
+        if (bean == hasBoolean) {
+          seenHasBoolean = true;
+        } else if (bean == intf) {
+          seenIntf = true;
+        } else if (bean == other) {
+          seenOther = true;
+        } else {
+          fail("Unknown AutoBean");
+        }
+        return true;
+      }
+
+      void check() {
+        assertTrue(seenHasBoolean);
+        assertTrue(seenIntf);
+        assertTrue(seenOther);
+      }
+    }
+    Checker c = new Checker();
+    other.accept(c);
+    c.check();
+  }
+
+  public void testType() {
+    assertEquals(Intf.class, factory.intf().getType());
+  }
+
+  /**
+   * Ensure that a totally automatic bean can't be unwrapped, since the
+   * generated mapper depends on the AutoBean.
+   */
+  public void testUnwrappingSimpleBean() {
+    AutoBean<Intf> auto = factory.intf();
+    try {
+      auto.unwrap();
+      fail();
+    } catch (IllegalStateException expected) {
+    }
+  }
+
+  public void testWrapped() {
+    RealIntf real = new RealIntf();
+    AutoBean<Intf> auto = factory.intf(real);
+    Intf intf = auto.as();
+
+    assertNotSame(real, intf);
+    assertNull(intf.getString());
+    assertEquals(0, intf.getInt());
+
+    real.string = "blah";
+    assertEquals("blah", intf.getString());
+    real.i = 42;
+    assertEquals(42, intf.getInt());
+
+    intf.setString("bar");
+    assertEquals("bar", real.string);
+
+    intf.setInt(41);
+    assertEquals(41, real.i);
+
+    AutoBean<Intf> rewrapped = factory.intf(real);
+    assertSame(auto, rewrapped);
+
+    // Disconnect the wrapper, make sure it shuts down correctly.
+    Intf unwrapped = auto.unwrap();
+    assertSame(real, unwrapped);
+    assertNull(AutoBeanUtils.getAutoBean(real));
+    try {
+      intf.setInt(42);
+      fail("Should have thrown exception");
+    } catch (IllegalStateException expected) {
+    }
+  }
+
+  @Override
+  protected void gwtSetUp() throws Exception {
+    factory = GWT.create(Factory.class);
+  }
+}
diff --git a/user/test/com/google/web/bindery/autobean/shared/AutoBeanCodexTest.java b/user/test/com/google/web/bindery/autobean/shared/AutoBeanCodexTest.java
new file mode 100644
index 0000000..13f69b3
--- /dev/null
+++ b/user/test/com/google/web/bindery/autobean/shared/AutoBeanCodexTest.java
@@ -0,0 +1,379 @@
+/*
+ * 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.AutoBean.PropertyName;
+import com.google.web.bindery.autobean.shared.impl.EnumMap;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Simple encoding / decoding tests for the AutoBeanCodex.
+ */
+public class AutoBeanCodexTest extends GWTTestCase {
+  /**
+   * Protected so that the JRE-only test can instantiate instances.
+   */
+  protected interface Factory extends AutoBeanFactory {
+    AutoBean<HasSplittable> hasAutoBean();
+
+    AutoBean<HasCycle> hasCycle();
+
+    AutoBean<HasEnum> hasEnum();
+
+    AutoBean<HasList> hasList();
+
+    AutoBean<HasMap> hasMap();
+
+    AutoBean<HasSimple> hasSimple();
+
+    AutoBean<Simple> simple();
+  }
+
+  /*
+   * These enums are used to verify that a List<Enum> or Map<Enum, Enum> pulls
+   * in the necessary metadata.
+   */
+  enum EnumReachableThroughList {
+    FOO_LIST
+  }
+
+  enum EnumReachableThroughMapKey {
+    FOO_KEY
+  }
+
+  enum EnumReachableThroughMapValue {
+    FOO_VALUE
+  }
+
+  /**
+   * Used to test that cycles are detected.
+   */
+  interface HasCycle {
+    List<HasCycle> getCycle();
+
+    void setCycle(List<HasCycle> cycle);
+  }
+
+  interface HasEnum {
+    MyEnum getEnum();
+
+    List<MyEnum> getEnums();
+
+    Map<MyEnum, Integer> getMap();
+
+    List<EnumReachableThroughList> getParameterizedList();
+
+    Map<EnumReachableThroughMapKey, EnumReachableThroughMapValue> getParameterizedMap();
+
+    void setEnum(MyEnum value);
+
+    void setEnums(List<MyEnum> value);
+
+    void setMap(Map<MyEnum, Integer> value);
+  }
+
+  interface HasList {
+    List<Integer> getIntList();
+
+    List<Simple> getList();
+
+    void setIntList(List<Integer> list);
+
+    void setList(List<Simple> list);
+  }
+
+  interface HasMap {
+    Map<Simple, Simple> getComplexMap();
+
+    Map<Map<String, String>, Map<String, String>> getNestedMap();
+
+    Map<String, Simple> getSimpleMap();
+
+    void setComplexMap(Map<Simple, Simple> map);
+
+    void setNestedMap(Map<Map<String, String>, Map<String, String>> map);
+
+    void setSimpleMap(Map<String, Simple> map);
+  }
+
+  interface HasSimple {
+    Simple getSimple();
+
+    void setSimple(Simple s);
+  }
+
+  interface HasSplittable {
+    Splittable getSimple();
+
+    List<Splittable> getSimpleList();
+
+    Map<Splittable, Splittable> getSplittableMap();
+
+    Splittable getString();
+
+    void setSimple(Splittable simple);
+
+    void setSimpleList(List<Splittable> simple);
+
+    void setSplittableMap(Map<Splittable, Splittable> map);
+
+    void setString(Splittable s);
+  }
+
+  enum MyEnum {
+    FOO, BAR,
+    // The eclipse formatter wants to put this annotation inline
+    @PropertyName("quux")
+    BAZ;
+  }
+
+  interface ReachableOnlyFromParameterization extends Simple {
+  }
+
+  interface Simple {
+    int getInt();
+
+    String getString();
+
+    Boolean hasOtherBoolean();
+
+    boolean isBoolean();
+
+    void setBoolean(boolean b);
+
+    void setInt(int i);
+
+    void setOtherBoolean(Boolean b);
+
+    void setString(String s);
+  }
+
+  protected Factory f;
+
+  @Override
+  public String getModuleName() {
+    return "com.google.web.bindery.autobean.AutoBean";
+  }
+
+  public void testCycle() {
+    AutoBean<HasCycle> bean = f.hasCycle();
+    bean.as().setCycle(Arrays.asList(bean.as()));
+    try {
+      checkEncode(bean);
+      fail("Should not have encoded");
+    } catch (UnsupportedOperationException expected) {
+    }
+  }
+
+  public void testEmptyList() {
+    AutoBean<HasList> bean = f.hasList();
+    bean.as().setList(Collections.<Simple> emptyList());
+    AutoBean<HasList> decodedBean = checkEncode(bean);
+    assertNotNull(decodedBean.as().getList());
+    assertTrue(decodedBean.as().getList().isEmpty());
+  }
+
+  public void testEnum() {
+    EnumMap map = (EnumMap) f;
+    assertEquals("BAR", map.getToken(MyEnum.BAR));
+    assertEquals("quux", map.getToken(MyEnum.BAZ));
+    assertEquals(MyEnum.BAR, map.getEnum(MyEnum.class, "BAR"));
+    assertEquals(MyEnum.BAZ, map.getEnum(MyEnum.class, "quux"));
+
+    List<MyEnum> arrayValue = Arrays.asList(MyEnum.FOO, MyEnum.BAR, null, MyEnum.BAZ);
+    Map<MyEnum, Integer> mapValue = new HashMap<MyEnum, Integer>();
+    mapValue.put(MyEnum.FOO, 0);
+    mapValue.put(MyEnum.BAR, 1);
+    mapValue.put(MyEnum.BAZ, 2);
+
+    AutoBean<HasEnum> bean = f.hasEnum();
+    bean.as().setEnum(MyEnum.BAZ);
+    bean.as().setEnums(arrayValue);
+    bean.as().setMap(mapValue);
+
+    Splittable split = AutoBeanCodex.encode(bean);
+    // Make sure the overridden form is always used
+    assertFalse(split.getPayload().contains("BAZ"));
+
+    AutoBean<HasEnum> decoded = checkEncode(bean);
+    assertEquals(MyEnum.BAZ, decoded.as().getEnum());
+    assertEquals(arrayValue, decoded.as().getEnums());
+    assertEquals(mapValue, decoded.as().getMap());
+
+    assertEquals(MyEnum.BAZ, AutoBeanUtils.getAllProperties(bean).get("enum"));
+    bean.as().setEnum(null);
+    assertNull(bean.as().getEnum());
+    assertNull(AutoBeanUtils.getAllProperties(bean).get("enum"));
+    decoded = checkEncode(bean);
+    assertNull(decoded.as().getEnum());
+  }
+
+  /**
+   * Ensures that enum types that are reachable only through a method
+   * parameterization are included in the enum map.
+   */
+  public void testEnumReachableOnlyThroughParameterization() {
+    EnumMap map = (EnumMap) f;
+    assertEquals("FOO_LIST", map.getToken(EnumReachableThroughList.FOO_LIST));
+    assertEquals("FOO_KEY", map.getToken(EnumReachableThroughMapKey.FOO_KEY));
+    assertEquals("FOO_VALUE", map.getToken(EnumReachableThroughMapValue.FOO_VALUE));
+    assertEquals(EnumReachableThroughList.FOO_LIST, map.getEnum(EnumReachableThroughList.class,
+        "FOO_LIST"));
+    assertEquals(EnumReachableThroughMapKey.FOO_KEY, map.getEnum(EnumReachableThroughMapKey.class,
+        "FOO_KEY"));
+    assertEquals(EnumReachableThroughMapValue.FOO_VALUE, map.getEnum(
+        EnumReachableThroughMapValue.class, "FOO_VALUE"));
+  }
+
+  public void testMap() {
+    AutoBean<HasMap> bean = f.hasMap();
+    Map<String, Simple> map = new HashMap<String, Simple>();
+    Map<Simple, Simple> complex = new HashMap<Simple, Simple>();
+    bean.as().setSimpleMap(map);
+    bean.as().setComplexMap(complex);
+
+    for (int i = 0, j = 5; i < j; i++) {
+      Simple s = f.simple().as();
+      s.setInt(i);
+      map.put(String.valueOf(i), s);
+
+      Simple key = f.simple().as();
+      key.setString(String.valueOf(i));
+      complex.put(key, s);
+    }
+
+    AutoBean<HasMap> decoded = checkEncode(bean);
+    map = decoded.as().getSimpleMap();
+    complex = decoded.as().getComplexMap();
+    assertEquals(5, map.size());
+    for (int i = 0, j = 5; i < j; i++) {
+      Simple s = map.get(String.valueOf(i));
+      assertNotNull(s);
+      assertEquals(i, s.getInt());
+    }
+    assertEquals(5, complex.size());
+    for (Map.Entry<Simple, Simple> entry : complex.entrySet()) {
+      assertEquals(entry.getKey().getString(), String.valueOf(entry.getValue().getInt()));
+    }
+  }
+
+  /**
+   * Verify that arbitrarily complicated Maps of Maps work.
+   */
+  public void testNestedMap() {
+    Map<String, String> key = new HashMap<String, String>();
+    key.put("a", "b");
+
+    Map<String, String> value = new HashMap<String, String>();
+    value.put("c", "d");
+
+    Map<Map<String, String>, Map<String, String>> test =
+        new HashMap<Map<String, String>, Map<String, String>>();
+    test.put(key, value);
+
+    AutoBean<HasMap> bean = f.hasMap();
+    bean.as().setNestedMap(test);
+
+    AutoBean<HasMap> decoded = checkEncode(bean);
+    assertEquals(1, decoded.as().getNestedMap().size());
+  }
+
+  public void testNull() {
+    AutoBean<Simple> bean = f.simple();
+    AutoBean<Simple> decodedBean = checkEncode(bean);
+    assertNull(decodedBean.as().getString());
+  }
+
+  public void testSimple() {
+    AutoBean<Simple> bean = f.simple();
+    Simple simple = bean.as();
+    simple.setBoolean(true);
+    simple.setInt(42);
+    simple.setOtherBoolean(true);
+    simple.setString("Hello World!");
+
+    AutoBean<Simple> decodedBean = checkEncode(bean);
+    assertTrue(AutoBeanUtils.diff(bean, decodedBean).isEmpty());
+    assertTrue(decodedBean.as().isBoolean());
+    assertTrue(decodedBean.as().hasOtherBoolean());
+
+    AutoBean<HasSimple> bean2 = f.hasSimple();
+    bean2.as().setSimple(simple);
+
+    AutoBean<HasSimple> decodedBean2 = checkEncode(bean2);
+    assertNotNull(decodedBean2.as().getSimple());
+    assertTrue(AutoBeanUtils.diff(bean, AutoBeanUtils.getAutoBean(decodedBean2.as().getSimple()))
+        .isEmpty());
+
+    AutoBean<HasList> bean3 = f.hasList();
+    bean3.as().setIntList(Arrays.asList(1, 2, 3, null, 4, 5));
+    bean3.as().setList(Arrays.asList(simple));
+
+    AutoBean<HasList> decodedBean3 = checkEncode(bean3);
+    assertNotNull(decodedBean3.as().getIntList());
+    assertEquals(Arrays.asList(1, 2, 3, null, 4, 5), decodedBean3.as().getIntList());
+    assertNotNull(decodedBean3.as().getList());
+    assertEquals(1, decodedBean3.as().getList().size());
+    assertTrue(AutoBeanUtils.diff(bean,
+        AutoBeanUtils.getAutoBean(decodedBean3.as().getList().get(0))).isEmpty());
+  }
+
+  public void testSplittable() {
+    AutoBean<Simple> simple = f.simple();
+    simple.as().setString("Simple");
+    AutoBean<HasSplittable> bean = f.hasAutoBean();
+    bean.as().setSimple(AutoBeanCodex.encode(simple));
+    bean.as().setString(ValueCodex.encode("Hello ['\"] world"));
+    List<Splittable> testList =
+        Arrays.asList(AutoBeanCodex.encode(simple), null, AutoBeanCodex.encode(simple));
+    bean.as().setSimpleList(testList);
+    Map<Splittable, Splittable> testMap =
+        Collections.singletonMap(ValueCodex.encode("12345"), ValueCodex.encode("5678"));
+    bean.as().setSplittableMap(testMap);
+
+    AutoBean<HasSplittable> decoded = checkEncode(bean);
+    Splittable toDecode = decoded.as().getSimple();
+    AutoBean<Simple> decodedSimple = AutoBeanCodex.decode(f, Simple.class, toDecode);
+    assertEquals("Simple", decodedSimple.as().getString());
+    assertEquals("Hello ['\"] world", ValueCodex.decode(String.class, decoded.as().getString()));
+    assertEquals("12345", decoded.as().getSplittableMap().keySet().iterator().next().asString());
+    assertEquals("5678", decoded.as().getSplittableMap().values().iterator().next().asString());
+
+    List<Splittable> list = decoded.as().getSimpleList();
+    assertEquals(3, list.size());
+    assertNull(list.get(1));
+    assertEquals("Simple", AutoBeanCodex.decode(f, Simple.class, list.get(2)).as().getString());
+  }
+
+  @Override
+  protected void gwtSetUp() throws Exception {
+    f = GWT.create(Factory.class);
+  }
+
+  private <T> AutoBean<T> checkEncode(AutoBean<T> bean) {
+    Splittable split = AutoBeanCodex.encode(bean);
+    AutoBean<T> decoded = AutoBeanCodex.decode(f, bean.getType(), split);
+    assertTrue(AutoBeanUtils.deepEquals(bean, decoded));
+    return decoded;
+  }
+}
diff --git a/user/test/com/google/web/bindery/autobean/shared/SplittableTest.java b/user/test/com/google/web/bindery/autobean/shared/SplittableTest.java
new file mode 100644
index 0000000..46a6baf
--- /dev/null
+++ b/user/test/com/google/web/bindery/autobean/shared/SplittableTest.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.shared;
+
+import com.google.web.bindery.autobean.gwt.client.impl.JsoSplittable;
+import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl;
+import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.Coder;
+import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.EncodeState;
+import com.google.web.bindery.autobean.shared.impl.SplittableList;
+import com.google.web.bindery.autobean.shared.impl.SplittableSimpleMap;
+import com.google.web.bindery.autobean.shared.impl.StringQuoter;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Tests for the underlying Splittable implementation. This test class is not
+ * indicative of code that users would write, it's simply doing spot-checks of
+ * functionality that AbstractAutoBean depends on.
+ */
+public class SplittableTest extends GWTTestCase {
+
+  /**
+   * 
+   */
+  private static final EncodeState testState = EncodeState.forTesting();
+
+  @Override
+  public String getModuleName() {
+    return "com.google.web.bindery.autobean.AutoBean";
+  }
+
+  public void testBasicProperties() {
+    Splittable data = StringQuoter.split("{\"a\":true, \"b\":3, \"c\":\"string\", \"d\":null}");
+    assertTrue("isBoolean", data.get("a").isBoolean());
+    assertTrue("asBoolean", data.get("a").asBoolean());
+    assertTrue("isNumber", data.get("b").isNumber());
+    assertEquals(3.0, data.get("b").asNumber());
+    assertTrue("isString", data.get("c").isString());
+    assertEquals("string", data.get("c").asString());
+    assertTrue("isNull", data.isNull("d"));
+    assertNull("should be null", data.get("d"));
+  }
+
+  /**
+   * Ensure that hashcodes don't leak into the payload.
+   */
+  public void testHashCode() {
+    Splittable data = StringQuoter.split("{\"a\":\"b\"}");
+    int hash = data.hashCode();
+    String payload = data.getPayload();
+    assertFalse(payload, payload.contains("$H"));
+    assertFalse(payload, payload.contains(String.valueOf(hash)));
+    assertEquals(hash, data.hashCode());
+  }
+
+  /**
+   * Splittables are implemented by a couple of different concrete types. We'll
+   * use this method to make sure that the correct implementation type is being
+   * used in various circumstances.
+   */
+  public void testImplementationChoice() {
+    Splittable s = StringQuoter.split("[1,false,\"true\"]");
+    if (GWT.isScript()) {
+      assertTrue("s should be JsoSplittable", s instanceof JsoSplittable);
+      assertTrue("s[0] should be JsoSplittable", s.get(0) instanceof JsoSplittable);
+      assertTrue("s[1] should be JsoSplittable", s.get(1) instanceof JsoSplittable);
+      assertTrue("s[2] should be JsoSplittable", s.get(2) instanceof JsoSplittable);
+    } else {
+      // Using the same types in both pure-JRE and DevMode to avoid JSNI
+      // overhead
+      assertTrue("s should be JsonSplittable", s.getClass().getName().endsWith("JsonSplittable"));
+      assertTrue("s[0] should be JsonSplittable", s.get(0).getClass().getName().endsWith(
+          "JsonSplittable"));
+      assertTrue("s[1] should be JsonSplittable", s.get(1).getClass().getName().endsWith(
+          "JsonSplittable"));
+      assertTrue("s[2] should be JsonSplittable", s.get(2).getClass().getName().endsWith(
+          "JsonSplittable"));
+    }
+  }
+
+  public void testIndexed() {
+    Splittable s = StringQuoter.createIndexed();
+    assertTrue(s.isIndexed());
+    assertFalse(s.isKeyed());
+    assertFalse(s.isString());
+    assertEquals(0, s.size());
+
+    string("foo").assign(s, 0);
+    string("bar").assign(s, 1);
+    string("baz").assign(s, 2);
+
+    assertEquals(3, s.size());
+    assertEquals("[\"foo\",\"bar\",\"baz\"]", s.getPayload());
+
+    string("quux").assign(s, 1);
+    assertEquals("[\"foo\",\"quux\",\"baz\"]", s.getPayload());
+
+    Splittable s2 = s.deepCopy();
+    assertNotSame(s, s2);
+    assertEquals(s.size(), s2.size());
+    for (int i = 0, j = s.size(); i < j; i++) {
+      assertEquals(s.get(i).asString(), s2.get(i).asString());
+    }
+
+    s.setSize(2);
+    assertEquals(2, s.size());
+    assertEquals("[\"foo\",\"quux\"]", s.getPayload());
+
+    // Make sure reified values aren't in the payload
+    Object o = new Object();
+    s.setReified("reified", o);
+    assertFalse(s.getPayload().contains("reified"));
+    assertFalse(s.getPayload().contains("__s"));
+    assertSame(o, s.getReified("reified"));
+  }
+
+  public void testKeyed() {
+    Splittable s = StringQuoter.createSplittable();
+    assertFalse(s.isIndexed());
+    assertTrue(s.isKeyed());
+    assertFalse(s.isString());
+    assertTrue(s.getPropertyKeys().isEmpty());
+
+    string("bar").assign(s, "foo");
+    string("quux").assign(s, "baz");
+
+    // Actual iteration order is undefined
+    assertEquals(new HashSet<String>(Arrays.asList("foo", "baz")), new HashSet<String>(s
+        .getPropertyKeys()));
+
+    assertFalse(s.isNull("foo"));
+    assertTrue(s.isNull("bar"));
+
+    assertEquals("bar", s.get("foo").asString());
+    assertEquals("quux", s.get("baz").asString());
+
+    String payload = s.getPayload();
+    assertTrue(payload.startsWith("{"));
+    assertTrue(payload.endsWith("}"));
+    assertTrue(payload.contains("\"foo\":\"bar\""));
+    assertTrue(payload.contains("\"baz\":\"quux\""));
+
+    Splittable s2 = s.deepCopy();
+    assertNotSame(s, s2);
+    assertEquals("bar", s2.get("foo").asString());
+    assertEquals("quux", s2.get("baz").asString());
+
+    // Make sure reified values aren't in the payload
+    Object o = new Object();
+    s.setReified("reified", o);
+    assertFalse("Should not see reified in " + s.getPayload(), s.getPayload().contains("reified"));
+    assertFalse("Should not see __s in " + s.getPayload(), s.getPayload().contains("__s"));
+    assertSame(o, s.getReified("reified"));
+  }
+
+  public void testNested() {
+    Splittable s = StringQuoter.split("{\"a\":{\"foo\":\"bar\"}}");
+    Splittable a = s.get("a");
+    assertNotNull(a);
+    assertEquals("bar", a.get("foo").asString());
+    assertSame(a, s.get("a"));
+    assertEquals(a, s.get("a"));
+  }
+
+  /**
+   * Tests attributes of the {@link Splittable#NULL} field.
+   */
+  public void testNull() {
+    Splittable n = Splittable.NULL;
+    if (GWT.isScript()) {
+      assertNull(n);
+    } else {
+      assertNotNull(n);
+    }
+    assertFalse("boolean", n.isBoolean());
+    assertFalse("indexed", n.isIndexed());
+    assertFalse("keyed", n.isKeyed());
+    assertFalse("string", n.isString());
+    assertEquals("null", n.getPayload());
+  }
+
+  /**
+   * Extra tests in here due to potential to confuse {@code false} and
+   * {@code null} values.
+   */
+  public void testSplittableListBoolean() {
+    Coder boolCoder = AutoBeanCodexImpl.valueCoder(Boolean.class);
+    Splittable s = StringQuoter.createIndexed();
+    bool(false).assign(s, 0);
+    assertFalse("0 should not be null", s.isNull(0));
+    assertTrue("s[0] should be a boolean", s.get(0).isBoolean());
+    assertFalse("s[0] should be false", s.get(0).asBoolean());
+    assertNotNull("Null decode", ValueCodex.decode(Boolean.class, s.get(0)));
+    Object decodedBoolean = boolCoder.decode(testState, s.get(0));
+    assertNotNull("decode should not return null", decodedBoolean);
+    assertFalse("decoded value should be false", (Boolean) decodedBoolean);
+
+    bool(true).assign(s, 1);
+    assertTrue("s[1] should be a boolean", s.get(1).isBoolean());
+    assertTrue("s[1] should be true", s.get(1).asBoolean());
+    assertTrue("boolCoder 1", (Boolean) boolCoder.decode(testState, s.get(1)));
+
+    Splittable.NULL.assign(s, 2);
+    assertTrue("3 should be null", s.isNull(3));
+    assertEquals("payload", "[false,true,null]", s.getPayload());
+    List<Boolean> boolList = new SplittableList<Boolean>(s, boolCoder, testState);
+    assertEquals("boolList", Arrays.<Boolean> asList(false, true, null), boolList);
+  }
+
+  /**
+   * Extra tests in here due to potential to confuse 0 and {@code null} values.
+   */
+  public void testSplittableListNumbers() {
+    Coder intCoder = AutoBeanCodexImpl.valueCoder(Integer.class);
+    Coder doubleCoder = AutoBeanCodexImpl.valueCoder(Double.class);
+    Splittable s = StringQuoter.createIndexed();
+    number(0).assign(s, 0);
+    assertFalse("0 should not be null", s.isNull(0));
+    assertTrue("s[0] should be a number", s.get(0).isNumber());
+    assertNotNull("Null decode", ValueCodex.decode(Integer.class, s.get(0)));
+    Object decodedInt = intCoder.decode(testState, s.get(0));
+    assertNotNull("decode should not return null", decodedInt);
+    assertEquals("intCoder 0", Integer.valueOf(0), decodedInt);
+    assertEquals("doubleCoder 0", Double.valueOf(0), doubleCoder.decode(testState, s.get(0)));
+
+    number(3.141592).assign(s, 1);
+    assertEquals("intCoder 1", Integer.valueOf(3), intCoder.decode(testState, s.get(1)));
+    assertEquals("doubleCoder 1", Double.valueOf(3.141592), doubleCoder.decode(testState, s.get(1)));
+
+    number(42).assign(s, 2);
+    Splittable.NULL.assign(s, 3);
+    assertTrue("3 should be null", s.isNull(3));
+    assertEquals("payload", "[0,3.141592,42,null]", s.getPayload());
+    List<Double> doubleList = new SplittableList<Double>(s, doubleCoder, testState);
+    assertEquals(Double.valueOf(0), doubleList.get(0));
+    assertEquals("doubleList", Arrays.<Double> asList(0d, 3.141592, 42d, null), doubleList);
+
+    // Don't share backing data between lists
+    s = StringQuoter.split("[0,3.141592,42,null]");
+    List<Integer> intList = new SplittableList<Integer>(s, intCoder, testState);
+    assertEquals("intList", Arrays.<Integer> asList(0, 3, 42, null), intList);
+  }
+
+  public void testSplittableListString() {
+    Splittable data = StringQuoter.split("[\"Hello\",\"World\"]");
+    SplittableList<String> list =
+        new SplittableList<String>(data, AutoBeanCodexImpl.valueCoder(String.class), testState);
+    assertEquals(2, list.size());
+    assertEquals(Arrays.asList("Hello", "World"), list);
+    list.set(0, "Goodbye");
+    assertEquals(Arrays.asList("Goodbye", "World"), list);
+    assertEquals("[\"Goodbye\",\"World\"]", data.getPayload());
+    list.remove(0);
+    assertEquals(Arrays.asList("World"), list);
+    assertEquals("[\"World\"]", data.getPayload());
+    list.add("Wide");
+    list.add("Web");
+    assertEquals(Arrays.asList("World", "Wide", "Web"), list);
+    assertEquals("[\"World\",\"Wide\",\"Web\"]", data.getPayload());
+  }
+
+  public void testSplittableMapStringString() {
+    Splittable data = StringQuoter.split("{\"foo\":\"bar\",\"baz\":\"quux\",\"isNull\":null}");
+    assertTrue("isNull should be null", data.isNull("isNull"));
+    assertFalse("isNull should not be undefined", data.isUndefined("isNull"));
+    Map<String, String> map =
+        new SplittableSimpleMap<String, String>(data, AutoBeanCodexImpl.valueCoder(String.class),
+            AutoBeanCodexImpl.valueCoder(String.class), testState);
+    assertEquals(3, map.size());
+    assertEquals("bar", map.get("foo"));
+    assertEquals("quux", map.get("baz"));
+    assertTrue("Map should have isNull key", map.containsKey("isNull"));
+    assertNull(map.get("isNull"));
+    assertFalse("Map should not have unknown key", map.containsKey("unknown"));
+
+    map.put("bar", "foo2");
+    assertEquals("foo2", map.get("bar"));
+    String payload = data.getPayload();
+    assertTrue(payload.contains("\"bar\":\"foo2\""));
+    assertTrue(payload.contains("\"isNull\":null"));
+  }
+
+  public void testString() {
+    Splittable s = string("Hello '\" World!");
+    assertFalse(s.isIndexed());
+    assertFalse(s.isKeyed());
+    assertTrue(s.isString());
+    assertEquals("Hello '\" World!", s.asString());
+    assertEquals("\"Hello '\\\" World!\"", s.getPayload());
+  }
+
+  public void testStringEmpty() {
+    Splittable s = string("");
+    assertFalse(s.isIndexed());
+    assertFalse(s.isKeyed());
+    assertTrue(s.isString());
+    assertEquals("", s.asString());
+    assertEquals("\"\"", s.getPayload());
+  }
+
+  private Splittable bool(boolean value) {
+    return StringQuoter.split(String.valueOf(value));
+  }
+
+  private Splittable number(double number) {
+    return StringQuoter.split(String.valueOf(number));
+  }
+
+  private Splittable string(String value) {
+    return StringQuoter.split(StringQuoter.quote(value));
+  }
+}
diff --git a/user/test/com/google/web/bindery/autobean/vm/AutoBeanCodexJreTest.java b/user/test/com/google/web/bindery/autobean/vm/AutoBeanCodexJreTest.java
new file mode 100644
index 0000000..46adb69
--- /dev/null
+++ b/user/test/com/google/web/bindery/autobean/vm/AutoBeanCodexJreTest.java
@@ -0,0 +1,35 @@
+/*
+ * 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.AutoBeanCodexTest;
+
+/**
+ * Runs AutoBeanCodexTest in pure-JRE mode.
+ */
+public class AutoBeanCodexJreTest extends AutoBeanCodexTest {
+
+  @Override
+  public String getModuleName() {
+    return null;
+  }
+
+  @Override
+  protected void gwtSetUp() throws Exception {
+    f = AutoBeanFactorySource.create(Factory.class);
+  }
+
+}
diff --git a/user/test/com/google/web/bindery/autobean/vm/AutoBeanJreTest.java b/user/test/com/google/web/bindery/autobean/vm/AutoBeanJreTest.java
new file mode 100644
index 0000000..e56be29
--- /dev/null
+++ b/user/test/com/google/web/bindery/autobean/vm/AutoBeanJreTest.java
@@ -0,0 +1,34 @@
+/*
+ * 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.gwt.client.AutoBeanTest;
+
+/**
+ * Runs the AutoBeanTests against the JRE implementation.
+ */
+public class AutoBeanJreTest extends AutoBeanTest {
+
+  @Override
+  public String getModuleName() {
+    return null;
+  }
+
+  @Override
+  protected void gwtSetUp() throws Exception {
+    factory = AutoBeanFactorySource.create(Factory.class);
+  }
+}
diff --git a/user/test/com/google/web/bindery/autobean/vm/SplittableJreTest.java b/user/test/com/google/web/bindery/autobean/vm/SplittableJreTest.java
new file mode 100644
index 0000000..ddb9126
--- /dev/null
+++ b/user/test/com/google/web/bindery/autobean/vm/SplittableJreTest.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.autobean.vm;
+
+import com.google.web.bindery.autobean.shared.SplittableTest;
+
+/**
+ * A JRE-only version of SplittableTest.
+ */
+public class SplittableJreTest extends SplittableTest {
+  @Override
+  public String getModuleName() {
+    return null;
+  }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/gwt/RequestFactoryExceptionHandlerTest.gwt.xml b/user/test/com/google/web/bindery/requestfactory/gwt/RequestFactoryExceptionHandlerTest.gwt.xml
new file mode 100644
index 0000000..32645c8
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/gwt/RequestFactoryExceptionHandlerTest.gwt.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 2.0.1//EN" "http://google-web-toolkit.googlecode.com/svn/tags/2.0.1/distro-source/core/src/gwt-module.dtd">
+<!--
+  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.
+-->
+<module>
+  <inherits name='com.google.web.bindery.requestfactory.RequestFactory'/>
+  <!-- use this old inefficient JSON library just for the time being, replace soon -->
+  <inherits name='com.google.gwt.junit.JUnit'/>
+  <inherits name='com.google.gwt.json.JSON'/>
+  <servlet path='/gwtRequest'
+    class='com.google.web.bindery.requestfactory.server.RequestFactoryExceptionHandlerServlet' />
+</module>
diff --git a/user/test/com/google/web/bindery/requestfactory/gwt/RequestFactoryGwtJreSuite.java b/user/test/com/google/web/bindery/requestfactory/gwt/RequestFactoryGwtJreSuite.java
new file mode 100644
index 0000000..c8c61d1
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/gwt/RequestFactoryGwtJreSuite.java
@@ -0,0 +1,66 @@
+/*
+ * 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.requestfactory.gwt;
+
+import com.google.web.bindery.requestfactory.gwt.rebind.model.RequestFactoryModelTest;
+import com.google.web.bindery.requestfactory.server.BoxesAndPrimitivesJreTest;
+import com.google.web.bindery.requestfactory.server.ComplexKeysJreTest;
+import com.google.web.bindery.requestfactory.server.FindServiceJreTest;
+import com.google.web.bindery.requestfactory.server.LocatorJreTest;
+import com.google.web.bindery.requestfactory.server.RequestFactoryExceptionPropagationJreTest;
+import com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidatorTest;
+import com.google.web.bindery.requestfactory.server.RequestFactoryJreTest;
+import com.google.web.bindery.requestfactory.server.RequestFactoryUnicodeEscapingJreTest;
+import com.google.web.bindery.requestfactory.server.ServiceInheritanceJreTest;
+import com.google.web.bindery.requestfactory.shared.impl.SimpleEntityProxyIdTest;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Suite of RequestFactory tests that require the JRE.
+ * <p>
+ * Note: these tests require gwt-user src on the classpath. To run in
+ * Eclipse, use Google Plugin for Eclipse to run as a GWT JUnit test
+ * or edit the Eclipse launch config and add the src folder to the classpath
+ * (click Classpath tab, User entries, Advanced..., Add folders)
+ */
+public class RequestFactoryGwtJreSuite {
+  public static Test suite() {
+    TestSuite suite = new TestSuite(
+        "requestfactory package tests that require the JRE");
+    suite.addTestSuite(BoxesAndPrimitivesJreTest.class);
+    suite.addTestSuite(ComplexKeysJreTest.class);
+    suite.addTestSuite(FindServiceJreTest.class);
+    suite.addTestSuite(LocatorJreTest.class);
+    suite.addTestSuite(RequestFactoryExceptionPropagationJreTest.class);
+    suite.addTestSuite(RequestFactoryInterfaceValidatorTest.class);
+    suite.addTestSuite(RequestFactoryJreTest.class);
+    suite.addTestSuite(RequestFactoryModelTest.class);
+    suite.addTestSuite(RequestFactoryUnicodeEscapingJreTest.class);
+    suite.addTestSuite(ServiceInheritanceJreTest.class);
+    suite.addTestSuite(SimpleEntityProxyIdTest.class);
+
+    return suite;
+  }
+
+  /**
+   * Used to test the client package.
+   */
+  public static void main(String[] args) {
+    junit.textui.TestRunner.run(suite());
+  }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/gwt/RequestFactorySuite.gwt.xml b/user/test/com/google/web/bindery/requestfactory/gwt/RequestFactorySuite.gwt.xml
new file mode 100644
index 0000000..51a99f4
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/gwt/RequestFactorySuite.gwt.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 2.0.1//EN" "http://google-web-toolkit.googlecode.com/svn/tags/2.0.1/distro-source/core/src/gwt-module.dtd">
+<!--
+  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.
+-->
+<module>
+  <inherits name='com.google.web.bindery.requestfactory.RequestFactory'/>
+  <!-- use this old inefficient JSON library just for the time being, replace soon -->
+  <inherits name='com.google.gwt.junit.JUnit'/>
+  <inherits name='com.google.gwt.json.JSON'/>
+  <servlet path='/gwtRequest'
+    class='com.google.web.bindery.requestfactory.server.RequestFactoryServlet' />
+</module>
diff --git a/user/test/com/google/web/bindery/requestfactory/gwt/RequestFactorySuite.java b/user/test/com/google/web/bindery/requestfactory/gwt/RequestFactorySuite.java
new file mode 100644
index 0000000..5794598
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/gwt/RequestFactorySuite.java
@@ -0,0 +1,53 @@
+/*
+ * 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.requestfactory.gwt;
+
+import com.google.gwt.junit.tools.GWTTestSuite;
+import com.google.web.bindery.requestfactory.gwt.client.FindServiceTest;
+import com.google.web.bindery.requestfactory.gwt.client.RequestFactoryExceptionHandlerTest;
+import com.google.web.bindery.requestfactory.gwt.client.RequestFactoryExceptionPropagationTest;
+import com.google.web.bindery.requestfactory.gwt.client.RequestFactoryPolymorphicTest;
+import com.google.web.bindery.requestfactory.gwt.client.RequestFactoryTest;
+import com.google.web.bindery.requestfactory.gwt.client.RequestFactoryUnicodeEscapingTest;
+import com.google.web.bindery.requestfactory.gwt.client.ui.EditorTest;
+import com.google.web.bindery.requestfactory.shared.BoxesAndPrimitivesTest;
+import com.google.web.bindery.requestfactory.shared.ComplexKeysTest;
+import com.google.web.bindery.requestfactory.shared.LocatorTest;
+import com.google.web.bindery.requestfactory.shared.ServiceInheritanceTest;
+
+import junit.framework.Test;
+
+/**
+ * Tests of RequestFactory that require GWT.
+ */
+public class RequestFactorySuite {
+  public static Test suite() {
+    GWTTestSuite suite = new GWTTestSuite(
+        "Test suite for requestfactory gwt code.");
+    suite.addTestSuite(BoxesAndPrimitivesTest.class);
+    suite.addTestSuite(ComplexKeysTest.class);
+    suite.addTestSuite(EditorTest.class);
+    suite.addTestSuite(FindServiceTest.class);
+    suite.addTestSuite(LocatorTest.class);
+    suite.addTestSuite(RequestFactoryTest.class);
+    suite.addTestSuite(RequestFactoryExceptionHandlerTest.class);
+    suite.addTestSuite(RequestFactoryExceptionPropagationTest.class);
+    suite.addTestSuite(RequestFactoryPolymorphicTest.class);
+    suite.addTestSuite(RequestFactoryUnicodeEscapingTest.class);
+    suite.addTestSuite(ServiceInheritanceTest.class);
+    return suite;
+  }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/gwt/client/FindServiceTest.java b/user/test/com/google/web/bindery/requestfactory/gwt/client/FindServiceTest.java
new file mode 100644
index 0000000..ca82e36
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/gwt/client/FindServiceTest.java
@@ -0,0 +1,245 @@
+/*
+ * 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.requestfactory.gwt.client;
+
+import com.google.web.bindery.event.shared.HandlerRegistration;
+import com.google.web.bindery.requestfactory.shared.EntityProxyChange;
+import com.google.web.bindery.requestfactory.shared.EntityProxyId;
+import com.google.web.bindery.requestfactory.shared.Receiver;
+import com.google.web.bindery.requestfactory.shared.Request;
+import com.google.web.bindery.requestfactory.shared.SimpleBarProxy;
+import com.google.web.bindery.requestfactory.shared.SimpleBarRequest;
+import com.google.web.bindery.requestfactory.shared.SimpleFooProxy;
+import com.google.web.bindery.requestfactory.shared.SimpleFooRequest;
+import com.google.web.bindery.requestfactory.shared.SimpleRequestFactory;
+
+/**
+ * Tests for {@link com.google.web.bindery.requestfactory.shared.RequestFactory}.
+ */
+public class FindServiceTest extends RequestFactoryTestBase {
+  /*
+   * DO NOT USE finishTest(). Instead, call finishTestAndReset();
+   */
+
+  private static final int TEST_DELAY = 5000;
+
+  @Override
+  public String getModuleName() {
+    return "com.google.web.bindery.requestfactory.gwt.RequestFactorySuite";
+  }
+
+  public void testFetchDeletedEntity() {
+    delayTestFinish(TEST_DELAY);
+    SimpleBarRequest context = req.simpleBarRequest();
+    SimpleBarProxy willDelete = context.create(SimpleBarProxy.class);
+    context.persistAndReturnSelf().using(willDelete).fire(
+        new Receiver<SimpleBarProxy>() {
+          @Override
+          public void onSuccess(SimpleBarProxy response) {
+            final EntityProxyId<SimpleBarProxy> id = response.stableId();
+
+            // Make the entity behave as though it's been deleted
+            SimpleBarRequest context = req.simpleBarRequest();
+            Request<Void> persist = context.persist().using(response);
+            context.edit(response).setFindFails(true);
+            persist.fire(new Receiver<Void>() {
+
+              @Override
+              public void onSuccess(Void response) {
+                // Now try fetching the deleted instance
+                req.find(id).fire(new Receiver<SimpleBarProxy>() {
+                  @Override
+                  public void onSuccess(SimpleBarProxy response) {
+                    assertNull(response);
+                    finishTestAndReset();
+                  }
+                });
+              }
+            });
+          }
+        });
+  }
+
+  public void testFetchEntityWithLongId() {
+    final boolean relationsAbsent = false;
+    delayTestFinish(TEST_DELAY);
+    req.simpleFooRequest().findSimpleFooById(999L).fire(
+        new Receiver<SimpleFooProxy>() {
+          @Override
+          public void onSuccess(SimpleFooProxy response) {
+            checkReturnedProxy(response, relationsAbsent);
+
+            final EntityProxyId<SimpleFooProxy> stableId = response.stableId();
+            req.find(stableId).fire(new Receiver<SimpleFooProxy>() {
+
+              @Override
+              public void onSuccess(SimpleFooProxy returnedProxy) {
+                assertEquals(stableId, returnedProxy.stableId());
+                checkReturnedProxy(returnedProxy, relationsAbsent);
+                finishTestAndReset();
+              }
+            });
+          }
+        });
+  }
+
+  public void testFetchEntityWithRelation() {
+    final boolean relationsPresent = true;
+    delayTestFinish(TEST_DELAY);
+    req.simpleFooRequest().findSimpleFooById(999L).with("barField").fire(
+        new Receiver<SimpleFooProxy>() {
+          @Override
+          public void onSuccess(SimpleFooProxy response) {
+            checkReturnedProxy(response, relationsPresent);
+
+            final EntityProxyId<SimpleFooProxy> stableId = response.stableId();
+            req.find(stableId).with("barField").fire(
+                new Receiver<SimpleFooProxy>() {
+
+                  @Override
+                  public void onSuccess(SimpleFooProxy returnedProxy) {
+                    assertEquals(stableId, returnedProxy.stableId());
+                    checkReturnedProxy(returnedProxy, relationsPresent);
+                    finishTestAndReset();
+                  }
+                });
+          }
+        });
+  }
+
+  public void testFetchEntityWithStringId() {
+    delayTestFinish(TEST_DELAY);
+    req.simpleBarRequest().findSimpleBarById("999L").fire(
+        new Receiver<SimpleBarProxy>() {
+          @Override
+          public void onSuccess(SimpleBarProxy response) {
+            final EntityProxyId<SimpleBarProxy> stableId = response.stableId();
+            req.find(stableId).fire(new Receiver<SimpleBarProxy>() {
+
+              @Override
+              public void onSuccess(SimpleBarProxy returnedProxy) {
+                assertEquals(stableId, returnedProxy.stableId());
+                finishTestAndReset();
+              }
+            });
+          }
+        });
+  }
+
+  public void testFetchsAfterCreateDontUpdate() {
+    final int[] count = {0};
+    final HandlerRegistration registration = EntityProxyChange.registerForProxyType(
+        req.getEventBus(), SimpleFooProxy.class,
+        new EntityProxyChange.Handler<SimpleFooProxy>() {
+          public void onProxyChange(EntityProxyChange<SimpleFooProxy> event) {
+            count[0]++;
+          }
+        });
+    delayTestFinish(TEST_DELAY);
+    SimpleFooRequest context = req.simpleFooRequest();
+    SimpleFooProxy proxy = context.create(SimpleFooProxy.class);
+    context.persistAndReturnSelf().using(proxy).fire(
+        new Receiver<SimpleFooProxy>() {
+          @Override
+          public void onSuccess(SimpleFooProxy response) {
+            // Persist and Update events
+            assertEquals(2, count[0]);
+            req.find(response.stableId()).fire(new Receiver<SimpleFooProxy>() {
+              @Override
+              public void onSuccess(SimpleFooProxy response) {
+                // No new events
+                assertEquals(2, count[0]);
+                registration.removeHandler();
+                finishTestAndReset();
+              }
+            });
+          }
+        });
+  }
+
+  /**
+   * Demonstrates behavior when fetching an unpersisted id. The setup is
+   * analagous to saving a future id into a cookie and then trying to fetch it
+   * later.
+   */
+  public void testFetchUnpersistedFutureId() {
+    String historyToken;
+
+    // Here's the factory from the "previous invocation" of the client
+    {
+      SimpleRequestFactory oldFactory = createFactory();
+      EntityProxyId<SimpleBarProxy> id = oldFactory.simpleBarRequest().create(
+          SimpleBarProxy.class).stableId();
+      historyToken = oldFactory.getHistoryToken(id);
+    }
+
+    EntityProxyId<SimpleBarProxy> id = req.getProxyId(historyToken);
+    assertNotNull(id);
+    try {
+      req.find(id);
+      fail();
+    } catch (IllegalArgumentException expected) {
+    }
+  }
+
+  /**
+   * A second fetch for an unchanged object should not result in any update
+   * event.
+   */
+  public void testMultipleFetchesDontUpdate() {
+    final int[] count = {0};
+    final HandlerRegistration registration = EntityProxyChange.registerForProxyType(
+        req.getEventBus(), SimpleFooProxy.class,
+        new EntityProxyChange.Handler<SimpleFooProxy>() {
+          public void onProxyChange(EntityProxyChange<SimpleFooProxy> event) {
+            count[0]++;
+          }
+        });
+    delayTestFinish(TEST_DELAY);
+    req.simpleFooRequest().findSimpleFooById(999L).fire(
+        new Receiver<SimpleFooProxy>() {
+          @Override
+          public void onSuccess(SimpleFooProxy response) {
+            assertEquals(1, count[0]);
+
+            final EntityProxyId<SimpleFooProxy> stableId = response.stableId();
+            req.find(stableId).fire(new Receiver<SimpleFooProxy>() {
+
+              @Override
+              public void onSuccess(SimpleFooProxy returnedProxy) {
+                assertEquals(1, count[0]);
+                registration.removeHandler();
+                finishTestAndReset();
+              }
+            });
+          }
+        });
+  }
+
+  private void checkReturnedProxy(SimpleFooProxy response,
+      boolean checkForRelations) {
+    assertEquals(42, (int) response.getIntId());
+    assertEquals("GWT", response.getUserName());
+    assertEquals(8L, (long) response.getLongField());
+    assertEquals(com.google.web.bindery.requestfactory.shared.SimpleEnum.FOO,
+        response.getEnumField());
+    if (checkForRelations) {
+      assertNotNull(response.getBarField());
+    } else {
+      assertEquals(null, response.getBarField());
+    }
+  }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryExceptionHandlerTest.java b/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryExceptionHandlerTest.java
new file mode 100644
index 0000000..16a7b96
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryExceptionHandlerTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.requestfactory.gwt.client;
+
+import com.google.web.bindery.requestfactory.shared.Request;
+import com.google.web.bindery.requestfactory.shared.SimpleFooProxy;
+import com.google.web.bindery.requestfactory.shared.SimpleFooRequest;
+
+/**
+ * Tests that {@code RequestFactoryServlet} when using a custom
+ * ExceptionHandler.
+ */
+public class RequestFactoryExceptionHandlerTest extends RequestFactoryTest {
+
+  private static final int DELAY_TEST_FINISH = 5000;
+
+  @Override
+  public String getModuleName() {
+    return "com.google.web.bindery.requestfactory.gwt.RequestFactoryExceptionHandlerTest";
+  }
+
+  @Override
+  public void testServerFailureCheckedException() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    SimpleFooRequest context = req.simpleFooRequest();
+    SimpleFooProxy rayFoo = context.create(SimpleFooProxy.class);
+    final Request<SimpleFooProxy> persistRay = context.persistAndReturnSelf().using(
+        rayFoo);
+    rayFoo = context.edit(rayFoo);
+    // 42 is the crash causing magic number
+    rayFoo.setPleaseCrash(42);
+    persistRay.fire(new FooReciever(rayFoo, persistRay,
+        "java.lang.UnsupportedOperationException"));
+  }
+
+  @Override
+  public void testServerFailureRuntimeException() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    SimpleFooRequest context = req.simpleFooRequest();
+    SimpleFooProxy rayFoo = context.create(SimpleFooProxy.class);
+    final Request<SimpleFooProxy> persistRay = context.persistAndReturnSelf().using(
+        rayFoo);
+    rayFoo = context.edit(rayFoo);
+    // 43 is the crash causing magic number
+    rayFoo.setPleaseCrash(43);
+    persistRay.fire(new FooReciever(rayFoo, persistRay, "java.lang.Exception"));
+  }
+
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryExceptionPropagationTest.java b/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryExceptionPropagationTest.java
new file mode 100644
index 0000000..48482c7
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryExceptionPropagationTest.java
@@ -0,0 +1,308 @@
+/*
+ * 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.requestfactory.gwt.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.web.bindery.event.shared.UmbrellaException;
+import com.google.web.bindery.requestfactory.shared.Receiver;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.ServerFailure;
+import com.google.web.bindery.requestfactory.shared.SimpleFooProxy;
+import com.google.web.bindery.requestfactory.shared.SimpleFooRequest;
+import com.google.web.bindery.requestfactory.shared.Violation;
+
+import java.util.Set;
+
+/**
+ * Tests that an exception thrown by a {@link Receiver} does not prevent other
+ * {@link Receiver}s from being called.
+ */
+public class RequestFactoryExceptionPropagationTest extends
+    RequestFactoryTestBase {
+  /*
+   * DO NOT USE finishTest(). Instead, call finishTestAndReset();
+   */
+
+  private class CountingReceiver extends Receiver<Object> {
+    int failureCallCount;
+    int successCallCount;
+    int violationCallCount;
+
+    public void assertCounts(int expectedFailureCallCount,
+        int expectedSuccessCallCount, int expectedViolationCallCount) {
+      assertEquals(expectedFailureCallCount, failureCallCount);
+      assertEquals(expectedSuccessCallCount, successCallCount);
+      assertEquals(expectedViolationCallCount, violationCallCount);
+    }
+
+    @Override
+    public void onFailure(ServerFailure error) {
+      failureCallCount++;
+    }
+
+    @Override
+    public void onSuccess(Object response) {
+      successCallCount++;
+    }
+
+    @Override
+    public void onViolation(Set<Violation> errors) {
+      violationCallCount++;
+    }
+  }
+
+  private class ThrowingReceiver<T> extends Receiver<T> {
+    private final RuntimeException e;
+
+    public ThrowingReceiver(RuntimeException e) {
+      this.e = e;
+    }
+
+    @Override
+    public void onFailure(ServerFailure error) {
+      throw e;
+    }
+
+    @Override
+    public void onSuccess(T response) {
+      throw e;
+    }
+
+    @Override
+    public void onViolation(Set<Violation> errors) {
+      throw e;
+    }
+  }
+
+  private static final int DELAY_TEST_FINISH = 10 * 1000;
+
+  GWT.UncaughtExceptionHandler defaultUncaughtExceptionHandler;
+
+  @Override
+  public String getModuleName() {
+    return "com.google.web.bindery.requestfactory.gwt.RequestFactorySuite";
+  }
+
+  @Override
+  public void gwtSetUp() {
+    super.gwtSetUp();
+
+    defaultUncaughtExceptionHandler = GWT.getUncaughtExceptionHandler();
+  }
+
+  /**
+   * Test mixed invocation failure and success. One receiver will throw from
+   * onSuccess and another from onFailure. Other receivers onSuccess and
+   * onFailure will corectly be called.
+   */
+  public void testMixedSuccessAndFailureThrow() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    final RuntimeException exception1 = new RuntimeException("first exception");
+    final RuntimeException exception2 = new RuntimeException("second exception");
+    final CountingReceiver count = new CountingReceiver();
+
+    SimpleFooRequest context = req.simpleFooRequest();
+    // 42 is the crash causing magic number for a runtime exception
+    context.pleaseCrash(42).to(count);
+    context.returnNullString().to(new ThrowingReceiver<String>(exception1));
+    context.returnNullString().to(count);
+
+    fireContextAndCatch(context, new ThrowingReceiver<Void>(exception2),
+        new GWT.UncaughtExceptionHandler() {
+          public void onUncaughtException(Throwable e) {
+            if (e instanceof UmbrellaException) {
+              count.assertCounts(1, 1, 0);
+
+              Set<Throwable> causes = ((UmbrellaException) e).getCauses();
+              assertEquals(2, causes.size());
+              assertTrue(causes.contains(exception1));
+              assertTrue(causes.contains(exception2));
+
+              finishTestAndReset();
+            } else {
+              defaultUncaughtExceptionHandler.onUncaughtException(e);
+            }
+          }
+        });
+  }
+
+  /**
+   * Test invocation failure. Other invocations' onSuccess should correctly be
+   * called.
+   */
+  public void testOnFailureThrow() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    final RuntimeException exception1 = new RuntimeException("first exception");
+    final RuntimeException exception2 = new RuntimeException("second exception");
+    final CountingReceiver count = new CountingReceiver();
+
+    SimpleFooRequest context = req.simpleFooRequest();
+    context.returnNullString().to(count);
+    // 42 is the crash causing magic number for a runtime exception
+    context.pleaseCrash(42).to(new ThrowingReceiver<Void>(exception1));
+    context.returnNullString().to(count);
+
+    fireContextAndCatch(context, new ThrowingReceiver<Void>(exception2),
+        new GWT.UncaughtExceptionHandler() {
+          public void onUncaughtException(Throwable e) {
+            if (e instanceof UmbrellaException) {
+              count.assertCounts(0, 2, 0);
+
+              Set<Throwable> causes = ((UmbrellaException) e).getCauses();
+              assertEquals(2, causes.size());
+              assertTrue(causes.contains(exception1));
+              assertTrue(causes.contains(exception2));
+
+              finishTestAndReset();
+            } else {
+              defaultUncaughtExceptionHandler.onUncaughtException(e);
+            }
+          }
+        });
+  }
+
+  /**
+   * Test global failure. All receivers will have their onFailure called, and
+   * some of them will throw.
+   */
+  public void testOnGlobalFailureThrow() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    final RuntimeException exception1 = new RuntimeException("first exception");
+    final RuntimeException exception2 = new RuntimeException("second exception");
+    final CountingReceiver count = new CountingReceiver();
+
+    SimpleFooRequest context = req.simpleFooRequest();
+    SimpleFooProxy newFoo = context.create(SimpleFooProxy.class);
+
+    context.returnNullString().to(count);
+    context.persist().using(newFoo).to(new ThrowingReceiver<Void>(exception1));
+    context.returnNullString().to(count);
+
+    final SimpleFooProxy mutableFoo = context.edit(newFoo);
+    // 42 is the crash causing magic number for a runtime exception
+    mutableFoo.setPleaseCrash(42);
+
+    fireContextAndCatch(context, new ThrowingReceiver<Void>(exception2),
+        new GWT.UncaughtExceptionHandler() {
+          public void onUncaughtException(Throwable e) {
+            if (e instanceof UmbrellaException) {
+              count.assertCounts(2, 0, 0);
+
+              Set<Throwable> causes = ((UmbrellaException) e).getCauses();
+              assertEquals(2, causes.size());
+              assertTrue(causes.contains(exception1));
+              assertTrue(causes.contains(exception2));
+
+              finishTestAndReset();
+            } else {
+              defaultUncaughtExceptionHandler.onUncaughtException(e);
+            }
+          }
+        });
+  }
+
+  /**
+   * All receivers will have their onSuccess called, and some of them will
+   * throw.
+   */
+  public void testOnSuccessThrow() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    final RuntimeException exception1 = new RuntimeException("first exception");
+    final RuntimeException exception2 = new RuntimeException("second exception");
+    final CountingReceiver count = new CountingReceiver();
+
+    SimpleFooRequest context = req.simpleFooRequest();
+    context.returnNullString().to(count);
+    context.returnNullString().to(new ThrowingReceiver<String>(exception1));
+    context.returnNullString().to(count);
+
+    fireContextAndCatch(context, new ThrowingReceiver<Void>(exception2),
+        new GWT.UncaughtExceptionHandler() {
+          public void onUncaughtException(Throwable e) {
+            if (e instanceof UmbrellaException) {
+              count.assertCounts(0, 2, 0);
+
+              Set<Throwable> causes = ((UmbrellaException) e).getCauses();
+              assertEquals(2, causes.size());
+              assertTrue(causes.contains(exception1));
+              assertTrue(causes.contains(exception2));
+
+              finishTestAndReset();
+            } else {
+              defaultUncaughtExceptionHandler.onUncaughtException(e);
+            }
+          }
+        });
+  }
+
+  /**
+   * Test violations. All receivers will have their onViolation called, and some
+   * of them will throw.
+   */
+  public void testOnViolationThrow() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    final RuntimeException exception1 = new RuntimeException("first exception");
+    final RuntimeException exception2 = new RuntimeException("second exception");
+    final CountingReceiver count = new CountingReceiver();
+
+    SimpleFooRequest context = req.simpleFooRequest();
+    SimpleFooProxy newFoo = context.create(SimpleFooProxy.class);
+    newFoo.setUserName("a"); // too short
+
+    context.returnNullString().to(count);
+    context.persist().using(newFoo).to(new ThrowingReceiver<Void>(exception1));
+    context.returnNullString().to(count);
+
+    fireContextAndCatch(context, new ThrowingReceiver<Void>(exception2),
+        new GWT.UncaughtExceptionHandler() {
+          public void onUncaughtException(Throwable e) {
+            if (e instanceof UmbrellaException) {
+              count.assertCounts(0, 0, 2);
+
+              Set<Throwable> causes = ((UmbrellaException) e).getCauses();
+              assertEquals(2, causes.size());
+              assertTrue(causes.contains(exception1));
+              assertTrue(causes.contains(exception2));
+
+              finishTestAndReset();
+            } else {
+              defaultUncaughtExceptionHandler.onUncaughtException(e);
+            }
+          }
+        });
+  }
+
+  protected void fireContextAndCatch(RequestContext context,
+      Receiver<Void> receiver, GWT.UncaughtExceptionHandler exceptionHandler) {
+    GWT.setUncaughtExceptionHandler(exceptionHandler);
+
+    if (receiver == null) {
+      context.fire();
+    } else {
+      context.fire(receiver);
+    }
+  }
+
+  @Override
+  protected void gwtTearDown() {
+    GWT.setUncaughtExceptionHandler(defaultUncaughtExceptionHandler);
+  }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryPolymorphicTest.java b/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryPolymorphicTest.java
new file mode 100644
index 0000000..83baef3
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryPolymorphicTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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.requestfactory.gwt.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.web.bindery.event.shared.EventBus;
+import com.google.web.bindery.event.shared.SimpleEventBus;
+import com.google.web.bindery.requestfactory.shared.SimpleFooProxy;
+import com.google.web.bindery.requestfactory.shared.TestRequestFactory;
+
+/**
+ * Just tests the
+ * {@link com.google.web.bindery.requestfactory.gwt.rebind.RequestFactoryGenerator} to see
+ * if polymorphic signatures are allowed.
+ */
+public class RequestFactoryPolymorphicTest extends GWTTestCase {
+
+  @Override
+  public String getModuleName() {
+    return "com.google.web.bindery.requestfactory.gwt.RequestFactorySuite";
+  }
+
+  public void testGenerator() {
+    TestRequestFactory rf = GWT.create(TestRequestFactory.class);
+    EventBus eventBus = new SimpleEventBus();
+    rf.initialize(eventBus);
+    SimpleFooProxy simpleFoo = rf.testFooPolymorphicRequest().create(
+        SimpleFooProxy.class);
+    assertNull(simpleFoo.getUserName());
+  }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryTest.java b/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryTest.java
new file mode 100644
index 0000000..6b6f4fd
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryTest.java
@@ -0,0 +1,2741 @@
+/*
+ * 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.requestfactory.gwt.client;
+
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.EntityProxyChange;
+import com.google.web.bindery.requestfactory.shared.EntityProxyId;
+import com.google.web.bindery.requestfactory.shared.OnlyUsedByRequestContextMethod;
+import com.google.web.bindery.requestfactory.shared.OnlyUsedInListProxy;
+import com.google.web.bindery.requestfactory.shared.Receiver;
+import com.google.web.bindery.requestfactory.shared.Request;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.ServerFailure;
+import com.google.web.bindery.requestfactory.shared.SimpleBarProxy;
+import com.google.web.bindery.requestfactory.shared.SimpleBarRequest;
+import com.google.web.bindery.requestfactory.shared.SimpleEnum;
+import com.google.web.bindery.requestfactory.shared.SimpleFooProxy;
+import com.google.web.bindery.requestfactory.shared.SimpleFooRequest;
+import com.google.web.bindery.requestfactory.shared.SimpleValueContext;
+import com.google.web.bindery.requestfactory.shared.SimpleValueProxy;
+import com.google.web.bindery.requestfactory.shared.Violation;
+import com.google.web.bindery.requestfactory.shared.impl.SimpleEntityProxyId;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Tests for {@link com.google.web.bindery.requestfactory.shared.RequestFactory}.
+ */
+public class RequestFactoryTest extends RequestFactoryTestBase {
+  /*
+   * DO NOT USE finishTest(). Instead, call finishTestAndReset();
+   * 
+   * When possible, pass any returned proxies to checkSerialization() and use
+   * the return value in the place of the returned object.
+   */
+
+  class FooReciever extends Receiver<SimpleFooProxy> {
+    private SimpleFooProxy mutableFoo;
+    private Request<SimpleFooProxy> persistRequest;
+    private String expectedException;
+
+    public FooReciever(SimpleFooProxy mutableFoo,
+        Request<SimpleFooProxy> persistRequest, String exception) {
+      this.mutableFoo = mutableFoo;
+      this.persistRequest = persistRequest;
+      this.expectedException = exception;
+    }
+
+    @Override
+    public void onFailure(ServerFailure error) {
+      assertEquals(expectedException, error.getExceptionType());
+      if (expectedException != null) {
+        assertFalse(error.getStackTraceString().length() == 0);
+        assertEquals("THIS EXCEPTION IS EXPECTED BY A TEST", error.getMessage());
+      } else {
+        assertEquals(null, error.getStackTraceString());
+        assertEquals("Server Error: THIS EXCEPTION IS EXPECTED BY A TEST",
+            error.getMessage());
+      }
+
+      // Now show that we can fix the error and try again with the same
+      // request
+
+      mutableFoo.setPleaseCrash(24); // Only 42 and 43 crash
+      persistRequest.fire(new Receiver<SimpleFooProxy>() {
+        @Override
+        public void onSuccess(SimpleFooProxy response) {
+          response = checkSerialization(response);
+          finishTestAndReset();
+        }
+      });
+    }
+
+    @Override
+    public void onSuccess(SimpleFooProxy response) {
+      fail("Failure expected but onSuccess() was called");
+    }
+
+    @Override
+    public void onViolation(Set<Violation> errors) {
+      fail("Failure expected but onViolation() was called");
+    }
+  }
+
+  class NullReceiver extends Receiver<Object> {
+    @Override
+    public void onSuccess(Object response) {
+      assertNull(response);
+      finishTestAndReset();
+    }
+  }
+
+  private class FailFixAndRefire<T> extends Receiver<T> {
+
+    private final SimpleFooProxy proxy;
+    private final Request<T> request;
+    private boolean voidReturnExpected;
+
+    FailFixAndRefire(SimpleFooProxy proxy, RequestContext context,
+        Request<T> request) {
+      this.proxy = context.edit(proxy);
+      this.request = request;
+    }
+
+    @Override
+    public void onSuccess(T response) {
+      /*
+       * Make sure your class path includes:
+       * 
+       * tools/lib/apache/log4j/log4j-1.2.16.jar
+       * tools/lib/hibernate/validator/hibernate-validator-4.1.0.Final.jar
+       * tools/lib/slf4j/slf4j-api/slf4j-api-1.6.1.jar
+       * tools/lib/slf4j/slf4j-log4j12/slf4j-log4j12-1.6.1.jar
+       */
+      fail("Violations expected (you might be missing some jars, "
+          + "see the comment above this line)");
+    }
+
+    @Override
+    public void onViolation(Set<Violation> errors) {
+
+      // size violation expected
+
+      assertEquals(1, errors.size());
+      Violation error = errors.iterator().next();
+      assertEquals("userName", error.getPath());
+      assertEquals("size must be between 3 and 30", error.getMessage());
+      assertEquals(proxy.stableId(), error.getProxyId());
+
+      // Now re-used the request to fix the edit
+
+      proxy.setUserName("long enough");
+      request.fire(new Receiver<T>() {
+        @Override
+        public void onSuccess(T response) {
+          if (voidReturnExpected) {
+            assertNull(response);
+          } else {
+            assertEquals(proxy.stableId(),
+                ((SimpleFooProxy) response).stableId());
+          }
+          finishTestAndReset();
+        }
+      });
+    }
+
+    void doTest() {
+      proxy.setUserName("a"); // too short
+      request.fire(this);
+    }
+
+    void doVoidTest() {
+      voidReturnExpected = true;
+      doTest();
+    }
+  }
+
+  private static final int DELAY_TEST_FINISH = 10 * 1000;
+
+  public <T extends EntityProxy> void assertContains(Collection<T> col, T value) {
+    EntityProxyId<?> lookFor = value.stableId();
+    for (T x : col) {
+      EntityProxyId<?> found = x.stableId();
+      if (found.equals(lookFor)) {
+        return;
+      }
+    }
+    assertTrue("Value " + value + " not found in collection " + col.toString(),
+        false);
+  }
+
+  public <T extends EntityProxy> void assertNotContains(Collection<T> col,
+      T value) {
+    for (T x : col) {
+      assertNotSame(x.stableId(), value.stableId());
+    }
+  }
+
+  @Override
+  public String getModuleName() {
+    return "com.google.web.bindery.requestfactory.gwt.RequestFactorySuite";
+  }
+
+  /**
+   * Test that the same object, referenced twice, points to the same instance.
+   */
+  public void testAntiAliasing() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    simpleFooRequest().fetchDoubleReference().with("fooField",
+        "selfOneToManyField").fire(new Receiver<SimpleFooProxy>() {
+      @Override
+      public void onSuccess(SimpleFooProxy response) {
+        response = checkSerialization(response);
+        assertNotNull(response.getFooField());
+        assertSame(response.getFooField(),
+            response.getSelfOneToManyField().get(0));
+        finishTestAndReset();
+      }
+    });
+  }
+
+  /**
+   * Test that we can commit child objects.
+   */
+  public void testCascadingCommit() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    SimpleFooRequest context = req.simpleFooRequest();
+    final SimpleFooProxy foo = context.create(SimpleFooProxy.class);
+    final SimpleBarProxy bar0 = context.create(SimpleBarProxy.class);
+    final SimpleBarProxy bar1 = context.create(SimpleBarProxy.class);
+    List<SimpleBarProxy> bars = new ArrayList<SimpleBarProxy>();
+    bars.add(bar0);
+    bars.add(bar1);
+
+    final SimpleFooEventHandler<SimpleBarProxy> handler = new SimpleFooEventHandler<SimpleBarProxy>();
+    EntityProxyChange.registerForProxyType(req.getEventBus(),
+        SimpleBarProxy.class, handler);
+
+    Request<SimpleFooProxy> request = context.persistCascadingAndReturnSelf().using(
+        foo);
+    SimpleFooProxy editFoo = context.edit(foo);
+    editFoo.setOneToManyField(bars);
+    request.fire(new Receiver<SimpleFooProxy>() {
+      @Override
+      public void onSuccess(SimpleFooProxy response) {
+        response = checkSerialization(response);
+        assertFalse(((SimpleEntityProxyId<SimpleFooProxy>) response.stableId()).isEphemeral());
+        assertEquals(2, handler.persistEventCount); // two bars persisted.
+        assertEquals(2, handler.updateEventCount); // two bars persisted.
+        assertEquals(4, handler.totalEventCount);
+        finishTestAndReset();
+      }
+    });
+  }
+
+  public void testChangedCreate() {
+    SimpleFooRequest context = simpleFooRequest();
+
+    // Creates don't cause a change
+    SimpleFooProxy foo = context.create(SimpleFooProxy.class);
+    assertFalse(context.isChanged());
+
+    // Change
+    foo.setCharField('c');
+    assertTrue(context.isChanged());
+
+    // Undo the change
+    foo.setCharField(null);
+    assertFalse(context.isChanged());
+  }
+
+  public void testChangedCreateValueProxy() {
+    SimpleFooRequest context = simpleFooRequest();
+
+    // Creates don't cause a change
+    SimpleValueProxy foo = context.create(SimpleValueProxy.class);
+    assertFalse(context.isChanged());
+
+    // Change
+    foo.setString("foo");
+    assertTrue(context.isChanged());
+
+    // Undo the change
+    foo.setString(null);
+    assertFalse(context.isChanged());
+  }
+
+  public void testChangedEdit() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    simpleFooRequest().findSimpleFooById(1L).fire(
+        new Receiver<SimpleFooProxy>() {
+
+          @Override
+          public void onSuccess(SimpleFooProxy foo) {
+            foo = checkSerialization(foo);
+            SimpleFooRequest context = simpleFooRequest();
+
+            // edit() doesn't cause a change
+            foo = context.edit(foo);
+            assertFalse(context.isChanged());
+
+            final String newName = "something else;";
+            String oldName = foo.getUserName();
+            assertFalse("Don't accidentally set the same name",
+                newName.equals(oldName));
+
+            // gets don't cause a change
+            assertFalse(context.isChanged());
+
+            // Change
+            foo.setUserName(newName);
+            assertTrue(context.isChanged());
+
+            // Undo the change
+            foo.setUserName(oldName);
+            assertFalse(context.isChanged());
+
+            finishTestAndReset();
+          }
+        });
+  }
+
+  public void testChangedNothing() {
+    SimpleFooRequest context = simpleFooRequest();
+    assertFalse(context.isChanged());
+  }
+
+  public void testClassToken() {
+    String token = req.getHistoryToken(SimpleFooProxy.class);
+    assertEquals(SimpleFooProxy.class, req.getProxyClass(token));
+
+    SimpleFooProxy foo = simpleFooRequest().create(SimpleFooProxy.class);
+    assertEquals(SimpleFooProxy.class, foo.stableId().getProxyClass());
+  }
+
+  public void testCollectionSubProperties() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    simpleFooRequest().getSimpleFooWithSubPropertyCollection().with(
+        "selfOneToManyField", "selfOneToManyField.fooField").fire(
+        new Receiver<SimpleFooProxy>() {
+          @Override
+          public void onSuccess(SimpleFooProxy response) {
+            response = checkSerialization(response);
+            assertEquals(
+                "I'm here",
+                response.getSelfOneToManyField().get(0).getFooField().getUserName());
+            finishTestAndReset();
+          }
+        });
+  }
+
+  public void testDomainUpcast() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    simpleFooRequest().returnSimpleFooSubclass().fire(
+        new Receiver<SimpleFooProxy>() {
+          @Override
+          public void onSuccess(SimpleFooProxy response) {
+            response = checkSerialization(response);
+            assertEquals(42, response.getIntId().intValue());
+            finishTestAndReset();
+          }
+        });
+  }
+
+  public void testDummyCreate() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    final SimpleFooEventHandler<SimpleFooProxy> handler = new SimpleFooEventHandler<SimpleFooProxy>();
+    EntityProxyChange.registerForProxyType(req.getEventBus(),
+        SimpleFooProxy.class, handler);
+
+    SimpleFooRequest context = simpleFooRequest();
+    final SimpleFooProxy foo = context.create(SimpleFooProxy.class);
+    final EntityProxyId<SimpleFooProxy> futureId = foo.stableId();
+    assertTrue(((SimpleEntityProxyId<?>) futureId).isEphemeral());
+    Request<SimpleFooProxy> fooReq = context.persistAndReturnSelf().using(foo);
+    fooReq.fire(new Receiver<SimpleFooProxy>() {
+
+      @Override
+      public void onSuccess(SimpleFooProxy returned) {
+        returned = checkSerialization(returned);
+        EntityProxyId<SimpleFooProxy> returnedId = returned.stableId();
+        assertEquals(futureId, returnedId);
+        assertFalse((((SimpleEntityProxyId<?>) returnedId).isEphemeral()));
+        assertEquals(1, handler.persistEventCount);
+        assertEquals(1, handler.updateEventCount);
+        assertEquals(2, handler.totalEventCount);
+
+        checkStableIdEquals(foo, returned);
+        finishTestAndReset();
+      }
+    });
+  }
+
+  public void testDummyCreateBar() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    SimpleBarRequest context = simpleBarRequest();
+    final SimpleBarProxy foo = context.create(SimpleBarProxy.class);
+    assertTrue(((SimpleEntityProxyId<?>) foo.stableId()).isEphemeral());
+    Request<SimpleBarProxy> fooReq = context.persistAndReturnSelf().using(foo);
+    fooReq.fire(new Receiver<SimpleBarProxy>() {
+
+      @Override
+      public void onSuccess(SimpleBarProxy returned) {
+        returned = checkSerialization(returned);
+        assertFalse(((SimpleEntityProxyId<?>) foo.stableId()).isEphemeral());
+
+        checkStableIdEquals(foo, returned);
+        finishTestAndReset();
+      }
+    });
+  }
+
+  public void testDummyCreateList() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    SimpleBarRequest context = simpleBarRequest();
+    final SimpleBarProxy bar = context.create(SimpleBarProxy.class);
+    assertTrue(((SimpleEntityProxyId<?>) bar.stableId()).isEphemeral());
+    Request<SimpleBarProxy> fooReq = context.returnFirst(Collections.singletonList(bar));
+    fooReq.fire(new Receiver<SimpleBarProxy>() {
+
+      @Override
+      public void onSuccess(SimpleBarProxy returned) {
+        returned = checkSerialization(returned);
+        assertFalse(((SimpleEntityProxyId<?>) bar.stableId()).isEphemeral());
+        assertFalse(((SimpleEntityProxyId<?>) returned.stableId()).isEphemeral());
+
+        checkStableIdEquals(bar, returned);
+        finishTestAndReset();
+      }
+    });
+  }
+
+  /**
+   * Tests behaviors relating to editing an object with one context and then
+   * using with another.
+   */
+  public void testEditAcrossContexts() {
+    SimpleFooRequest contextA = simpleFooRequest();
+    final SimpleFooRequest contextB = simpleFooRequest();
+
+    SimpleFooProxy fromA = contextA.create(SimpleFooProxy.class);
+
+    try {
+      contextB.edit(fromA);
+      fail();
+    } catch (IllegalArgumentException expected) {
+    }
+
+    try {
+      contextB.persistAndReturnSelf().using(fromA).fire(
+          new Receiver<SimpleFooProxy>() {
+            @Override
+            public void onSuccess(SimpleFooProxy response) {
+              fail();
+            }
+          });
+      fail();
+    } catch (IllegalArgumentException expected) {
+    }
+
+    delayTestFinish(DELAY_TEST_FINISH);
+    contextA.findSimpleFooById(999L).fire(new Receiver<SimpleFooProxy>() {
+      @Override
+      public void onSuccess(SimpleFooProxy response) {
+        response = checkSerialization(response);
+        // The response shouldn't be associated with a RequestContext
+        contextB.edit(response);
+        finishTestAndReset();
+      }
+    });
+  }
+
+  /**
+   * Tests that enum values used only as method parameters in a RequestContext
+   * are in the EnumMap. This test only applies to GWT-based clients.
+   */
+  public void testEnumOnlyUsedByRequestContext() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    SimpleFooRequest ctx = simpleFooRequest();
+    ctx.receiveEnum(OnlyUsedByRequestContextMethod.FOO).fire(
+        new Receiver<Void>() {
+          @Override
+          public void onSuccess(Void response) {
+            finishTest();
+          }
+        });
+  }
+
+  /**
+   * Check default value, a newly-set value, and a null value.
+   */
+  public void testEnumProperty() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    simpleFooRequest().findSimpleFooById(999L).fire(
+        new Receiver<SimpleFooProxy>() {
+          @Override
+          public void onSuccess(SimpleFooProxy response) {
+            assertEquals(SimpleEnum.FOO, response.getEnumField());
+            SimpleFooRequest ctx = simpleFooRequest();
+            response = ctx.edit(response);
+            response.setEnumField(SimpleEnum.BAR);
+            ctx.persistAndReturnSelf().using(response).fire(
+                new Receiver<SimpleFooProxy>() {
+                  @Override
+                  public void onSuccess(SimpleFooProxy response) {
+                    assertEquals(SimpleEnum.BAR, response.getEnumField());
+                    SimpleFooRequest ctx = simpleFooRequest();
+                    response = ctx.edit(response);
+                    response.setEnumField(null);
+                    ctx.persistAndReturnSelf().using(response).fire(
+                        new Receiver<SimpleFooProxy>() {
+                          @Override
+                          public void onSuccess(SimpleFooProxy response) {
+                            assertNull(response.getEnumField());
+                            finishTestAndReset();
+                          }
+                        });
+                  }
+                });
+          }
+        });
+  }
+
+  public void testFetchEntity() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    simpleFooRequest().findSimpleFooById(999L).fire(
+        new Receiver<SimpleFooProxy>() {
+          @Override
+          public void onSuccess(SimpleFooProxy response) {
+            response = checkSerialization(response);
+            assertEquals(42, (int) response.getIntId());
+            assertEquals("GWT", response.getUserName());
+            assertEquals(8L, (long) response.getLongField());
+            assertEquals(com.google.web.bindery.requestfactory.shared.SimpleEnum.FOO,
+                response.getEnumField());
+            assertEquals(null, response.getBarField());
+            finishTestAndReset();
+          }
+        });
+  }
+
+  public void testFetchEntityWithRelation() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    simpleFooRequest().findSimpleFooById(999L).with("barField").fire(
+        new Receiver<SimpleFooProxy>() {
+          @Override
+          public void onSuccess(SimpleFooProxy response) {
+            response = checkSerialization(response);
+            assertEquals(42, (int) response.getIntId());
+            assertEquals("GWT", response.getUserName());
+            assertEquals(8L, (long) response.getLongField());
+            assertEquals(com.google.web.bindery.requestfactory.shared.SimpleEnum.FOO,
+                response.getEnumField());
+            assertNotNull(response.getBarField());
+            finishTestAndReset();
+          }
+        });
+  }
+
+  public void testFetchList() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    simpleFooRequest().findAll().fire(new Receiver<List<SimpleFooProxy>>() {
+      @Override
+      public void onSuccess(List<SimpleFooProxy> responseList) {
+        SimpleFooProxy response = checkSerialization(responseList.get(0));
+        assertEquals(42, (int) response.getIntId());
+        assertEquals("GWT", response.getUserName());
+        assertEquals(8L, (long) response.getLongField());
+        assertEquals(com.google.web.bindery.requestfactory.shared.SimpleEnum.FOO,
+            response.getEnumField());
+        finishTestAndReset();
+      }
+    });
+  }
+
+  public void testFetchSet() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    simpleBarRequest().findAsSet().fire(new Receiver<Set<SimpleBarProxy>>() {
+      @Override
+      public void onSuccess(Set<SimpleBarProxy> response) {
+        assertEquals(2, response.size());
+        finishTestAndReset();
+      }
+    });
+  }
+
+  public void testFindFindEdit() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    final SimpleFooEventHandler<SimpleFooProxy> handler = new SimpleFooEventHandler<SimpleFooProxy>();
+    EntityProxyChange.registerForProxyType(req.getEventBus(),
+        SimpleFooProxy.class, handler);
+
+    req.simpleFooRequest().findSimpleFooById(999L).fire(
+        new Receiver<SimpleFooProxy>() {
+
+          @Override
+          public void onSuccess(SimpleFooProxy newFoo) {
+            newFoo = checkSerialization(newFoo);
+            assertEquals(1, handler.updateEventCount);
+            assertEquals(1, handler.totalEventCount);
+
+            req.simpleFooRequest().findSimpleFooById(999L).fire(
+                new Receiver<SimpleFooProxy>() {
+
+                  @Override
+                  public void onSuccess(SimpleFooProxy newFoo) {
+                    newFoo = checkSerialization(newFoo);
+                    // no events are fired second time.
+                    assertEquals(1, handler.updateEventCount);
+                    assertEquals(1, handler.totalEventCount);
+                    SimpleFooRequest context = req.simpleFooRequest();
+                    final Request<Void> mutateRequest = context.persist().using(
+                        newFoo);
+                    newFoo = context.edit(newFoo);
+                    newFoo.setUserName("Ray");
+                    mutateRequest.fire(new Receiver<Void>() {
+                      @Override
+                      public void onSuccess(Void response) {
+                        // events fired on updates.
+                        assertEquals(2, handler.updateEventCount);
+                        assertEquals(2, handler.totalEventCount);
+
+                        finishTestAndReset();
+                      }
+                    });
+                  }
+                });
+          }
+        });
+  }
+
+  public void testForwardReferenceDecode() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    simpleFooRequest().getTripletReference().with(
+        "selfOneToManyField.selfOneToManyField.fooField").fire(
+        new Receiver<SimpleFooProxy>() {
+          @Override
+          public void onSuccess(SimpleFooProxy response) {
+            response = checkSerialization(response);
+            assertNotNull(response.getSelfOneToManyField().get(0));
+            assertNotNull(response.getSelfOneToManyField().get(0).getSelfOneToManyField());
+            assertNotNull(response.getSelfOneToManyField().get(0).getSelfOneToManyField().get(
+                0));
+            assertNotNull(response.getSelfOneToManyField().get(0).getSelfOneToManyField().get(
+                0).getFooField());
+            finishTestAndReset();
+          }
+        });
+  }
+
+  public void testGetEventBus() {
+    assertEquals(eventBus, req.getEventBus());
+  }
+
+  public void testGetListLongId() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    // Long ids
+    simpleFooRequest().findAll().with("barField.userName").fire(
+        new Receiver<List<SimpleFooProxy>>() {
+          @Override
+          public void onSuccess(List<SimpleFooProxy> response) {
+            assertEquals(2, response.size());
+            for (SimpleFooProxy foo : response) {
+              foo = checkSerialization(foo);
+              assertNotNull(foo.stableId());
+              assertEquals("FOO", foo.getBarField().getUserName());
+            }
+            finishTestAndReset();
+          }
+        });
+  }
+
+  public void testGetListStringId() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    // String ids
+    simpleBarRequest().findAll().fire(new Receiver<List<SimpleBarProxy>>() {
+      @Override
+      public void onSuccess(List<SimpleBarProxy> response) {
+        assertEquals(2, response.size());
+        for (SimpleBarProxy bar : response) {
+          bar = checkSerialization(bar);
+          assertNotNull(bar.stableId());
+          finishTestAndReset();
+        }
+        finishTestAndReset();
+      }
+    });
+  }
+
+  public void testHistoryToken() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    SimpleBarRequest context = simpleBarRequest();
+    final SimpleBarProxy foo = context.create(SimpleBarProxy.class);
+    final EntityProxyId<SimpleBarProxy> futureId = foo.stableId();
+    final String futureToken = req.getHistoryToken(futureId);
+
+    // Check that a newly-created object's token can be found
+    assertEquals(futureId, req.getProxyId(futureToken));
+    assertEquals(futureId.getProxyClass(), req.getProxyClass(futureToken));
+
+    Request<SimpleBarProxy> fooReq = context.persistAndReturnSelf().using(foo);
+    fooReq.fire(new Receiver<SimpleBarProxy>() {
+      @Override
+      public void onSuccess(SimpleBarProxy returned) {
+        returned = checkSerialization(returned);
+        EntityProxyId<SimpleBarProxy> persistedId = returned.stableId();
+        String persistedToken = req.getHistoryToken(returned.stableId());
+
+        // Expect variations after persist
+        assertFalse(futureToken.equals(persistedToken));
+
+        // Make sure the token is stable after persist using the future id
+        assertEquals(persistedToken, req.getHistoryToken(futureId));
+
+        // Check that the persisted object can be found with future token
+        assertEquals(futureId, req.getProxyId(futureToken));
+        assertEquals(futureId, req.getProxyId(persistedToken));
+        assertEquals(futureId.getProxyClass(),
+            req.getProxyClass(persistedToken));
+
+        assertEquals(persistedId, req.getProxyId(futureToken));
+        assertEquals(persistedId, req.getProxyId(persistedToken));
+        assertEquals(persistedId.getProxyClass(),
+            req.getProxyClass(futureToken));
+
+        finishTestAndReset();
+      }
+    });
+  }
+
+  public void testInstanceServiceRequest() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    req.instanceServiceRequest().add(5).fire(new Receiver<Integer>() {
+      @Override
+      public void onSuccess(Integer response) {
+        assertEquals(10, (int) response);
+        finishTestAndReset();
+      }
+    });
+  }
+
+  public void testInstanceServiceRequestByName() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    req.instanceServiceRequestByName().add(5).fire(new Receiver<Integer>() {
+      @Override
+      public void onSuccess(Integer response) {
+        assertEquals(10, (int) response);
+        finishTestAndReset();
+      }
+    });
+  }
+
+  /**
+   * Make sure our stock RF logging service keeps receiving.
+   */
+  public void testLoggingService() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    String logRecordJson = new StringBuilder("{") //
+    .append("\"level\": \"ALL\", ") //
+    .append("\"loggerName\": \"logger\", ") //
+    .append("\"msg\": \"Hi mom\", ") //
+    .append("\"timestamp\": \"1234567890\",") //
+    .append("\"thrown\": {}") //
+    .append("}") //
+    .toString();
+
+    req.loggingRequest().logMessage(logRecordJson).fire(new Receiver<Void>() {
+      @Override
+      public void onSuccess(Void response) {
+        finishTestAndReset();
+      }
+    });
+  }
+
+  /**
+   * Test that removing a parent entity and implicitly removing the child sends
+   * an event to the client that the child was removed.
+   */
+  public void testMethodWithSideEffectDeleteChild() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    // Handle changes to SimpleFooProxy.
+    final SimpleFooEventHandler<SimpleFooProxy> fooHandler = new SimpleFooEventHandler<SimpleFooProxy>();
+    EntityProxyChange.registerForProxyType(req.getEventBus(),
+        SimpleFooProxy.class, fooHandler);
+
+    // Handle changes to SimpleBarProxy.
+    final SimpleFooEventHandler<SimpleBarProxy> barHandler = new SimpleFooEventHandler<SimpleBarProxy>();
+    EntityProxyChange.registerForProxyType(req.getEventBus(),
+        SimpleBarProxy.class, barHandler);
+
+    // Persist bar.
+    SimpleBarRequest context = req.simpleBarRequest();
+    final SimpleBarProxy bar = context.create(SimpleBarProxy.class);
+    context.persistAndReturnSelf().using(bar).fire(
+        new Receiver<SimpleBarProxy>() {
+          @Override
+          public void onSuccess(SimpleBarProxy persistentBar) {
+            persistentBar = checkSerialization(persistentBar);
+            // Persist foo with bar as a child.
+            SimpleFooRequest context = req.simpleFooRequest();
+            SimpleFooProxy foo = context.create(SimpleFooProxy.class);
+            final Request<SimpleFooProxy> persistRequest = context.persistAndReturnSelf().using(
+                foo).with("barField");
+            foo = context.edit(foo);
+            foo.setUserName("John");
+            foo.setBarField(bar);
+            persistRequest.fire(new Receiver<SimpleFooProxy>() {
+              @Override
+              public void onSuccess(SimpleFooProxy persistentFoo) {
+                persistentFoo = checkSerialization(persistentFoo);
+
+                // Delete bar.
+                SimpleFooRequest context = req.simpleFooRequest();
+                final Request<Void> deleteRequest = context.deleteBar().using(
+                    persistentFoo);
+                deleteRequest.fire(new Receiver<Void>() {
+                  @Override
+                  public void onSuccess(Void response) {
+                    assertEquals(1, fooHandler.persistEventCount);
+                    assertEquals(2, fooHandler.updateEventCount);
+                    assertEquals(3, fooHandler.totalEventCount);
+
+                    assertEquals(1, barHandler.persistEventCount);
+                    assertEquals(1, barHandler.updateEventCount);
+                    assertEquals(1, barHandler.deleteEventCount);
+                    assertEquals(3, barHandler.totalEventCount);
+                    finishTestAndReset();
+                  }
+                });
+              }
+            });
+          }
+        });
+  }
+
+  /*
+   * tests that (a) any method can have a side effect that is handled correctly.
+   * (b) instance methods are handled correctly and (c) a request cannot be
+   * reused after a successful response is received. (Yet?)
+   */
+  public void testMethodWithSideEffects() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    final SimpleFooEventHandler<SimpleFooProxy> handler = new SimpleFooEventHandler<SimpleFooProxy>();
+    EntityProxyChange.registerForProxyType(req.getEventBus(),
+        SimpleFooProxy.class, handler);
+
+    simpleFooRequest().findSimpleFooById(999L).fire(
+        new Receiver<SimpleFooProxy>() {
+
+          @Override
+          public void onSuccess(SimpleFooProxy newFoo) {
+            newFoo = checkSerialization(newFoo);
+            assertEquals(1, handler.updateEventCount);
+            assertEquals(1, handler.totalEventCount);
+            SimpleFooRequest context = simpleFooRequest();
+            final Request<Long> mutateRequest = context.countSimpleFooWithUserNameSideEffect().using(
+                newFoo);
+            newFoo = context.edit(newFoo);
+            newFoo.setUserName("Ray");
+            mutateRequest.fire(new Receiver<Long>() {
+              @Override
+              public void onSuccess(Long response) {
+                assertCannotFire(mutateRequest);
+                assertEquals(new Long(2L), response);
+                assertEquals(2, handler.updateEventCount);
+                assertEquals(2, handler.totalEventCount);
+
+                // confirm that the instance method did have the desired
+                // sideEffect.
+                simpleFooRequest().findSimpleFooById(999L).fire(
+                    new Receiver<SimpleFooProxy>() {
+                      @Override
+                      public void onSuccess(SimpleFooProxy finalFoo) {
+                        finalFoo = checkSerialization(finalFoo);
+                        assertEquals("Ray", finalFoo.getUserName());
+                        assertEquals(2, handler.updateEventCount);
+                        assertEquals(2, handler.totalEventCount);
+                        finishTestAndReset();
+                      }
+                    });
+              }
+
+            });
+
+            try {
+              newFoo.setUserName("Barney");
+              fail();
+            } catch (IllegalStateException e) {
+              /* pass, cannot change a request that is in flight */
+            }
+          }
+        });
+  }
+
+  public void testMultipleEdits() {
+    RequestContext c1 = req.simpleFooRequest();
+    SimpleFooProxy proxy = c1.create(SimpleFooProxy.class);
+    // Re-editing is idempotent
+    assertSame(proxy, c1.edit(c1.edit(proxy)));
+
+    // Should not allow "crossing the steams"
+    RequestContext c2 = req.simpleFooRequest();
+    try {
+      c2.edit(proxy);
+      fail();
+    } catch (IllegalArgumentException expected) {
+    }
+  }
+
+  /**
+   * Tests a no-op request.
+   */
+  public void testNoOpRequest() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    simpleFooRequest().fire(new Receiver<Void>() {
+      @Override
+      public void onSuccess(Void response) {
+        finishTestAndReset();
+      }
+    });
+  }
+
+  /**
+   * Ensures that a service method can respond with a null value.
+   */
+  public void testNullEntityProxyResult() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    simpleFooRequest().returnNullSimpleFoo().fire(new NullReceiver());
+  }
+
+  /**
+   * Test that a null value can be sent in a request.
+   */
+  public void testNullListRequest() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    final Request<Void> fooReq = req.simpleFooRequest().receiveNullList(null);
+    fooReq.fire(new Receiver<Void>() {
+      @Override
+      public void onSuccess(Void v) {
+        finishTestAndReset();
+      }
+    });
+  }
+
+  /**
+   * Ensures that a service method can respond with a null value.
+   */
+  public void testNullListResult() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    simpleFooRequest().returnNullList().fire(new NullReceiver());
+  }
+
+  /**
+   * Test that a null value can be sent in a request.
+   */
+  public void testNullSimpleFooRequest() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    final Request<Void> fooReq = req.simpleFooRequest().receiveNullSimpleFoo(
+        null);
+    fooReq.fire(new Receiver<Void>() {
+      @Override
+      public void onSuccess(Void v) {
+        finishTestAndReset();
+      }
+    });
+  }
+
+  /**
+   * Test that a null value can be sent to an instance method.
+   */
+  public void testNullStringInstanceRequest() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    // Get a valid proxy entity.
+    req.simpleFooRequest().findSimpleFooById(999L).fire(
+        new Receiver<SimpleFooProxy>() {
+          @Override
+          public void onSuccess(SimpleFooProxy response) {
+            response = checkSerialization(response);
+            Request<Void> fooReq = req.simpleFooRequest().receiveNull(null).using(
+                response);
+            fooReq.fire(new Receiver<Void>() {
+              @Override
+              public void onSuccess(Void v) {
+                finishTestAndReset();
+              }
+            });
+          }
+        });
+  }
+
+  /**
+   * Test that a null value can be sent in a request.
+   */
+  public void testNullStringRequest() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    final Request<Void> fooReq = req.simpleFooRequest().receiveNullString(null);
+    fooReq.fire(new Receiver<Void>() {
+      @Override
+      public void onSuccess(Void v) {
+        finishTestAndReset();
+      }
+    });
+  }
+
+  /**
+   * Ensures that a service method can respond with a null value.
+   */
+  public void testNullStringResult() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    simpleFooRequest().returnNullString().fire(new NullReceiver());
+  }
+
+  /**
+   * Test that a null value can be sent within a list of entities.
+   */
+  public void testNullValueInEntityListRequest() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    // Get a valid proxy entity.
+    req.simpleFooRequest().findSimpleFooById(999L).fire(
+        new Receiver<SimpleFooProxy>() {
+          @Override
+          public void onSuccess(SimpleFooProxy response) {
+            response = checkSerialization(response);
+            List<SimpleFooProxy> list = new ArrayList<SimpleFooProxy>();
+            list.add(response); // non-null
+            list.add(null); // null
+            final Request<Void> fooReq = req.simpleFooRequest().receiveNullValueInEntityList(
+                list);
+            fooReq.fire(new Receiver<Void>() {
+              @Override
+              public void onSuccess(Void v) {
+                finishTestAndReset();
+              }
+            });
+          }
+        });
+  }
+
+  /**
+   * Test that a null value can be sent within a list of objects.
+   */
+  public void testNullValueInIntegerListRequest() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    List<Integer> list = Arrays.asList(new Integer[] {1, 2, null});
+    final Request<Void> fooReq = req.simpleFooRequest().receiveNullValueInIntegerList(
+        list);
+    fooReq.fire(new Receiver<Void>() {
+      @Override
+      public void onSuccess(Void v) {
+        finishTestAndReset();
+      }
+    });
+  }
+
+  /**
+   * Test that a null value can be sent within a list of strings.
+   */
+  public void testNullValueInStringListRequest() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    List<String> list = Arrays.asList(new String[] {"nonnull", "null", null});
+    final Request<Void> fooReq = req.simpleFooRequest().receiveNullValueInStringList(
+        list);
+    fooReq.fire(new Receiver<Void>() {
+      @Override
+      public void onSuccess(Void v) {
+        finishTestAndReset();
+      }
+    });
+  }
+
+  /**
+   * Tests a message consisting only of operations, with no invocations.
+   */
+  public void testOperationOnlyMessage() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    RequestContext ctx = simpleFooRequest();
+    SimpleFooProxy proxy = ctx.create(SimpleFooProxy.class);
+    proxy.setUserName("GWT");
+    ctx.fire(new Receiver<Void>() {
+      @Override
+      public void onSuccess(Void response) {
+        finishTestAndReset();
+      }
+    });
+  }
+
+  public void testPersistAllValueTypes() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    SimpleFooRequest r = simpleFooRequest();
+    SimpleFooProxy f = r.create(SimpleFooProxy.class);
+
+    f.setUserName("user name");
+    f.setByteField((byte) 100);
+    f.setShortField((short) 12345);
+    f.setFloatField(1234.56f);
+    f.setDoubleField(1.2345);
+    f.setLongField(1234L);
+    f.setBoolField(false);
+    f.setOtherBoolField(true);
+    f.setCreated(new Date(397387389L));
+
+    r.persistAndReturnSelf().using(f).fire(new Receiver<SimpleFooProxy>() {
+      @Override
+      public void onSuccess(SimpleFooProxy f) {
+        f = checkSerialization(f);
+        assertEquals("user name", f.getUserName());
+        assertEquals(Byte.valueOf((byte) 100), f.getByteField());
+        assertEquals(Short.valueOf((short) 12345), f.getShortField());
+        assertEquals(0, (int) Math.rint(123456f - 100 * f.getFloatField()));
+        assertEquals(Double.valueOf(1.2345), f.getDoubleField());
+        assertEquals(Long.valueOf(1234L), f.getLongField());
+        assertFalse(f.getBoolField());
+        assertTrue(f.getOtherBoolField());
+        assertEquals(new Date(397387389L), f.getCreated());
+
+        finishTestAndReset();
+      }
+    });
+  }
+
+  /**
+   * Test that the server code will not allow a persisted entity to be returned
+   * if it has a null version property.
+   */
+  public void testPersistedEntityWithNullVersion() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    simpleFooRequest().getSimpleFooWithNullVersion().fire(
+        new Receiver<SimpleFooProxy>() {
+
+          @Override
+          public void onFailure(ServerFailure error) {
+            finishTestAndReset();
+          }
+
+          @Override
+          public void onSuccess(SimpleFooProxy response) {
+            fail();
+          }
+        });
+  }
+
+  public void testPersistExistingEntityExistingRelation() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    // Retrieve a Bar
+    simpleBarRequest().findSimpleBarById("999L").fire(
+        new Receiver<SimpleBarProxy>() {
+          @Override
+          public void onSuccess(SimpleBarProxy response) {
+            final SimpleBarProxy barProxy = checkSerialization(response);
+            // Retrieve a Foo
+            simpleFooRequest().findSimpleFooById(999L).fire(
+                new Receiver<SimpleFooProxy>() {
+                  @Override
+                  public void onSuccess(SimpleFooProxy fooProxy) {
+                    fooProxy = checkSerialization(fooProxy);
+                    SimpleFooRequest context = simpleFooRequest();
+                    fooProxy = context.edit(fooProxy);
+                    // Make the Foo point to the Bar
+                    fooProxy.setBarField(barProxy);
+                    fooProxy.setUserName("Hello");
+                    fooProxy.setByteField((byte) 55);
+                    context.persistAndReturnSelf().using(fooProxy).with(
+                        "barField").fire(new Receiver<SimpleFooProxy>() {
+                      @Override
+                      public void onSuccess(SimpleFooProxy received) {
+                        received = checkSerialization(received);
+                        // Check that Foo points to Bar
+                        assertNotNull(received.getBarField());
+                        assertEquals(barProxy.stableId(),
+                            received.getBarField().stableId());
+                        assertEquals("Hello", received.getUserName());
+                        assertTrue(55 == received.getByteField());
+
+                        // Unset the association
+                        SimpleFooRequest context = simpleFooRequest();
+                        received = context.edit(received);
+                        received.setBarField(null);
+                        received.setUserName(null);
+                        received.setByteField(null);
+                        context.persistAndReturnSelf().using(received).fire(
+                            new Receiver<SimpleFooProxy>() {
+                              @Override
+                              public void onSuccess(SimpleFooProxy response) {
+                                response = checkSerialization(response);
+                                assertNull(response.getBarField());
+                                assertNull(response.getUserName());
+                                assertNull(response.getByteField());
+                                finishTestAndReset();
+                              }
+                            });
+                      }
+                    });
+                  }
+                });
+          }
+        });
+  }
+
+  /*
+   * Find Entity Create Entity2 Relate Entity2 to Entity Persist Entity
+   */
+  public void testPersistExistingEntityNewRelation() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    // Make a new bar
+    SimpleBarRequest context = simpleBarRequest();
+    SimpleBarProxy makeABar = context.create(SimpleBarProxy.class);
+    Request<SimpleBarProxy> persistRequest = context.persistAndReturnSelf().using(
+        makeABar);
+    makeABar = context.edit(makeABar);
+    makeABar.setUserName("Amit");
+
+    persistRequest.fire(new Receiver<SimpleBarProxy>() {
+      @Override
+      public void onSuccess(SimpleBarProxy response) {
+        final SimpleBarProxy persistedBar = checkSerialization(response);
+
+        // It was made, now find a foo to assign it to
+        simpleFooRequest().findSimpleFooById(999L).fire(
+            new Receiver<SimpleFooProxy>() {
+              @Override
+              public void onSuccess(SimpleFooProxy response) {
+                response = checkSerialization(response);
+
+                // Found the foo, edit it
+                SimpleFooRequest context = simpleFooRequest();
+                Request<Void> fooReq = context.persist().using(response);
+                response = context.edit(response);
+                response.setBarField(persistedBar);
+                fooReq.fire(new Receiver<Void>() {
+                  @Override
+                  public void onSuccess(Void response) {
+
+                    // Foo was persisted, fetch it again check the goods
+                    simpleFooRequest().findSimpleFooById(999L).with(
+                        "barField.userName").fire(
+                        new Receiver<SimpleFooProxy>() {
+
+                          // Here it is
+                          @Override
+                          public void onSuccess(SimpleFooProxy finalFooProxy) {
+                            finalFooProxy = checkSerialization(finalFooProxy);
+                            assertEquals("Amit",
+                                finalFooProxy.getBarField().getUserName());
+                            finishTestAndReset();
+                          }
+                        });
+                  }
+                });
+              }
+            });
+      }
+    });
+  }
+
+  /*
+   * TODO: all these tests should check the final values. It will be easy when
+   * we have better persistence than the singleton pattern.
+   */
+
+  /**
+   * Ensure that a relationship can be set up between two newly-created objects.
+   */
+  public void testPersistFutureToFuture() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    SimpleFooRequest context = simpleFooRequest();
+    SimpleFooProxy newFoo = context.create(SimpleFooProxy.class);
+    final SimpleBarProxy newBar = context.create(SimpleBarProxy.class);
+
+    Request<SimpleFooProxy> fooReq = context.persistAndReturnSelf().using(
+        newFoo).with("barField");
+    newFoo = context.edit(newFoo);
+    newFoo.setBarField(newBar);
+
+    fooReq.fire(new Receiver<SimpleFooProxy>() {
+      @Override
+      public void onSuccess(SimpleFooProxy response) {
+        response = checkSerialization(response);
+        assertNotNull(response.getBarField());
+        assertEquals(newBar.stableId(), response.getBarField().stableId());
+        finishTestAndReset();
+      }
+    });
+  }
+
+  /*
+   * Find Entity2 Create Entity, Persist Entity Relate Entity2 to Entity Persist
+   * Entity
+   */
+  public void testPersistNewEntityExistingRelation() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    SimpleFooRequest context = simpleFooRequest();
+    SimpleFooProxy newFoo = context.edit(context.create(SimpleFooProxy.class));
+    final Request<Void> fooReq = context.persist().using(newFoo);
+
+    newFoo.setUserName("Ray");
+
+    final SimpleFooProxy finalFoo = newFoo;
+    simpleBarRequest().findSimpleBarById("999L").fire(
+        new Receiver<SimpleBarProxy>() {
+          @Override
+          public void onSuccess(SimpleBarProxy response) {
+            response = checkSerialization(response);
+            finalFoo.setBarField(response);
+            fooReq.fire(new Receiver<Void>() {
+              @Override
+              public void onSuccess(Void response) {
+                simpleFooRequest().findSimpleFooById(999L).fire(
+                    new Receiver<SimpleFooProxy>() {
+                      @Override
+                      public void onSuccess(SimpleFooProxy finalFooProxy) {
+                        finalFooProxy = checkSerialization(finalFooProxy);
+                        // newFoo hasn't been persisted, so userName is the old
+                        // value.
+                        assertEquals("GWT", finalFooProxy.getUserName());
+                        finishTestAndReset();
+                      }
+                    });
+              }
+            });
+          }
+        });
+  }
+
+  /*
+   * Create Entity, Persist Entity Create Entity2, Perist Entity2 relate Entity2
+   * to Entity Persist
+   */
+  public void testPersistNewEntityNewRelation() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    SimpleFooRequest context = simpleFooRequest();
+    SimpleFooProxy newFoo = context.create(SimpleFooProxy.class);
+
+    final Request<SimpleFooProxy> fooReq = context.persistAndReturnSelf().using(
+        newFoo);
+    newFoo = context.edit(newFoo);
+    newFoo.setUserName("Ray");
+
+    SimpleBarRequest context2 = simpleBarRequest();
+    SimpleBarProxy newBar = context2.create(SimpleBarProxy.class);
+    final Request<SimpleBarProxy> barReq = context2.persistAndReturnSelf().using(
+        newBar);
+    newBar = context2.edit(newBar);
+    newBar.setUserName("Amit");
+
+    fooReq.fire(new Receiver<SimpleFooProxy>() {
+      @Override
+      public void onSuccess(final SimpleFooProxy response) {
+        final SimpleFooProxy persistedFoo = checkSerialization(response);
+        barReq.fire(new Receiver<SimpleBarProxy>() {
+          @Override
+          public void onSuccess(SimpleBarProxy response) {
+            final SimpleBarProxy persistedBar = checkSerialization(response);
+
+            assertEquals("Ray", persistedFoo.getUserName());
+            SimpleFooRequest context = simpleFooRequest();
+            final Request<Void> fooReq2 = context.persist().using(persistedFoo);
+            SimpleFooProxy editablePersistedFoo = context.edit(persistedFoo);
+            editablePersistedFoo.setBarField(persistedBar);
+            fooReq2.fire(new Receiver<Void>() {
+              @Override
+              public void onSuccess(Void response) {
+                req.simpleFooRequest().findSimpleFooById(persistedFoo.getId()).with(
+                    "barField.userName").fire(new Receiver<SimpleFooProxy>() {
+                  @Override
+                  public void onSuccess(SimpleFooProxy finalFooProxy) {
+                    finalFooProxy = checkSerialization(finalFooProxy);
+                    assertEquals("Amit",
+                        finalFooProxy.getBarField().getUserName());
+                    finishTestAndReset();
+                  }
+                });
+              }
+            });
+          }
+        });
+      }
+    });
+  }
+
+  /*
+   * TODO: all these tests should check the final values. It will be easy when
+   * we have better persistence than the singleton pattern.
+   */
+  public void testPersistOneToManyExistingEntityExistingRelation() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    simpleBarRequest().findSimpleBarById("999L").fire(
+        new Receiver<SimpleBarProxy>() {
+          @Override
+          public void onSuccess(SimpleBarProxy response) {
+            final SimpleBarProxy barProxy = checkSerialization(response);
+            simpleFooRequest().findSimpleFooById(999L).with("oneToManyField").fire(
+                new Receiver<SimpleFooProxy>() {
+                  @Override
+                  public void onSuccess(SimpleFooProxy fooProxy) {
+                    fooProxy = checkSerialization(fooProxy);
+
+                    SimpleFooRequest context = simpleFooRequest();
+                    Request<SimpleFooProxy> updReq = context.persistAndReturnSelf().using(
+                        fooProxy).with("oneToManyField");
+                    fooProxy = context.edit(fooProxy);
+
+                    List<SimpleBarProxy> barProxyList = fooProxy.getOneToManyField();
+                    final int listCount = barProxyList.size();
+                    barProxyList.add(barProxy);
+                    updReq.fire(new Receiver<SimpleFooProxy>() {
+                      @Override
+                      public void onSuccess(SimpleFooProxy response) {
+                        response = checkSerialization(response);
+                        assertEquals(response.getOneToManyField().size(),
+                            listCount + 1);
+                        assertContains(response.getOneToManyField(), barProxy);
+                        finishTestAndReset();
+                      }
+                    });
+                  }
+                });
+          }
+        });
+  }
+
+  public void testPersistRecursiveRelation() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    SimpleFooRequest context = simpleFooRequest();
+    SimpleFooProxy rayFoo = context.create(SimpleFooProxy.class);
+    final Request<SimpleFooProxy> persistRay = context.persistAndReturnSelf().using(
+        rayFoo);
+    rayFoo = context.edit(rayFoo);
+    rayFoo.setUserName("Ray");
+    rayFoo.setFooField(rayFoo);
+    persistRay.fire(new Receiver<SimpleFooProxy>() {
+      @Override
+      public void onSuccess(SimpleFooProxy response) {
+        response = checkSerialization(response);
+        finishTestAndReset();
+      }
+    });
+  }
+
+  public void testPersistRelation() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    SimpleFooRequest context = simpleFooRequest();
+    SimpleFooProxy rayFoo = context.create(SimpleFooProxy.class);
+    final Request<SimpleFooProxy> persistRay = context.persistAndReturnSelf().using(
+        rayFoo);
+    rayFoo = context.edit(rayFoo);
+    rayFoo.setUserName("Ray");
+
+    persistRay.fire(new Receiver<SimpleFooProxy>() {
+      @Override
+      public void onSuccess(SimpleFooProxy response) {
+        final SimpleFooProxy persistedRay = checkSerialization(response);
+        SimpleBarRequest context = simpleBarRequest();
+        SimpleBarProxy amitBar = context.create(SimpleBarProxy.class);
+        final Request<SimpleBarProxy> persistAmit = context.persistAndReturnSelf().using(
+            amitBar);
+        amitBar = context.edit(amitBar);
+        amitBar.setUserName("Amit");
+
+        persistAmit.fire(new Receiver<SimpleBarProxy>() {
+          @Override
+          public void onSuccess(SimpleBarProxy response) {
+            response = checkSerialization(response);
+
+            SimpleFooRequest context = simpleFooRequest();
+            final Request<SimpleFooProxy> persistRelationship = context.persistAndReturnSelf().using(
+                persistedRay).with("barField");
+            SimpleFooProxy newRec = context.edit(persistedRay);
+            newRec.setBarField(response);
+
+            persistRelationship.fire(new Receiver<SimpleFooProxy>() {
+              @Override
+              public void onSuccess(SimpleFooProxy response) {
+                response = checkSerialization(response);
+                assertEquals("Amit", response.getBarField().getUserName());
+                finishTestAndReset();
+              }
+            });
+          }
+        });
+      }
+    });
+  }
+
+  public void testPersistSelfOneToManyExistingEntityExistingRelation() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    simpleFooRequest().findSimpleFooById(999L).with("selfOneToManyField").fire(
+        new Receiver<SimpleFooProxy>() {
+          @Override
+          public void onSuccess(SimpleFooProxy fooProxy) {
+            fooProxy = checkSerialization(fooProxy);
+            SimpleFooRequest context = simpleFooRequest();
+            Request<SimpleFooProxy> updReq = context.persistAndReturnSelf().using(
+                fooProxy).with("selfOneToManyField");
+            fooProxy = context.edit(fooProxy);
+            List<SimpleFooProxy> fooProxyList = fooProxy.getSelfOneToManyField();
+            final int listCount = fooProxyList.size();
+            fooProxyList.add(fooProxy);
+            updReq.fire(new Receiver<SimpleFooProxy>() {
+              @Override
+              public void onSuccess(SimpleFooProxy response) {
+                response = checkSerialization(response);
+                assertEquals(response.getSelfOneToManyField().size(),
+                    listCount + 1);
+                assertContains(response.getSelfOneToManyField(), response);
+                finishTestAndReset();
+              }
+            });
+          }
+        });
+  }
+
+  /*
+   * TODO: all these tests should check the final values. It will be easy when
+   * we have better persistence than the singleton pattern.
+   */
+
+  public void testPersistValueList() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    simpleFooRequest().findSimpleFooById(999L).fire(
+        new Receiver<SimpleFooProxy>() {
+          @Override
+          public void onSuccess(SimpleFooProxy fooProxy) {
+            fooProxy = checkSerialization(fooProxy);
+            SimpleFooRequest context = simpleFooRequest();
+            Request<SimpleFooProxy> updReq = context.persistAndReturnSelf().using(
+                fooProxy);
+            fooProxy = context.edit(fooProxy);
+            fooProxy.getNumberListField().add(100);
+            updReq.fire(new Receiver<SimpleFooProxy>() {
+              @Override
+              public void onSuccess(SimpleFooProxy response) {
+                response = checkSerialization(response);
+                assertTrue(response.getNumberListField().contains(100));
+                finishTestAndReset();
+              }
+            });
+          }
+        });
+  }
+
+  /*
+   * TODO: all these tests should check the final values. It will be easy when
+   * we have better persistence than the singleton pattern.
+   */
+
+  /*
+   * TODO: all these tests should check the final values. It will be easy when
+   * we have better persistence than the singleton pattern.
+   */
+  public void testPersistValueListNull() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    simpleFooRequest().findSimpleFooById(999L).fire(
+        new Receiver<SimpleFooProxy>() {
+          @Override
+          public void onSuccess(SimpleFooProxy fooProxy) {
+            fooProxy = checkSerialization(fooProxy);
+            SimpleFooRequest context = simpleFooRequest();
+            Request<SimpleFooProxy> updReq = context.persistAndReturnSelf().using(
+                fooProxy);
+            fooProxy = context.edit(fooProxy);
+
+            fooProxy.setNumberListField(null);
+            updReq.fire(new Receiver<SimpleFooProxy>() {
+              @Override
+              public void onSuccess(SimpleFooProxy response) {
+                response = checkSerialization(response);
+                List<Integer> list = response.getNumberListField();
+                assertNull(list);
+                finishTestAndReset();
+              }
+            });
+          }
+        });
+  }
+
+  /*
+   * TODO: all these tests should check the final values. It will be easy when
+   * we have better persistence than the singleton pattern.
+   */
+  public void testPersistValueListRemove() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    simpleFooRequest().findSimpleFooById(999L).fire(
+        new Receiver<SimpleFooProxy>() {
+          @Override
+          public void onSuccess(SimpleFooProxy fooProxy) {
+            fooProxy = checkSerialization(fooProxy);
+            SimpleFooRequest context = simpleFooRequest();
+            Request<SimpleFooProxy> updReq = context.persistAndReturnSelf().using(
+                fooProxy);
+            fooProxy = context.edit(fooProxy);
+            final int oldValue = fooProxy.getNumberListField().remove(0);
+            updReq.fire(new Receiver<SimpleFooProxy>() {
+              @Override
+              public void onSuccess(SimpleFooProxy response) {
+                response = checkSerialization(response);
+                assertFalse(response.getNumberListField().contains(oldValue));
+                finishTestAndReset();
+              }
+            });
+          }
+        });
+  }
+
+  /*
+   * TODO: all these tests should check the final values. It will be easy when
+   * we have better persistence than the singleton pattern.
+   */
+  public void testPersistValueListReplace() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    simpleFooRequest().findSimpleFooById(999L).fire(
+        new Receiver<SimpleFooProxy>() {
+          @Override
+          public void onSuccess(SimpleFooProxy fooProxy) {
+            fooProxy = checkSerialization(fooProxy);
+            SimpleFooRequest context = simpleFooRequest();
+            Request<SimpleFooProxy> updReq = context.persistAndReturnSelf().using(
+                fooProxy);
+            fooProxy = context.edit(fooProxy);
+            final ArrayList<Integer> al = new ArrayList<Integer>();
+            al.add(5);
+            al.add(8);
+            al.add(13);
+            fooProxy.setNumberListField(al);
+            updReq.fire(new Receiver<SimpleFooProxy>() {
+              @Override
+              public void onSuccess(SimpleFooProxy response) {
+                response = checkSerialization(response);
+                List<Integer> list = response.getNumberListField();
+                assertEquals(5, (int) list.get(0));
+                assertEquals(8, (int) list.get(1));
+                assertEquals(13, (int) list.get(2));
+                finishTestAndReset();
+              }
+            });
+          }
+        });
+  }
+
+  /*
+   * TODO: all these tests should check the final values. It will be easy when
+   * we have better persistence than the singleton pattern.
+   */
+  public void testPersistValueListReverse() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    simpleFooRequest().findSimpleFooById(999L).fire(
+        new Receiver<SimpleFooProxy>() {
+          @Override
+          public void onSuccess(SimpleFooProxy fooProxy) {
+            fooProxy = checkSerialization(fooProxy);
+            SimpleFooRequest context = simpleFooRequest();
+            Request<SimpleFooProxy> updReq = context.persistAndReturnSelf().using(
+                fooProxy);
+            fooProxy = context.edit(fooProxy);
+            final ArrayList<Integer> al = new ArrayList<Integer>();
+            List<Integer> listField = fooProxy.getNumberListField();
+            al.addAll(listField);
+            Collections.reverse(listField);
+            updReq.fire(new Receiver<SimpleFooProxy>() {
+              @Override
+              public void onSuccess(SimpleFooProxy response) {
+                response = checkSerialization(response);
+                Collections.reverse(al);
+                assertTrue(response.getNumberListField().equals(al));
+                finishTestAndReset();
+              }
+            });
+          }
+        });
+  }
+
+  /*
+   * TODO: all these tests should check the final values. It will be easy when
+   * we have better persistence than the singleton pattern.
+   */
+  public void testPersistValueListSetIndex() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    simpleFooRequest().findSimpleFooById(999L).fire(
+        new Receiver<SimpleFooProxy>() {
+          @Override
+          public void onSuccess(SimpleFooProxy fooProxy) {
+            fooProxy = checkSerialization(fooProxy);
+            SimpleFooRequest context = simpleFooRequest();
+            Request<SimpleFooProxy> updReq = context.persistAndReturnSelf().using(
+                fooProxy);
+            fooProxy = context.edit(fooProxy);
+            fooProxy.getNumberListField().set(0, 10);
+            updReq.fire(new Receiver<SimpleFooProxy>() {
+              @Override
+              public void onSuccess(SimpleFooProxy response) {
+                response = checkSerialization(response);
+                assertTrue(response.getNumberListField().get(0) == 10);
+                finishTestAndReset();
+              }
+            });
+          }
+        });
+  }
+
+  /*
+   * TODO: all these tests should check the final values. It will be easy when
+   * we have better persistence than the singleton pattern.
+   */
+  public void testPersistValueSetAddNew() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    SimpleBarRequest context = simpleBarRequest();
+    SimpleBarProxy newBar = context.create(SimpleBarProxy.class);
+
+    context.persistAndReturnSelf().using(newBar).fire(
+        new Receiver<SimpleBarProxy>() {
+          @Override
+          public void onSuccess(SimpleBarProxy response) {
+            final SimpleBarProxy barProxy = checkSerialization(response);
+            simpleFooRequest().findSimpleFooById(999L).with("oneToManySetField").fire(
+                new Receiver<SimpleFooProxy>() {
+                  @Override
+                  public void onSuccess(SimpleFooProxy fooProxy) {
+                    fooProxy = checkSerialization(fooProxy);
+                    SimpleFooRequest context = simpleFooRequest();
+                    Request<SimpleFooProxy> updReq = context.persistAndReturnSelf().using(
+                        fooProxy).with("oneToManySetField");
+                    fooProxy = context.edit(fooProxy);
+
+                    Set<SimpleBarProxy> setField = fooProxy.getOneToManySetField();
+                    final int listCount = setField.size();
+                    setField.add(barProxy);
+                    updReq.fire(new Receiver<SimpleFooProxy>() {
+                      @Override
+                      public void onSuccess(SimpleFooProxy response) {
+                        response = checkSerialization(response);
+                        assertEquals(listCount + 1,
+                            response.getOneToManySetField().size());
+                        assertContains(response.getOneToManySetField(),
+                            barProxy);
+                        finishTestAndReset();
+                      }
+                    });
+                  }
+                });
+          }
+        });
+  }
+
+  /*
+   * TODO: all these tests should check the final values. It will be easy when
+   * we have better persistence than the singleton pattern.
+   */
+  public void testPersistValueSetAlreadyExists() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    simpleBarRequest().findSimpleBarById("1L").fire(
+        new Receiver<SimpleBarProxy>() {
+          @Override
+          public void onSuccess(SimpleBarProxy response) {
+            final SimpleBarProxy barProxy = checkSerialization(response);
+
+            simpleFooRequest().findSimpleFooById(999L).with("oneToManySetField").fire(
+                new Receiver<SimpleFooProxy>() {
+                  @Override
+                  public void onSuccess(SimpleFooProxy fooProxy) {
+                    fooProxy = checkSerialization(fooProxy);
+                    SimpleFooRequest context = simpleFooRequest();
+                    Request<SimpleFooProxy> updReq = context.persistAndReturnSelf().using(
+                        fooProxy).with("oneToManySetField");
+                    fooProxy = context.edit(fooProxy);
+
+                    Set<SimpleBarProxy> setField = fooProxy.getOneToManySetField();
+                    final int listCount = setField.size();
+                    assertContains(setField, barProxy);
+                    setField.add(barProxy);
+                    updReq.fire(new Receiver<SimpleFooProxy>() {
+                      @Override
+                      public void onSuccess(SimpleFooProxy response) {
+                        response = checkSerialization(response);
+                        assertEquals(response.getOneToManySetField().size(),
+                            listCount);
+                        assertContains(response.getOneToManySetField(),
+                            barProxy);
+                        finishTestAndReset();
+                      }
+                    });
+                  }
+                });
+          }
+        });
+  }
+
+  /*
+   * TODO: all these tests should check the final values. It will be easy when
+   * we have better persistence than the singleton pattern.
+   */
+  public void testPersistValueSetRemove() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    simpleBarRequest().findSimpleBarById("1L").fire(
+        new Receiver<SimpleBarProxy>() {
+          @Override
+          public void onSuccess(SimpleBarProxy response) {
+            final SimpleBarProxy barProxy = checkSerialization(response);
+            simpleFooRequest().findSimpleFooById(999L).with("oneToManySetField").fire(
+                new Receiver<SimpleFooProxy>() {
+                  @Override
+                  public void onSuccess(SimpleFooProxy fooProxy) {
+                    fooProxy = checkSerialization(fooProxy);
+                    SimpleFooRequest context = simpleFooRequest();
+                    Request<SimpleFooProxy> updReq = context.persistAndReturnSelf().using(
+                        fooProxy).with("oneToManySetField");
+                    fooProxy = context.edit(fooProxy);
+
+                    Set<SimpleBarProxy> setField = fooProxy.getOneToManySetField();
+                    final int listCount = setField.size();
+                    assertContains(setField, barProxy);
+                    setField.remove(context.edit(barProxy));
+                    assertNotContains(setField, barProxy);
+                    updReq.fire(new Receiver<SimpleFooProxy>() {
+                      @Override
+                      public void onSuccess(SimpleFooProxy response) {
+                        response = checkSerialization(response);
+                        assertEquals(listCount - 1,
+                            response.getOneToManySetField().size());
+                        assertNotContains(response.getOneToManySetField(),
+                            barProxy);
+                        finishTestAndReset();
+                      }
+                    });
+                  }
+                });
+          }
+        });
+  }
+
+  public void testPrimitiveList() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    final Request<List<Integer>> fooReq = simpleFooRequest().getNumberList();
+    fooReq.fire(new Receiver<List<Integer>>() {
+      @Override
+      public void onSuccess(List<Integer> response) {
+        assertEquals(3, response.size());
+        assertEquals(1, (int) response.get(0));
+        assertEquals(2, (int) response.get(1));
+        assertEquals(3, (int) response.get(2));
+        finishTestAndReset();
+      }
+    });
+  }
+
+  public void testPrimitiveListAsParameter() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    final Request<SimpleFooProxy> fooReq = simpleFooRequest().findSimpleFooById(
+        999L);
+    fooReq.fire(new Receiver<SimpleFooProxy>() {
+      @Override
+      public void onSuccess(SimpleFooProxy response) {
+        response = checkSerialization(response);
+        final Request<Integer> sumReq = simpleFooRequest().sum(
+            Arrays.asList(1, 2, 3)).using(response);
+        sumReq.fire(new Receiver<Integer>() {
+          @Override
+          public void onSuccess(Integer response) {
+            assertEquals(6, response.intValue());
+            finishTestAndReset();
+          }
+        });
+      }
+    });
+  }
+
+  public void testPrimitiveListBigDecimalAsParameter() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    // Keep these values in sync with SimpleFoo.processBigIntegerList
+    final List<BigDecimal> testList = new ArrayList<BigDecimal>();
+    testList.add(BigDecimal.TEN);
+    testList.add(new BigDecimal("12345.6789") {
+      // This is an anonymous subtype
+    });
+    simpleFooRequest().processBigDecimalList(testList).fire(
+        new Receiver<List<BigDecimal>>() {
+          @Override
+          public void onSuccess(List<BigDecimal> response) {
+            // Check upcasted values only
+            assertEquals(testList, response);
+            finishTestAndReset();
+          }
+        });
+  }
+
+  public void testPrimitiveListBigIntegerAsParameter() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    // Keep these values in sync with SimpleFoo.processBigIntegerList
+    final List<BigInteger> testList = new ArrayList<BigInteger>();
+    testList.add(BigInteger.TEN);
+    testList.add(new BigInteger("12345") {
+      // This is an anonymous subtype
+    });
+    simpleFooRequest().processBigIntegerList(testList).fire(
+        new Receiver<List<BigInteger>>() {
+          @Override
+          public void onSuccess(List<BigInteger> response) {
+            // Check upcasted values only
+            assertEquals(testList, response);
+            finishTestAndReset();
+          }
+        });
+  }
+
+  public void testPrimitiveListBooleanAsParameter() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    Request<Boolean> procReq = simpleFooRequest().processBooleanList(
+        Arrays.asList(true, false));
+
+    procReq.fire(new Receiver<Boolean>() {
+      @Override
+      public void onSuccess(Boolean response) {
+        assertEquals(true, (boolean) response);
+        finishTestAndReset();
+      }
+    });
+  }
+
+  @SuppressWarnings("deprecation")
+  public void testPrimitiveListDateAsParameter() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    // Keep these values in sync with SimpleFoo.processDateList
+    Date date = new Date(90, 0, 1);
+    java.sql.Date sqlDate = new java.sql.Date(90, 0, 2);
+    Time sqlTime = new Time(1, 2, 3);
+    Timestamp sqlTimestamp = new Timestamp(12345L);
+    final List<Date> testList = Arrays.asList(date, sqlDate, sqlTime,
+        sqlTimestamp);
+    simpleFooRequest().processDateList(testList).fire(
+        new Receiver<List<Date>>() {
+          @Override
+          public void onSuccess(List<Date> response) {
+            // Check upcasted values only
+            assertEquals(testList.size(), response.size());
+            Iterator<Date> expected = testList.iterator();
+            Iterator<Date> actual = response.iterator();
+            while (expected.hasNext()) {
+              assertEquals(expected.next().getTime(), actual.next().getTime());
+            }
+            finishTestAndReset();
+          }
+        });
+  }
+
+  public void testPrimitiveListEnumAsParameter() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    Request<SimpleEnum> procReq = simpleFooRequest().processEnumList(
+        Arrays.asList(SimpleEnum.BAR));
+
+    procReq.fire(new Receiver<SimpleEnum>() {
+      @Override
+      public void onSuccess(SimpleEnum response) {
+        assertEquals(SimpleEnum.BAR, response);
+        finishTestAndReset();
+      }
+    });
+  }
+
+  public void testPrimitiveParameter() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    simpleFooRequest().add(3, 5).fire(new Receiver<Integer>() {
+      @Override
+      public void onSuccess(Integer response) {
+        assertTrue(8 == response);
+        finishTestAndReset();
+      }
+    });
+  }
+
+  public void testPrimitiveSet() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    final Request<Set<Integer>> fooReq = simpleFooRequest().getNumberSet();
+    fooReq.fire(new Receiver<Set<Integer>>() {
+      @Override
+      public void onSuccess(Set<Integer> response) {
+        assertEquals(3, response.size());
+        assertTrue(response.contains(1));
+        assertTrue(response.contains(2));
+        assertTrue(response.contains(3));
+        finishTestAndReset();
+      }
+    });
+  }
+
+  public void testPrimitiveString() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    final String testString = "test\"string\'with\nstring\u2060characters\t";
+    final Request<String> fooReq = simpleFooRequest().processString(testString);
+    fooReq.fire(new Receiver<String>() {
+      @Override
+      public void onSuccess(String response) {
+        assertEquals(testString, response);
+        finishTestAndReset();
+      }
+    });
+  }
+
+  public void testProxyList() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    final Request<SimpleFooProxy> fooReq = simpleFooRequest().findSimpleFooById(
+        999L).with("oneToManyField");
+    fooReq.fire(new Receiver<SimpleFooProxy>() {
+      @Override
+      public void onSuccess(SimpleFooProxy response) {
+        response = checkSerialization(response);
+        assertEquals(2, response.getOneToManyField().size());
+
+        // Check lists of proxies returned from a mutable object are mutable
+        response = simpleFooRequest().edit(response);
+        response.getOneToManyField().get(0).setUserName("canMutate");
+        finishTestAndReset();
+      }
+    });
+  }
+
+  public void testProxyListAsParameter() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    final Request<SimpleFooProxy> fooReq = simpleFooRequest().findSimpleFooById(
+        999L).with("selfOneToManyField");
+    fooReq.fire(new Receiver<SimpleFooProxy>() {
+      @Override
+      public void onSuccess(SimpleFooProxy response) {
+        final SimpleFooProxy fooProxy = checkSerialization(response);
+        final Request<String> procReq = simpleFooRequest().processList(
+            fooProxy.getSelfOneToManyField()).using(fooProxy);
+        procReq.fire(new Receiver<String>() {
+          @Override
+          public void onSuccess(String response) {
+            assertEquals(fooProxy.getUserName(), response);
+            finishTestAndReset();
+          }
+        });
+      }
+    });
+  }
+
+  public void testProxysAsInstanceMethodParams() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    simpleFooRequest().findSimpleFooById(999L).fire(
+        new Receiver<SimpleFooProxy>() {
+          @Override
+          public void onSuccess(SimpleFooProxy response) {
+            response = checkSerialization(response);
+            SimpleFooRequest context = simpleFooRequest();
+            SimpleBarProxy bar = context.create(SimpleBarProxy.class);
+            Request<String> helloReq = context.hello(bar).using(response);
+            bar = context.edit(bar);
+            bar.setUserName("BAR");
+            helloReq.fire(new Receiver<String>() {
+              @Override
+              public void onSuccess(String response) {
+                assertEquals("Greetings BAR from GWT", response);
+                finishTestAndReset();
+              }
+            });
+          }
+        });
+  }
+
+  public void testServerFailureCheckedException() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    SimpleFooRequest context = simpleFooRequest();
+    SimpleFooProxy newFoo = context.create(SimpleFooProxy.class);
+    final Request<SimpleFooProxy> persistRequest = context.persistAndReturnSelf().using(
+        newFoo);
+    final SimpleFooProxy mutableFoo = context.edit(newFoo);
+    // 43 is the crash causing magic number for a checked exception
+    mutableFoo.setPleaseCrash(43);
+    persistRequest.fire(new FooReciever(mutableFoo, persistRequest, null));
+  }
+
+  public void testServerFailureRuntimeException() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    SimpleFooRequest context = simpleFooRequest();
+    SimpleFooProxy newFoo = context.create(SimpleFooProxy.class);
+    final Request<SimpleFooProxy> persistRequest = context.persistAndReturnSelf().using(
+        newFoo);
+    final SimpleFooProxy mutableFoo = context.edit(newFoo);
+    // 42 is the crash causing magic number for a runtime exception
+    mutableFoo.setPleaseCrash(42);
+    persistRequest.fire(new FooReciever(mutableFoo, persistRequest, null));
+  }
+
+  /**
+   * Tests the behaviors of setters and their effects on getters.
+   */
+  public void testSetters() {
+    SimpleFooRequest context = simpleFooRequest();
+    SimpleFooProxy foo = context.create(SimpleFooProxy.class);
+    SimpleBarProxy bar = context.create(SimpleBarProxy.class);
+
+    // Assert that uninitalize references are null
+    assertNull(foo.getBarField());
+
+    // Assert that objects are mutable after creation
+    foo.setBarField(bar);
+
+    assertSame(foo, context.edit(foo));
+    foo.setBarField(bar);
+
+    // Assert that the set value is retained
+    SimpleBarProxy returnedBarField = foo.getBarField();
+    assertNotNull(returnedBarField);
+    assertEquals(bar.stableId(), returnedBarField.stableId());
+    assertEquals(returnedBarField, foo.getBarField());
+    assertSame(returnedBarField, foo.getBarField());
+
+    // Getters called on mutable objects are also mutable
+    returnedBarField.setUserName("userName");
+    assertEquals("userName", returnedBarField.getUserName());
+  }
+
+  /**
+   * There's plenty of special-case code for Collection properties, so they need
+   * to be tested as well.
+   */
+  public void testSettersWithCollections() {
+    SimpleFooRequest context = simpleFooRequest();
+    SimpleFooProxy foo = context.create(SimpleFooProxy.class);
+    SimpleBarProxy bar = context.create(SimpleBarProxy.class);
+    List<SimpleBarProxy> originalList = Collections.singletonList(bar);
+
+    // Assert that uninitalize references are null
+    assertNull(foo.getOneToManyField());
+
+    // Assert that objects are mutable after creation
+    foo.setOneToManyField(null);
+
+    assertSame(foo, context.edit(foo));
+    foo.setOneToManyField(originalList);
+    // There's a "dummy" create case here; AbstractRequest, DVS is untestable
+
+    // Quick sanity check on the behavior
+    List<SimpleBarProxy> list = foo.getOneToManyField();
+    assertNotSame(originalList, list);
+    assertEquals(originalList, list);
+    assertEquals(1, list.size());
+    assertEquals(bar.stableId(), list.get(0).stableId());
+    assertEquals(list, foo.getOneToManyField());
+
+    // Assert that entities returned from editable list are mutable
+    list.get(0).setUserName("userName");
+  }
+
+  public void testSettersWithMutableObject() {
+    SimpleFooRequest context = simpleFooRequest();
+    SimpleFooProxy foo = context.create(SimpleFooProxy.class);
+    foo = context.edit(foo);
+
+    SimpleBarProxy immutableBar = context.create(SimpleBarProxy.class);
+    SimpleBarProxy mutableBar = context.edit(immutableBar);
+    mutableBar.setUserName("userName");
+    foo.setBarField(mutableBar);
+
+    // Creating a new editable object in the same request should read through
+    context.edit(immutableBar).setUserName("Reset");
+    assertEquals("Reset", foo.getBarField().getUserName());
+  }
+
+  public void testStableId() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    SimpleFooRequest context = simpleFooRequest();
+    final SimpleFooProxy foo = context.create(SimpleFooProxy.class);
+    final Object futureId = foo.getId();
+    assertTrue(((SimpleEntityProxyId<?>) foo.stableId()).isEphemeral());
+    Request<SimpleFooProxy> fooReq = context.persistAndReturnSelf().using(foo);
+
+    final SimpleFooProxy newFoo = context.edit(foo);
+    assertEquals(futureId, foo.getId());
+    assertTrue(((SimpleEntityProxyId<?>) foo.stableId()).isEphemeral());
+    assertEquals(futureId, newFoo.getId());
+    assertTrue(((SimpleEntityProxyId<?>) newFoo.stableId()).isEphemeral());
+
+    newFoo.setUserName("GWT basic user");
+    fooReq.fire(new Receiver<SimpleFooProxy>() {
+
+      @Override
+      public void onSuccess(SimpleFooProxy response) {
+        final SimpleFooProxy returned = checkSerialization(response);
+        assertEquals(futureId, foo.getId());
+        assertFalse(((SimpleEntityProxyId<?>) foo.stableId()).isEphemeral());
+        assertEquals(futureId, newFoo.getId());
+        assertFalse(((SimpleEntityProxyId<?>) newFoo.stableId()).isEphemeral());
+
+        assertFalse(((SimpleEntityProxyId<?>) returned.stableId()).isEphemeral());
+
+        checkStableIdEquals(foo, returned);
+        checkStableIdEquals(newFoo, returned);
+        SimpleFooRequest context = simpleFooRequest();
+        Request<SimpleFooProxy> editRequest = context.persistAndReturnSelf().using(
+            returned);
+        final SimpleFooProxy editableFoo = context.edit(returned);
+        editableFoo.setUserName("GWT power user");
+        editRequest.fire(new Receiver<SimpleFooProxy>() {
+
+          @Override
+          public void onSuccess(SimpleFooProxy response) {
+            response = checkSerialization(response);
+            assertEquals(response.getId(), returned.getId());
+            assertEquals("GWT power user", response.getUserName());
+            checkStableIdEquals(editableFoo, response);
+            finishTestAndReset();
+          }
+        });
+      }
+    });
+  }
+
+  /**
+   * Test that a proxy only referenced via a parameterization is available.
+   */
+  public void testOnlyUsedInList() {
+    OnlyUsedInListProxy proxy = simpleFooRequest().create(
+        OnlyUsedInListProxy.class);
+    assertNotNull(proxy);
+  }
+
+  /**
+   * Check if a graph of unpersisted objects can be echoed.
+   */
+  public void testUnpersistedEchoComplexGraph() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    final SimpleFooEventHandler<SimpleFooProxy> handler = new SimpleFooEventHandler<SimpleFooProxy>();
+    EntityProxyChange.registerForProxyType(req.getEventBus(),
+        SimpleFooProxy.class, handler);
+    SimpleFooRequest context = req.simpleFooRequest();
+    final SimpleBarProxy simpleBar = context.create(SimpleBarProxy.class);
+    simpleBar.setUnpersisted(true);
+    final SimpleFooProxy simpleFoo = context.create(SimpleFooProxy.class);
+    simpleFoo.setUnpersisted(true);
+    simpleFoo.setBarField(simpleBar);
+    context.echoComplex(simpleFoo, simpleBar).with("barField").fire(
+        new Receiver<SimpleFooProxy>() {
+          @Override
+          public void onSuccess(SimpleFooProxy response) {
+            // The reconstituted object may not have the same stable id
+            checkStableIdEquals(simpleBar, response.getBarField());
+            response = checkSerialization(response);
+            assertEquals(0, handler.totalEventCount);
+            checkStableIdEquals(simpleFoo, response);
+            SimpleBarProxy responseBar = response.getBarField();
+            assertNotNull(responseBar);
+            finishTestAndReset();
+          }
+        });
+  }
+
+  /**
+   * Check if an unpersisted object can be echoed.
+   */
+  public void testUnpersistedEchoObject() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    final SimpleFooEventHandler<SimpleFooProxy> handler = new SimpleFooEventHandler<SimpleFooProxy>();
+    EntityProxyChange.registerForProxyType(req.getEventBus(),
+        SimpleFooProxy.class, handler);
+    SimpleFooRequest context = req.simpleFooRequest();
+    final SimpleFooProxy simpleFoo = context.create(SimpleFooProxy.class);
+    simpleFoo.setUnpersisted(true);
+    context.echo(simpleFoo).fire(new Receiver<SimpleFooProxy>() {
+      @Override
+      public void onSuccess(SimpleFooProxy response) {
+        response = checkSerialization(response);
+        assertEquals(0, handler.totalEventCount);
+        checkStableIdEquals(simpleFoo, response);
+        finishTestAndReset();
+      }
+    });
+  }
+
+  /**
+   * Return an unpersisted object from a service method and echo it.
+   */
+  public void testUnpersistedObjectFetch() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    req.simpleFooRequest().getUnpersistedInstance().fire(
+        new Receiver<SimpleFooProxy>() {
+          @Override
+          public void onSuccess(SimpleFooProxy response) {
+            final SimpleFooProxy created = checkSerialization(response);
+            assertNotNull(created);
+            assertTrue(created.getUnpersisted());
+            req.simpleFooRequest().echo(created).fire(
+                new Receiver<SimpleFooProxy>() {
+                  @Override
+                  public void onSuccess(SimpleFooProxy response) {
+                    response = checkSerialization(response);
+                    assertNotNull(response);
+                    assertEquals(created.stableId(), response.stableId());
+                    assertTrue(response.getUnpersisted());
+                    finishTestAndReset();
+                  }
+                });
+          }
+        });
+  }
+
+  /**
+   * This is analagous to FindServiceTest.testFetchDeletedEntity() only we're
+   * trying to invoke a method on the deleted entity using a stale EntityProxy
+   * reference on the client.
+   */
+  public void testUseOfDeletedEntity() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    SimpleBarRequest context = simpleBarRequest();
+    SimpleBarProxy willDelete = context.create(SimpleBarProxy.class);
+    willDelete.setUserName("A");
+
+    // Persist the newly-created object
+    context.persistAndReturnSelf().using(willDelete).fire(
+        new Receiver<SimpleBarProxy>() {
+          @Override
+          public void onSuccess(SimpleBarProxy response) {
+            response = checkSerialization(response);
+            assertEquals("A", response.getUserName());
+            // Mark the object as deleted
+            SimpleBarRequest context = simpleBarRequest();
+            response = context.edit(response);
+            response.setFindFails(true);
+            response.setUserName("B");
+            context.persistAndReturnSelf().using(response).fire(
+                new Receiver<SimpleBarProxy>() {
+
+                  @Override
+                  public void onSuccess(SimpleBarProxy response) {
+                    response = checkSerialization(response);
+                    // The last-known state should be returned
+                    assertNotNull(response);
+                    assertEquals("B", response.getUserName());
+
+                    SimpleBarRequest context = simpleBarRequest();
+                    // Ensure attempts to mutate deleted objects don't blow up
+                    response = context.edit(response);
+                    response.setUserName("C");
+
+                    // Attempting to use the now-deleted object should fail
+                    context.persistAndReturnSelf().using(response).fire(
+                        new Receiver<SimpleBarProxy>() {
+                          @Override
+                          public void onFailure(ServerFailure error) {
+                            assertTrue(error.getMessage().contains(
+                                "The requested entity is not available on"
+                                    + " the server"));
+                            finishTestAndReset();
+                          }
+
+                          @Override
+                          public void onSuccess(SimpleBarProxy response) {
+                            response = checkSerialization(response);
+                            fail();
+                          }
+                        });
+                  }
+                });
+          }
+        });
+  }
+
+  public void testValueMethodInvocation() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    SimpleValueContext ctx = req.simpleValueContext();
+    SimpleValueProxy p = ctx.create(SimpleValueProxy.class);
+    p.setString("Hello World!");
+    ctx.getString().using(p).fire(new Receiver<String>() {
+      @Override
+      public void onSuccess(String response) {
+        assertEquals("Hello World!", response);
+        finishTestAndReset();
+      }
+    });
+  }
+
+  public void testValueObjectCreateSetRetrieveUpdate() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    SimpleFooRequest req = simpleFooRequest();
+    req.findSimpleFooById(1L).fire(new Receiver<SimpleFooProxy>() {
+      @Override
+      public void onSuccess(SimpleFooProxy response) {
+        response = checkSerialization(response);
+        SimpleFooRequest req = simpleFooRequest();
+
+        // Create
+        final SimpleValueProxy created = req.create(SimpleValueProxy.class);
+        created.setNumber(42);
+        created.setString("Hello world!");
+        created.setSimpleFoo(response);
+        // Test cycles in value
+        created.setSimpleValue(Arrays.asList(created));
+
+        // Set
+        response = req.edit(response);
+        response.setSimpleValue(created);
+
+        // Retrieve
+        req.persistAndReturnSelf().using(response).with(
+            "simpleValue.simpleFoo", "simpleValue.simpleValue").to(
+            new Receiver<SimpleFooProxy>() {
+              @Override
+              public void onSuccess(SimpleFooProxy response) {
+                response = checkSerialization(response);
+                SimpleValueProxy value = response.getSimpleValue();
+                assertEquals(42, value.getNumber());
+                assertEquals("Hello world!", value.getString());
+                assertSame(response, value.getSimpleFoo());
+                assertSame(value, value.getSimpleValue().get(0));
+
+                try {
+                  // Require owning object to be editable
+                  response.getSimpleValue().setNumber(43);
+                  fail("Should have thrown exception");
+                } catch (IllegalStateException expected) {
+                }
+
+                // Update
+                SimpleFooRequest req = simpleFooRequest();
+                response = req.edit(response);
+                response.getSimpleValue().setNumber(43);
+                req.persistAndReturnSelf().using(response).with("simpleValue").to(
+                    new Receiver<SimpleFooProxy>() {
+                      @Override
+                      public void onSuccess(SimpleFooProxy response) {
+                        response = checkSerialization(response);
+                        assertEquals(43, response.getSimpleValue().getNumber());
+                        finishTestAndReset();
+                      }
+                    }).fire();
+              }
+            }).fire();
+      }
+    });
+  }
+
+  public void testValueObjectCreateSetRetrieveUpdateViaList() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    SimpleFooRequest req = simpleFooRequest();
+    req.findSimpleFooById(1L).fire(new Receiver<SimpleFooProxy>() {
+      @Override
+      public void onSuccess(SimpleFooProxy response) {
+        response = checkSerialization(response);
+        SimpleFooRequest req = simpleFooRequest();
+
+        // Create
+        final SimpleValueProxy created = req.create(SimpleValueProxy.class);
+        created.setNumber(42);
+        created.setString("Hello world!");
+        created.setSimpleFoo(response);
+
+        // Set
+        response = req.edit(response);
+        response.setSimpleValues(Arrays.asList(created));
+
+        // Retrieve
+        req.persistAndReturnSelf().using(response).with("simpleValues").to(
+            new Receiver<SimpleFooProxy>() {
+              @Override
+              public void onSuccess(SimpleFooProxy response) {
+                response = checkSerialization(response);
+                SimpleValueProxy value = response.getSimpleValues().get(0);
+                assertEquals(42, value.getNumber());
+
+                try {
+                  // Require owning object to be editable
+                  response.getSimpleValues().get(0).setNumber(43);
+                  fail("Should have thrown exception");
+                } catch (IllegalStateException expected) {
+                }
+
+                // Update
+                SimpleFooRequest req = simpleFooRequest();
+                response = req.edit(response);
+                response.getSimpleValues().get(0).setNumber(43);
+                req.persistAndReturnSelf().using(response).with("simpleValues").to(
+                    new Receiver<SimpleFooProxy>() {
+                      @Override
+                      public void onSuccess(SimpleFooProxy response) {
+                        response = checkSerialization(response);
+                        assertEquals(43,
+                            response.getSimpleValues().get(0).getNumber());
+                        finishTestAndReset();
+                      }
+                    }).fire();
+              }
+            }).fire();
+      }
+    });
+  }
+
+  public void testValueObjectEquality() {
+    SimpleFooRequest req = simpleFooRequest();
+    SimpleValueProxy a = req.create(SimpleValueProxy.class);
+    SimpleValueProxy b = req.create(SimpleValueProxy.class);
+    checkEqualityAndHashcode(a, b);
+
+    a.setString("Hello");
+    assertFalse(a.equals(b));
+    assertFalse(b.equals(a));
+
+    b.setString("Hello");
+    checkEqualityAndHashcode(a, b);
+
+    a.setSimpleValue(Collections.singletonList(req.create(SimpleValueProxy.class)));
+    assertFalse(a.equals(b));
+    assertFalse(b.equals(a));
+    b.setSimpleValue(Collections.singletonList(req.create(SimpleValueProxy.class)));
+    checkEqualityAndHashcode(a, b);
+
+    a.getSimpleValue().get(0).setNumber(55);
+    assertFalse(a.equals(b));
+    assertFalse(b.equals(a));
+    b.getSimpleValue().get(0).setNumber(55);
+    checkEqualityAndHashcode(a, b);
+  }
+
+  /**
+   * Since a ValueProxy cannot be passed into RequestContext edit, a proxy
+   * returned from a service method should be mutable by default.
+   */
+  public void testValueObjectReturnedFromRequestIsImmutable() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    simpleFooRequest().returnValueProxy().fire(
+        new Receiver<SimpleValueProxy>() {
+          @Override
+          public void onSuccess(SimpleValueProxy a) {
+            a = checkSerialization(a);
+            try {
+              a.setNumber(77);
+              fail();
+            } catch (IllegalStateException expected) {
+            }
+            try {
+              // Ensure Dates comply with ValueProxy mutation behaviors
+              a.getDate().setTime(1);
+              fail();
+            } catch (IllegalStateException expected) {
+            }
+            SimpleFooRequest ctx = simpleFooRequest();
+            final SimpleValueProxy toCheck = ctx.edit(a);
+            toCheck.setNumber(77);
+            toCheck.getDate().setTime(1);
+            ctx.returnValueProxy().fire(new Receiver<SimpleValueProxy>() {
+              @Override
+              public void onSuccess(SimpleValueProxy b) {
+                b = checkSerialization(b);
+                b = simpleFooRequest().edit(b);
+                // Now check that same value is equal across contexts
+                b.setNumber(77);
+                b.setDate(new Date(1));
+                checkEqualityAndHashcode(toCheck, b);
+                finishTestAndReset();
+              }
+            });
+          }
+        });
+  }
+
+  public void testValueObjectViolationsOnCreate() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    SimpleFooRequest req = simpleFooRequest();
+    final SimpleValueProxy value = req.create(SimpleValueProxy.class);
+    value.setShouldBeNull("Hello world");
+
+    SimpleFooProxy foo = req.create(SimpleFooProxy.class);
+    foo.setSimpleValue(value);
+    req.echo(foo).fire(new Receiver<SimpleFooProxy>() {
+      @Override
+      public void onSuccess(SimpleFooProxy response) {
+        fail();
+      }
+
+      @Override
+      public void onViolation(Set<Violation> errors) {
+        assertEquals(1, errors.size());
+        Violation v = errors.iterator().next();
+        assertEquals(value, v.getInvalidProxy());
+        assertNull(v.getOriginalProxy());
+        assertEquals("shouldBeNull", v.getPath());
+        assertNull(v.getProxyId());
+        finishTestAndReset();
+      }
+    });
+  }
+
+  public void testValueObjectViolationsOnEdit() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    simpleFooRequest().returnValueProxy().fire(
+        new Receiver<SimpleValueProxy>() {
+          @Override
+          public void onSuccess(SimpleValueProxy response) {
+            final SimpleValueProxy original = checkSerialization(response);
+            SimpleFooRequest req = simpleFooRequest();
+            final SimpleValueProxy value = req.edit(response);
+            value.setShouldBeNull("Hello world");
+            SimpleFooProxy foo = req.create(SimpleFooProxy.class);
+            foo.setSimpleValue(value);
+            req.echo(foo).fire(new Receiver<SimpleFooProxy>() {
+              @Override
+              public void onSuccess(SimpleFooProxy response) {
+                fail();
+              }
+
+              @Override
+              public void onViolation(Set<Violation> errors) {
+                assertEquals(1, errors.size());
+                Violation v = errors.iterator().next();
+                assertEquals(value, v.getInvalidProxy());
+                assertEquals(original, v.getOriginalProxy());
+                assertEquals("shouldBeNull", v.getPath());
+                assertNull(v.getProxyId());
+                finishTestAndReset();
+              }
+            });
+          }
+        });
+  }
+
+  public void testViolationAbsent() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    SimpleFooRequest context = simpleFooRequest();
+    SimpleFooProxy newFoo = context.create(SimpleFooProxy.class);
+    final Request<Void> fooReq = context.persist().using(newFoo);
+
+    newFoo = context.edit(newFoo);
+    newFoo.setUserName("Amit"); // will not cause violation.
+
+    fooReq.fire(new Receiver<Void>() {
+      @Override
+      public void onSuccess(Void ignore) {
+        finishTestAndReset();
+      }
+    });
+  }
+
+  public void testViolationsOnCreate() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    SimpleFooRequest context = simpleFooRequest();
+    SimpleFooProxy newFoo = context.create(SimpleFooProxy.class);
+    final Request<SimpleFooProxy> create = context.persistAndReturnSelf().using(
+        newFoo);
+    new FailFixAndRefire<SimpleFooProxy>(newFoo, context, create).doTest();
+  }
+
+  public void testViolationsOnCreateVoidReturn() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    SimpleFooRequest context = simpleFooRequest();
+    SimpleFooProxy newFoo = context.create(SimpleFooProxy.class);
+    final Request<Void> create = context.persist().using(newFoo);
+    new FailFixAndRefire<Void>(newFoo, context, create).doVoidTest();
+  }
+
+  public void testViolationsOnEdit() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    fooCreationRequest().fire(new Receiver<SimpleFooProxy>() {
+      @Override
+      public void onSuccess(SimpleFooProxy returned) {
+        returned = checkSerialization(returned);
+        SimpleFooRequest context = simpleFooRequest();
+        Request<SimpleFooProxy> editRequest = context.persistAndReturnSelf().using(
+            returned);
+        new FailFixAndRefire<SimpleFooProxy>(returned, context, editRequest).doTest();
+      }
+    });
+  }
+
+  public void testViolationsOnEditVoidReturn() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    fooCreationRequest().fire(new Receiver<SimpleFooProxy>() {
+      @Override
+      public void onSuccess(SimpleFooProxy returned) {
+        returned = checkSerialization(returned);
+        SimpleFooRequest context = simpleFooRequest();
+        Request<Void> editRequest = context.persist().using(returned);
+        new FailFixAndRefire<Void>(returned, context, editRequest).doVoidTest();
+      }
+    });
+  }
+
+  protected SimpleBarRequest simpleBarRequest() {
+    return req.simpleBarRequest();
+  }
+
+  protected SimpleFooRequest simpleFooRequest() {
+    return req.simpleFooRequest();
+  }
+
+  private void assertCannotFire(final Request<Long> mutateRequest) {
+    try {
+      mutateRequest.fire(new Receiver<Long>() {
+        @Override
+        public void onSuccess(Long response) {
+          fail("Should not be called");
+        }
+      });
+      fail("Expected IllegalStateException");
+    } catch (IllegalStateException e) {
+      /* cannot reuse a successful request, mores the pity */
+    }
+  }
+
+  private Request<SimpleFooProxy> fooCreationRequest() {
+    SimpleFooRequest context = simpleFooRequest();
+    SimpleFooProxy originalFoo = context.create(SimpleFooProxy.class);
+    final Request<SimpleFooProxy> fooReq = context.persistAndReturnSelf().using(
+        originalFoo);
+    originalFoo = context.edit(originalFoo);
+    originalFoo.setUserName("GWT User");
+    return fooReq;
+  }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryTestBase.java b/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryTestBase.java
new file mode 100644
index 0000000..cac062b
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryTestBase.java
@@ -0,0 +1,166 @@
+/*
+ * 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.requestfactory.gwt.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanUtils;
+import com.google.web.bindery.event.shared.EventBus;
+import com.google.web.bindery.event.shared.SimpleEventBus;
+import com.google.web.bindery.requestfactory.shared.BaseProxy;
+import com.google.web.bindery.requestfactory.shared.DefaultProxyStore;
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.EntityProxyChange;
+import com.google.web.bindery.requestfactory.shared.ProxySerializer;
+import com.google.web.bindery.requestfactory.shared.Receiver;
+import com.google.web.bindery.requestfactory.shared.SimpleRequestFactory;
+import com.google.web.bindery.requestfactory.shared.impl.BaseProxyCategory;
+import com.google.web.bindery.requestfactory.shared.impl.Constants;
+import com.google.web.bindery.requestfactory.shared.impl.SimpleProxyId;
+
+/**
+ * A base class for anything that makes use of the SimpleRequestFactory.
+ * Subclasses must always use {@link #finishTestAndReset()} in order to allow
+ * calls to the reset methods to complete before the next test starts.
+ *
+ */
+public abstract class RequestFactoryTestBase extends GWTTestCase {
+
+  /**
+   * Class for counting events.
+   */
+  protected class SimpleFooEventHandler<P extends EntityProxy> implements
+      EntityProxyChange.Handler<P> {
+    int persistEventCount = 0;
+    int deleteEventCount = 0;
+    int totalEventCount = 0;
+    int updateEventCount = 0;
+
+    public void onProxyChange(EntityProxyChange<P> event) {
+      totalEventCount++;
+      switch (event.getWriteOperation()) {
+        case PERSIST:
+          persistEventCount++;
+          break;
+        case DELETE:
+          deleteEventCount++;
+          break;
+        case UPDATE:
+          updateEventCount++;
+          break;
+        default:
+          break;
+      }
+    }
+  }
+
+  protected EventBus eventBus;
+  protected SimpleRequestFactory req;
+
+  @Override
+  public void gwtSetUp() {
+    req = createFactory();
+    eventBus = req.getEventBus();
+  }
+
+  protected void checkEqualityAndHashcode(Object a, Object b) {
+    assertNotNull(a);
+    assertNotNull(b);
+    assertEquals(a.hashCode(), b.hashCode());
+    assertEquals(a, b);
+    assertEquals(b, a);
+  }
+
+  /**
+   * Run the given proxy through a ProxySerializer and verify that the
+   * before-and-after values match.
+   */
+  protected <T extends BaseProxy> T checkSerialization(T proxy) {
+    AutoBean<T> originalBean = AutoBeanUtils.getAutoBean(proxy);
+    SimpleProxyId<T> id = BaseProxyCategory.stableId(originalBean);
+    DefaultProxyStore store = new DefaultProxyStore();
+    ProxySerializer s = req.getSerializer(store);
+
+    String key = s.serialize(proxy);
+    assertNotNull(key);
+
+    // Use a new instance
+    store = new DefaultProxyStore(store.encode());
+    s = req.getSerializer(store);
+    T restored = s.deserialize(id.getProxyClass(), key);
+    AutoBean<? extends BaseProxy> restoredBean = AutoBeanUtils.getAutoBean(restored);
+    assertNotSame(proxy, restored);
+    /*
+     * Performing a regular assertEquals() or even an AutoBeanUtils.diff() here
+     * is wrong. If any of the objects in the graph are unpersisted, it's
+     * expected that the stable ids would change. Instead, we do a value-based
+     * check.
+     */
+    assertTrue(AutoBeanUtils.deepEquals(originalBean, restoredBean));
+
+    if (proxy instanceof EntityProxy && !id.isEphemeral()) {
+      assertEquals(((EntityProxy) proxy).stableId(),
+          ((EntityProxy) restored).stableId());
+    }
+
+    // In deference to testing stable ids, copy the original id into the clone
+    restoredBean.setTag(Constants.STABLE_ID,
+        originalBean.getTag(Constants.STABLE_ID));
+    return restored;
+  }
+
+  protected void checkStableIdEquals(EntityProxy expected, EntityProxy actual) {
+    assertEquals(expected.stableId(), actual.stableId());
+    assertEquals(expected.stableId().hashCode(), actual.stableId().hashCode());
+    assertSame(expected.stableId(), actual.stableId());
+
+    // No assumptions about the proxy objects (being proxies and all)
+    assertNotSame(expected, actual);
+    assertFalse(expected.equals(actual));
+  }
+
+  /**
+   * Create and initialize a new {@link SimpleRequestFactory}.
+   */
+  protected SimpleRequestFactory createFactory() {
+    SimpleRequestFactory toReturn = GWT.create(SimpleRequestFactory.class);
+    toReturn.initialize(new SimpleEventBus());
+    return toReturn;
+  }
+
+  protected void finishTestAndReset() {
+    final boolean[] reallyDone = {false, false};
+    req.simpleFooRequest().reset().fire(new Receiver<Void>() {
+      @Override
+      public void onSuccess(Void response) {
+        reallyDone[0] = true;
+        if (reallyDone[0] && reallyDone[1]) {
+          finishTest();
+        }
+      }
+    });
+    req.simpleBarRequest().reset().fire(new Receiver<Void>() {
+      @Override
+      public void onSuccess(Void response) {
+        reallyDone[1] = true;
+        if (reallyDone[0] && reallyDone[1]) {
+          finishTest();
+        }
+      }
+    });
+  }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryUnicodeEscapingTest.java b/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryUnicodeEscapingTest.java
new file mode 100644
index 0000000..3a334b2
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryUnicodeEscapingTest.java
@@ -0,0 +1,142 @@
+/*
+ * 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.requestfactory.gwt.client;
+
+import com.google.gwt.user.client.rpc.UnicodeEscapingService.InvalidCharacterException;
+import com.google.gwt.user.client.rpc.UnicodeEscapingTest;
+import com.google.web.bindery.requestfactory.shared.Receiver;
+import com.google.web.bindery.requestfactory.shared.ServerFailure;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Runs through a portion of the Basic Multilingual Plane.
+ */
+public class RequestFactoryUnicodeEscapingTest extends RequestFactoryTestBase {
+  private static final int TEST_FINISH_DELAY_MS = 5000;
+  private final UnicodeEscapingTest test = new UnicodeEscapingTest() {
+
+    @Override
+    protected void clientToServerVerifyRange(int start, final int end,
+        final int size, final int step) throws InvalidCharacterException {
+      current = start;
+      int blockEnd = Math.min(end, current + size);
+      req.unicodeTestRequest().verifyStringContainingCharacterRange(current,
+          blockEnd, getStringContainingCharacterRange(start, blockEnd)).fire(
+          new Receiver<Void>() {
+            List<ServerFailure> fails = new ArrayList<ServerFailure>();
+
+            @Override
+            public void onFailure(ServerFailure error) {
+              fails.add(error);
+              onSuccess(null);
+            }
+
+            @Override
+            public void onSuccess(Void response) {
+              current += step;
+              if (current < end) {
+                delayTestFinish(TEST_FINISH_DELAY_MS);
+                int blockEnd = Math.min(end, current + size);
+                req.unicodeTestRequest().verifyStringContainingCharacterRange(
+                    current, blockEnd,
+                    getStringContainingCharacterRange(current, blockEnd)).fire(
+                    this);
+              } else if (!fails.isEmpty()) {
+                StringBuilder msg = new StringBuilder();
+                for (ServerFailure error : fails) {
+                  msg.append(error.getMessage()).append("\n");
+                }
+                throw new RuntimeException(msg.toString());
+              } else {
+                finishTest();
+              }
+            }
+          });
+    }
+
+    @Override
+    protected void serverToClientVerify(int start, final int end,
+        final int size, final int step) {
+      current = start;
+      req.unicodeTestRequest().getStringContainingCharacterRange(start,
+          Math.min(end, current + size)).fire(new Receiver<String>() {
+        List<ServerFailure> fails = new ArrayList<ServerFailure>();
+
+        @Override
+        public void onFailure(ServerFailure error) {
+          fails.add(error);
+          nextBatch();
+        }
+
+        @Override
+        public void onSuccess(String response) {
+          try {
+            verifyStringContainingCharacterRange(current,
+                Math.min(end, current + size), response);
+          } catch (InvalidCharacterException e) {
+            fails.add(new ServerFailure(e.getMessage()));
+          }
+          nextBatch();
+        }
+
+        private void nextBatch() {
+          current += step;
+          if (current < end) {
+            delayTestFinish(TEST_FINISH_DELAY_MS);
+            req.unicodeTestRequest().getStringContainingCharacterRange(current,
+                Math.min(end, current + size)).fire(this);
+          } else if (!fails.isEmpty()) {
+            StringBuilder msg = new StringBuilder();
+            for (ServerFailure t : fails) {
+              msg.append(t.getMessage()).append("\n");
+            }
+            throw new RuntimeException(msg.toString());
+          } else {
+            finishTest();
+          }
+        }
+      });
+    }
+  };
+
+  @Override
+  public String getModuleName() {
+    return "com.google.web.bindery.requestfactory.gwt.RequestFactorySuite";
+  }
+
+  public void testClientToServerBMPHigh() throws InvalidCharacterException {
+    test.testClientToServerBMPHigh();
+  }
+
+  public void testClientToServerBMPLow() throws InvalidCharacterException {
+    test.testClientToServerBMPLow();
+  }
+
+  public void testClientToServerNonBMP() throws InvalidCharacterException {
+    test.testClientToServerNonBMP();
+  }
+
+  public void testServerToClientBMP() {
+    test.testServerToClientBMP();
+  }
+
+  public void testServerToClientNonBMP() {
+    test.testServerToClientNonBMP();
+  }
+
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/gwt/client/SimpleRequestFactoryInstance.java b/user/test/com/google/web/bindery/requestfactory/gwt/client/SimpleRequestFactoryInstance.java
new file mode 100644
index 0000000..da4c61e
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/gwt/client/SimpleRequestFactoryInstance.java
@@ -0,0 +1,36 @@
+/*
+ * 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.requestfactory.gwt.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.web.bindery.event.shared.SimpleEventBus;
+import com.google.web.bindery.requestfactory.shared.SimpleRequestFactory;
+
+/**
+ * Easy access for RequestFactory tests.
+ */
+public class SimpleRequestFactoryInstance {
+  private static SimpleRequestFactory factory;
+
+  public static SimpleRequestFactory factory() {
+    if (factory == null) {
+      factory = GWT.create(SimpleRequestFactory.class);
+      factory.initialize(new SimpleEventBus());
+    }
+
+    return factory;
+  }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/gwt/client/ui/EditorTest.java b/user/test/com/google/web/bindery/requestfactory/gwt/client/ui/EditorTest.java
new file mode 100644
index 0000000..66b8cc2
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/gwt/client/ui/EditorTest.java
@@ -0,0 +1,321 @@
+/*
+ * 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.requestfactory.gwt.client.ui;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.RepeatingCommand;
+import com.google.gwt.editor.client.Editor;
+import com.google.gwt.editor.client.EditorDelegate;
+import com.google.gwt.editor.client.EditorError;
+import com.google.gwt.editor.client.HasEditorDelegate;
+import com.google.gwt.editor.client.HasEditorErrors;
+import com.google.gwt.editor.client.adapters.EditorSource;
+import com.google.gwt.editor.client.adapters.ListEditor;
+import com.google.gwt.editor.client.adapters.SimpleEditor;
+import com.google.web.bindery.requestfactory.gwt.client.HasRequestContext;
+import com.google.web.bindery.requestfactory.gwt.client.RequestFactoryEditorDriver;
+import com.google.web.bindery.requestfactory.gwt.client.RequestFactoryTestBase;
+import com.google.web.bindery.requestfactory.shared.Receiver;
+import com.google.web.bindery.requestfactory.shared.Request;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.SimpleBarProxy;
+import com.google.web.bindery.requestfactory.shared.SimpleFooProxy;
+import com.google.web.bindery.requestfactory.shared.SimpleFooRequest;
+import com.google.web.bindery.requestfactory.shared.Violation;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Integration test of the Editor framework. Only tests for
+ * RequestFactory-specific features belong here; all other tests should use the
+ * SimpleBeanEditorDriver to make the tests simpler.
+ */
+public class EditorTest extends RequestFactoryTestBase {
+  /*
+   * DO NOT USE finishTest(). Instead, call finishTestAndReset();
+   */
+
+  static class SimpleBarEditor implements Editor<SimpleBarProxy>,
+      HasRequestContext<SimpleBarProxy> {
+    protected final SimpleEditor<String> userName = SimpleEditor.of();
+    RequestContext ctx;
+
+    public void setRequestContext(RequestContext ctx) {
+      this.ctx = ctx;
+    }
+  }
+
+  static class SimpleFooBarNameOnlyEditor implements Editor<SimpleFooProxy> {
+    /**
+     * Test nested path access.
+     */
+    @Path("barField.userName")
+    final SimpleEditor<String> barName = SimpleEditor.of();
+  }
+
+  interface SimpleFooDriver extends
+      RequestFactoryEditorDriver<SimpleFooProxy, SimpleFooEditor> {
+  }
+
+  static class SimpleFooEditor implements HasEditorErrors<SimpleFooProxy> {
+    /**
+     * Test field-based access.
+     */
+    final SimpleEditor<String> userName = SimpleEditor.of();
+
+    /**
+     * Test nested path access.
+     */
+    @Path("barField.userName")
+    final SimpleEditor<String> barName = SimpleEditor.of();
+
+    final ListEditor<SimpleFooProxy, SimpleFooBarNameOnlyEditor> selfOneToManyField = ListEditor.of(new EditorSource<SimpleFooBarNameOnlyEditor>() {
+      @Override
+      public SimpleFooBarNameOnlyEditor create(int index) {
+        return new SimpleFooBarNameOnlyEditor();
+      }
+    });
+
+    private final SimpleBarEditor barEditor = new SimpleBarEditor();
+
+    List<EditorError> errors;
+
+    public void showErrors(List<EditorError> errors) {
+      this.errors = errors;
+    }
+
+    /**
+     * Test method-based access with path override.
+     */
+    @Path("barField")
+    SimpleBarEditor barEditor() {
+      return barEditor;
+    }
+  }
+
+  static class SimpleFooEditorWithDelegate extends SimpleFooEditor implements
+      HasEditorDelegate<SimpleFooProxy> {
+    EditorDelegate<SimpleFooProxy> delegate;
+
+    public void setDelegate(EditorDelegate<SimpleFooProxy> delegate) {
+      this.delegate = delegate;
+    }
+  }
+
+  private static final int TEST_TIMEOUT = 5000;
+
+  @Override
+  public String getModuleName() {
+    return "com.google.web.bindery.requestfactory.gwt.RequestFactorySuite";
+  }
+
+  public void test() {
+    delayTestFinish(TEST_TIMEOUT);
+    final SimpleFooEditor editor = new SimpleFooEditor();
+
+    final SimpleFooDriver driver = GWT.create(SimpleFooDriver.class);
+    driver.initialize(req, editor);
+    final String[] paths = driver.getPaths();
+    assertEquals(Arrays.asList("barField", "selfOneToManyField",
+        "selfOneToManyField.barField"), Arrays.asList(paths));
+
+    req.simpleFooRequest().findSimpleFooById(1L).with(paths).fire(
+        new Receiver<SimpleFooProxy>() {
+          @Override
+          public void onSuccess(SimpleFooProxy response) {
+
+            SimpleFooRequest context = req.simpleFooRequest();
+            driver.edit(response, context);
+            assertSame(context, editor.barEditor().ctx);
+            context.persistAndReturnSelf().using(response).with(paths).to(
+                new Receiver<SimpleFooProxy>() {
+                  @Override
+                  public void onSuccess(SimpleFooProxy response) {
+                    assertEquals("EditorFooTest", response.getUserName());
+                    assertEquals("EditorBarTest",
+                        response.getBarField().getUserName());
+                    finishTestAndReset();
+                  }
+                });
+            assertEquals("GWT", editor.userName.getValue());
+            assertEquals("FOO", editor.barEditor().userName.getValue());
+            assertEquals("FOO", editor.barName.getValue());
+            editor.userName.setValue("EditorFooTest");
+            // When there are duplicate paths, last declared editor wins
+            editor.barEditor().userName.setValue("EditorBarTest");
+            editor.barName.setValue("ignored 1");
+            editor.selfOneToManyField.getEditors().get(0).barName.setValue(
+                "ignored 2");
+            driver.flush().fire();
+          }
+        });
+  }
+
+  public void testNoSubscription() {
+    final SimpleFooEditorWithDelegate editor = new SimpleFooEditorWithDelegate();
+
+    final SimpleFooDriver driver = GWT.create(SimpleFooDriver.class);
+    driver.initialize(req, editor);
+
+    /*
+     * Confirm that it's always safe to call subscribe. The editor's delegate
+     * isn't set until edit is called, so edit nothing.
+     */
+    driver.edit(null, null);
+    assertNull(editor.delegate.subscribe());
+  }
+
+  /**
+   * Tests the editor can be re-used while the initial context is locked and
+   * therefore its attached proxies are frozen..
+   * 
+   * @see http://code.google.com/p/google-web-toolkit/issues/detail?id=5752
+   */
+  public void testReuse() {
+    delayTestFinish(TEST_TIMEOUT);
+    final SimpleFooEditor editor = new SimpleFooEditor();
+
+    final SimpleFooDriver driver = GWT.create(SimpleFooDriver.class);
+    driver.initialize(req, editor);
+
+    req.simpleFooRequest().findSimpleFooById(1L).with(driver.getPaths()).fire(
+        new Receiver<SimpleFooProxy>() {
+          @Override
+          public void onSuccess(SimpleFooProxy response) {
+
+            SimpleFooRequest context = req.simpleFooRequest();
+            driver.edit(response, context);
+            editor.userName.setValue("One");
+            context.persistAndReturnSelf().using(response).with(
+                driver.getPaths()).to(new Receiver<SimpleFooProxy>() {
+              @Override
+              public void onSuccess(SimpleFooProxy response) {
+                assertEquals("One", response.getUserName());
+                // just testing that it doesn't throw (see issue 5752)
+                driver.edit(response, req.simpleFooRequest());
+                editor.userName.setValue("Two");
+                driver.flush();
+                finishTestAndReset();
+              }
+            });
+            // The fire() will freeze the proxies and lock the context
+            driver.flush().fire();
+          }
+        });
+  }
+
+  public void testSubscription() {
+    delayTestFinish(TEST_TIMEOUT);
+    final SimpleFooEditorWithDelegate editor = new SimpleFooEditorWithDelegate();
+
+    final SimpleFooDriver driver = GWT.create(SimpleFooDriver.class);
+    driver.initialize(req, editor);
+
+    String[] paths = driver.getPaths();
+    assertEquals(Arrays.asList("barField", "selfOneToManyField",
+        "selfOneToManyField.barField"), Arrays.asList(paths));
+
+    req.simpleFooRequest().findSimpleFooById(1L).with(paths).fire(
+        new Receiver<SimpleFooProxy>() {
+          @Override
+          public void onSuccess(SimpleFooProxy response) {
+            // Set up driver in read-only mode
+            driver.edit(response, null);
+            assertNotNull(editor.delegate.subscribe());
+
+            // Simulate edits occurring elsewhere in the module
+            SimpleFooRequest context = req.simpleFooRequest();
+            Request<SimpleFooProxy> request = context.persistAndReturnSelf().using(
+                response);
+            SimpleBarProxy newBar = context.create(SimpleBarProxy.class);
+            newBar = context.edit(newBar);
+            newBar.setUserName("newBar");
+            response = context.edit(response);
+            response.setBarField(newBar);
+            response.setUserName("updated");
+
+            request.fire(new Receiver<SimpleFooProxy>() {
+              @Override
+              public void onSuccess(SimpleFooProxy response) {
+                // EventBus notifications occur after the onSuccess()
+                Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
+                  public boolean execute() {
+                    if ("updated".equals(editor.userName.getValue())) {
+                      assertEquals("updated", editor.userName.getValue());
+                      assertEquals("newBar",
+                          editor.barEditor().userName.getValue());
+                      finishTestAndReset();
+                      return false;
+                    }
+                    return true;
+                  }
+                }, 50);
+              }
+            });
+          }
+        });
+  }
+
+  public void testViolations() {
+    delayTestFinish(TEST_TIMEOUT);
+    final SimpleFooEditor editor = new SimpleFooEditor();
+
+    final SimpleFooDriver driver = GWT.create(SimpleFooDriver.class);
+    driver.initialize(req, editor);
+
+    req.simpleFooRequest().findSimpleFooById(1L).with(driver.getPaths()).fire(
+        new Receiver<SimpleFooProxy>() {
+          @Override
+          public void onSuccess(SimpleFooProxy response) {
+
+            SimpleFooRequest context = req.simpleFooRequest();
+            driver.edit(response, context);
+            context.persistAndReturnSelf().using(response).with(
+                driver.getPaths()).to(new Receiver<SimpleFooProxy>() {
+              @Override
+              public void onSuccess(SimpleFooProxy response) {
+                fail("Expected errors. You may be missing jars, see "
+                    + "the comment in RequestFactoryTest.ShouldNotSucceedReceiver.onSuccess");
+              }
+
+              @Override
+              public void onViolation(Set<Violation> errors) {
+                assertEquals(1, errors.size());
+                Violation v = errors.iterator().next();
+
+                driver.setViolations(errors);
+                assertEquals(1, editor.errors.size());
+                EditorError error = editor.errors.get(0);
+                assertEquals("userName", error.getAbsolutePath());
+                assertSame(editor.userName, error.getEditor());
+                assertTrue(error.getMessage().length() > 0);
+                assertEquals("userName", error.getPath());
+                assertSame(v, error.getUserData());
+                assertNull(error.getValue());
+                finishTestAndReset();
+              }
+            });
+            // Set to an illegal value
+            editor.userName.setValue("");
+
+            driver.flush().fire();
+          }
+        });
+  }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/gwt/rebind/model/RequestFactoryModelTest.java b/user/test/com/google/web/bindery/requestfactory/gwt/rebind/model/RequestFactoryModelTest.java
new file mode 100644
index 0000000..14af62b
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/gwt/rebind/model/RequestFactoryModelTest.java
@@ -0,0 +1,314 @@
+/*
+ * 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.requestfactory.gwt.rebind.model;
+
+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.dev.javac.CompilationState;
+import com.google.gwt.dev.javac.CompilationStateBuilder;
+import com.google.gwt.dev.javac.impl.JavaResourceBase;
+import com.google.gwt.dev.javac.impl.MockJavaResource;
+import com.google.gwt.dev.resource.Resource;
+import com.google.gwt.dev.util.UnitTestTreeLogger;
+import com.google.gwt.dev.util.Util;
+import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
+import com.google.web.bindery.requestfactory.server.TestContextImpl;
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.InstanceRequest;
+import com.google.web.bindery.requestfactory.shared.Locator;
+import com.google.web.bindery.requestfactory.shared.ProxyFor;
+import com.google.web.bindery.requestfactory.shared.Receiver;
+import com.google.web.bindery.requestfactory.shared.Request;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.RequestFactory;
+import com.google.web.bindery.requestfactory.shared.Service;
+import com.google.web.bindery.requestfactory.shared.ServiceLocator;
+import com.google.web.bindery.requestfactory.shared.ValueProxy;
+
+import junit.framework.TestCase;
+
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+
+/**
+ * Test case for
+ * {@link com.google.web.bindery.requestfactory.gwt.rebind.model.RequestFactoryModel} that
+ * uses mock CompilationStates.
+ */
+public class RequestFactoryModelTest extends TestCase {
+
+  /**
+   * Constructs an empty interface representation of a type.
+   */
+  private static class EmptyMockJavaResource extends MockJavaResource {
+
+    private final StringBuilder code = new StringBuilder();
+
+    public EmptyMockJavaResource(Class<?> clazz) {
+      super(clazz.getName());
+
+      code.append("package ").append(clazz.getPackage().getName()).append(";\n");
+      code.append("public interface ").append(clazz.getSimpleName());
+
+      int numParams = clazz.getTypeParameters().length;
+      if (numParams != 0) {
+        code.append("<");
+        for (int i = 0; i < numParams; i++) {
+          if (i != 0) {
+            code.append(",");
+          }
+          code.append("T").append(i);
+        }
+        code.append(">");
+      }
+
+      code.append("{}\n");
+    }
+
+    @Override
+    protected CharSequence getContent() {
+      return code;
+    }
+  }
+
+  /**
+   * Loads the actual source of a type. This should be used only for types
+   * directly tested by this test. Note that use of this class requires your
+   * source files to be on your classpath.
+   */
+  private static class RealJavaResource extends MockJavaResource {
+
+    public RealJavaResource(Class<?> clazz) {
+      super(clazz.getName());
+    }
+
+    @Override
+    protected CharSequence getContent() {
+      String resourceName = getTypeName().replace('.', '/') + ".java";
+      InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(
+          resourceName);
+      assertNotNull("Could not open " + resourceName, stream);
+      return Util.readStreamAsString(stream);
+    }
+  }
+
+  private static TreeLogger createCompileLogger() {
+    PrintWriterTreeLogger logger = new PrintWriterTreeLogger(new PrintWriter(
+        System.err, true));
+    logger.setMaxDetail(TreeLogger.ERROR);
+    return logger;
+  }
+
+  private TreeLogger logger;
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    logger = createCompileLogger();
+  }
+
+  public void testBadCollectionType() {
+    testModelWithMethodDecl(
+        "Request<SortedSet<Integer>> badReturnType();",
+        "Requests that return collections may be declared with java.util.List or java.util.Set only");
+  }
+
+  public void testBadCollectionTypeNotParameterized() {
+    testModelWithMethodDecl("Request<SortedSet> badReturnType();",
+        "Requests that return collections of List or Set must be parameterized");
+  }
+
+  public void testBadReturnType() {
+    testModelWithMethodDecl("Request<Iterable> badReturnType();",
+        "Invalid Request parameterization java.lang.Iterable");
+  }
+
+  public void testDuplicateBooleanGetters() {
+    testModelWithMethodDecl("Request<t.ProxyWithRepeatedGetters> method();",
+        "Duplicate accessors for property foo: getFoo() and isFoo()");
+  }
+
+  public void testMissingProxyFor() {
+    testModelWithMethodDeclArgs("Request<TestProxy> okMethodProxy();",
+        TestContextImpl.class.getName(), null,
+        "The t.TestProxy type does not have a @ProxyFor, "
+            + "@ProxyForName, or @JsonRpcProxy annotation");
+  }
+
+  public void testMissingService() {
+    testModelWithMethodDeclArgs("Request<String> okMethod();", null,
+        TestContextImpl.class.getName(),
+        "RequestContext subtype t.TestContext is missing a "
+            + "@Service or @JsonRpcService annotation");
+  }
+
+  public void testModelWithMethodDecl(final String clientMethodDecls,
+      String... expected) {
+    testModelWithMethodDeclArgs(clientMethodDecls,
+        TestContextImpl.class.getName(), TestContextImpl.class.getName(),
+        expected);
+  }
+
+  public void testModelWithMethodDeclArgs(final String clientMethodDecls,
+      final String serviceClass, String proxyClass, String... expected) {
+    Set<Resource> javaResources = getJavaResources(proxyClass);
+    javaResources.add(new MockJavaResource("t.TestRequestFactory") {
+      @Override
+      protected CharSequence getContent() {
+        StringBuilder code = new StringBuilder();
+        code.append("package t;\n");
+        code.append("import " + RequestFactory.class.getName() + ";\n");
+        code.append("interface TestRequestFactory extends RequestFactory {\n");
+        code.append("TestContext testContext();");
+        code.append("}");
+        return code;
+      }
+    });
+    javaResources.add(new MockJavaResource("t.TestContext") {
+      @Override
+      protected CharSequence getContent() {
+        StringBuilder code = new StringBuilder();
+        code.append("package t;\n");
+        code.append("import " + Request.class.getName() + ";\n");
+        code.append("import " + InstanceRequest.class.getName() + ";\n");
+
+        code.append("import " + RequestContext.class.getName() + ";\n");
+        code.append("import " + SortedSet.class.getName() + ";\n");
+        code.append("import " + List.class.getName() + ";\n");
+        code.append("import " + Set.class.getName() + ";\n");
+        code.append("import " + Service.class.getName() + ";\n");
+        code.append("import " + TestContextImpl.class.getName() + ";\n");
+
+        if (serviceClass != null) {
+          code.append("@Service(" + serviceClass + ".class)");
+        }
+        code.append("interface TestContext extends RequestContext {\n");
+        code.append(clientMethodDecls);
+        code.append("}");
+        return code;
+      }
+    });
+
+    CompilationState state = CompilationStateBuilder.buildFrom(logger,
+        javaResources);
+
+    UnitTestTreeLogger.Builder builder = new UnitTestTreeLogger.Builder();
+    builder.setLowestLogLevel(TreeLogger.ERROR);
+    for (String expectedMsg : expected) {
+      builder.expectError(expectedMsg, null);
+    }
+    builder.expectError(RequestFactoryModel.poisonedMessage(), null);
+    UnitTestTreeLogger testLogger = builder.createLogger();
+    try {
+      new RequestFactoryModel(testLogger, state.getTypeOracle().findType(
+          "t.TestRequestFactory"));
+      fail("Should have complained");
+    } catch (UnableToCompleteException e) {
+    }
+    testLogger.assertCorrectLogEntries();
+  }
+
+  private Set<Resource> getJavaResources(final String proxyClass) {
+    MockJavaResource[] javaFiles = {new MockJavaResource("t.AddressProxy") {
+      @Override
+      protected CharSequence getContent() {
+        StringBuilder code = new StringBuilder();
+        code.append("package t;\n");
+        code.append("import " + ProxyFor.class.getName() + ";\n");
+        code.append("import " + EntityProxy.class.getName() + ";\n");
+        if (proxyClass != null) {
+          code.append("@ProxyFor(" + proxyClass + ".class)");
+        }
+        code.append("interface TestProxy extends EntityProxy {\n");
+        code.append("}");
+        System.out.println(code);
+        return code;
+      }
+    }, new MockJavaResource("t.ProxyWithRepeatedGetters") {
+      @Override
+      protected CharSequence getContent() {
+        StringBuilder code = new StringBuilder();
+        code.append("package t;\n");
+        code.append("import " + ProxyFor.class.getName() + ";\n");
+        code.append("import " + EntityProxy.class.getName() + ";\n");
+        if (proxyClass != null) {
+          code.append("@ProxyFor(" + proxyClass + ".class)");
+        }
+        code.append("interface ProxyWithRepeatedGetters extends EntityProxy {\n");
+        code.append("  boolean getFoo();");
+        code.append("  boolean isFoo();");
+        code.append("}");
+        return code;
+      }
+    }, new MockJavaResource("java.util.List") {
+        // Tests a Driver interface that extends more than RFED
+      @Override
+      protected CharSequence getContent() {
+        StringBuilder code = new StringBuilder();
+        code.append("package java.util;\n");
+        code.append("public interface List<T> extends Collection<T> {\n");
+        code.append("}");
+        return code;
+      }
+    }, new MockJavaResource("java.util.Set") {
+        // Tests a Driver interface that extends more than RFED
+      @Override
+      protected CharSequence getContent() {
+        StringBuilder code = new StringBuilder();
+        code.append("package java.util;\n");
+        code.append("public interface Set<T> extends Collection<T> {\n");
+        code.append("}");
+        return code;
+      }
+    }, new MockJavaResource("java.util.SortedSet") {
+        // Tests a Driver interface that extends more than RFED
+      @Override
+      protected CharSequence getContent() {
+        StringBuilder code = new StringBuilder();
+        code.append("package java.util;\n");
+        code.append("public interface SortedSet<T> extends Set<T> {\n");
+        code.append("}");
+        return code;
+      }
+    }};
+
+    Set<Resource> toReturn = new HashSet<Resource>(Arrays.asList(javaFiles));
+
+    toReturn.addAll(Arrays.asList(new Resource[] {
+        new EmptyMockJavaResource(Iterable.class),
+        new EmptyMockJavaResource(EntityProxy.class),
+        new EmptyMockJavaResource(InstanceRequest.class),
+        new EmptyMockJavaResource(Locator.class),
+        new EmptyMockJavaResource(RequestFactory.class),
+        new EmptyMockJavaResource(Receiver.class),
+        new EmptyMockJavaResource(ServiceLocator.class),
+        new EmptyMockJavaResource(Splittable.class),
+        new EmptyMockJavaResource(ValueProxy.class),
+
+        new RealJavaResource(Request.class),
+        new RealJavaResource(Service.class),
+        new RealJavaResource(ProxyFor.class),
+        new EmptyMockJavaResource(RequestContext.class),}));
+    toReturn.addAll(Arrays.asList(JavaResourceBase.getStandardResources()));
+    return toReturn;
+  }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/BoxesAndPrimitivesJreTest.java b/user/test/com/google/web/bindery/requestfactory/server/BoxesAndPrimitivesJreTest.java
new file mode 100644
index 0000000..c38d6c5
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/BoxesAndPrimitivesJreTest.java
@@ -0,0 +1,123 @@
+/*
+ * 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.requestfactory.server;
+
+import com.google.gwt.dev.asm.Type;
+import com.google.gwt.dev.asm.commons.Method;
+import com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidatorTest.VisibleErrorContext;
+import com.google.web.bindery.requestfactory.shared.BoxesAndPrimitivesTest;
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.ProxyFor;
+import com.google.web.bindery.requestfactory.shared.Request;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.Service;
+
+import java.util.Arrays;
+import java.util.logging.Logger;
+
+/**
+ * A JRE version of {@link BoxesAndPrimitivesTest} with additional validation
+ * tests.
+ */
+public class BoxesAndPrimitivesJreTest extends BoxesAndPrimitivesTest {
+
+  @Service(ServiceImpl.class)
+  interface ContextMismatchedParameterA extends RequestContext {
+    Request<Void> checkBoxed(int value);
+  }
+
+  @Service(ServiceImpl.class)
+  interface ContextMismatchedParameterB extends RequestContext {
+    Request<Void> checkPrimitive(Integer value);
+  }
+
+  @ProxyFor(Entity.class)
+  interface ProxyMismatchedGetterA extends EntityProxy {
+    int getBoxed();
+  }
+
+  @ProxyFor(Entity.class)
+  interface ProxyMismatchedGetterB extends EntityProxy {
+    Integer getPrimitive();
+  }
+
+  private VisibleErrorContext errors;
+  private RequestFactoryInterfaceValidator v;
+
+  @Override
+  public String getModuleName() {
+    return null;
+  }
+
+  public void test() {
+    RequestFactoryInterfaceValidator v = new RequestFactoryInterfaceValidator(
+        Logger.getAnonymousLogger(),
+        new RequestFactoryInterfaceValidator.ClassLoaderLoader(
+            getClass().getClassLoader()));
+    v.validateRequestFactory(Factory.class.getName());
+    assertFalse(v.isPoisoned());
+  }
+
+  /**
+   * Tests that mismatched primitive verses boxed getters are correctly
+   * reported.
+   */
+  public void testMismatchedGetters() {
+    v.validateEntityProxy(ProxyMismatchedGetterA.class.getName());
+    v.validateEntityProxy(ProxyMismatchedGetterB.class.getName());
+    assertTrue(v.isPoisoned());
+
+    String getBoxedMessage = RequestFactoryInterfaceValidator.messageCouldNotFindMethod(
+        Type.getType(Entity.class),
+        Arrays.asList(new Method("getBoxed", "()Ljava/lang/Integer;")));
+    String getPrimitiveMessage = RequestFactoryInterfaceValidator.messageCouldNotFindMethod(
+        Type.getType(Entity.class),
+        Arrays.asList(new Method("getPrimitive", "()I")));
+    assertEquals(Arrays.asList(getBoxedMessage, getPrimitiveMessage),
+        errors.logs);
+  }
+
+  /**
+   * Tests that mismatched parameter types are correctly reported.
+   */
+  public void testMismatchedParameters() {
+    v.validateRequestContext(ContextMismatchedParameterA.class.getName());
+    v.validateRequestContext(ContextMismatchedParameterB.class.getName());
+
+    String checkBoxedMessage = RequestFactoryInterfaceValidator.messageCouldNotFindMethod(
+        Type.getType(ServiceImpl.class),
+        Arrays.asList(new Method("checkBoxed", "(Ljava/lang/Integer;)V")));
+    String checkPrimitiveMessage = RequestFactoryInterfaceValidator.messageCouldNotFindMethod(
+        Type.getType(ServiceImpl.class),
+        Arrays.asList(new Method("checkPrimitive", "(I)V")));
+    assertEquals(Arrays.asList(checkBoxedMessage, checkPrimitiveMessage),
+        errors.logs);
+  }
+
+  @Override
+  protected Factory createFactory() {
+    return RequestFactoryJreTest.createInProcess(Factory.class);
+  }
+
+  @Override
+  protected void gwtSetUp() {
+    super.gwtSetUp();
+    errors = new VisibleErrorContext(Logger.getAnonymousLogger());
+    v = new RequestFactoryInterfaceValidator(errors,
+        new RequestFactoryInterfaceValidator.ClassLoaderLoader(
+            getClass().getClassLoader()));
+  }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/ComplexKeysJreTest.java b/user/test/com/google/web/bindery/requestfactory/server/ComplexKeysJreTest.java
new file mode 100644
index 0000000..44454a2
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/ComplexKeysJreTest.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.requestfactory.server;
+
+import com.google.web.bindery.requestfactory.shared.ComplexKeysTest;
+
+/**
+ * JRE version of ComplexKeysTest.
+ */
+public class ComplexKeysJreTest extends ComplexKeysTest {
+  @Override
+  public String getModuleName() {
+    return null;
+  }
+
+  @Override
+  protected Factory createFactory() {
+    return RequestFactoryJreTest.createInProcess(Factory.class);
+  }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/FindServiceJreTest.java b/user/test/com/google/web/bindery/requestfactory/server/FindServiceJreTest.java
new file mode 100644
index 0000000..d8fa59c
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/FindServiceJreTest.java
@@ -0,0 +1,35 @@
+/*
+ * 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.requestfactory.server;
+
+import com.google.web.bindery.requestfactory.gwt.client.FindServiceTest;
+import com.google.web.bindery.requestfactory.shared.SimpleRequestFactory;
+
+/**
+ * Run the FindService tests in-process.
+ */
+public class FindServiceJreTest extends FindServiceTest {
+
+  @Override
+  public String getModuleName() {
+    return null;
+  }
+
+  @Override
+  protected SimpleRequestFactory createFactory() {
+    return RequestFactoryJreTest.createInProcess(SimpleRequestFactory.class);
+  }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/HasId.java b/user/test/com/google/web/bindery/requestfactory/server/HasId.java
new file mode 100644
index 0000000..23b4c53
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/HasId.java
@@ -0,0 +1,25 @@
+/*
+ * 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.requestfactory.server;
+
+/**
+ * Used to test covariant return types.
+ */
+public interface HasId {
+  Object getId();
+
+  Object persistAndReturnSelf();
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/InstanceService.java b/user/test/com/google/web/bindery/requestfactory/server/InstanceService.java
new file mode 100644
index 0000000..26f5139
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/InstanceService.java
@@ -0,0 +1,24 @@
+/*
+ * 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.requestfactory.server;
+
+/**
+ * A service API that doesn't have static methods and that can't be
+ * default-instantiated.
+ */
+public interface InstanceService {
+  Integer add(int value);
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/InstanceServiceImpl.java b/user/test/com/google/web/bindery/requestfactory/server/InstanceServiceImpl.java
new file mode 100644
index 0000000..cd509b3
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/InstanceServiceImpl.java
@@ -0,0 +1,32 @@
+/*
+ * 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.requestfactory.server;
+
+/**
+ * A service API that doesn't have static methods and that can't be
+ * default-instantiated.
+ */
+public class InstanceServiceImpl implements InstanceService {
+  private final int base;
+
+  public InstanceServiceImpl(int base) {
+    this.base = base;
+  }
+
+  public Integer add(int value) {
+    return base + value;
+  }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/InstanceServiceLocator.java b/user/test/com/google/web/bindery/requestfactory/server/InstanceServiceLocator.java
new file mode 100644
index 0000000..131da3a
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/InstanceServiceLocator.java
@@ -0,0 +1,29 @@
+/*
+ * 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.requestfactory.server;
+
+import com.google.web.bindery.requestfactory.shared.ServiceLocator;
+
+/**
+ * Demonstrates instance-based service objects.
+ */
+public class InstanceServiceLocator implements ServiceLocator {
+
+  public Object getInstance(Class<?> clazz) {
+    assert InstanceService.class.equals(clazz);
+    return clazz.cast(new InstanceServiceImpl(5));
+  }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/LocatorJreTest.java b/user/test/com/google/web/bindery/requestfactory/server/LocatorJreTest.java
new file mode 100644
index 0000000..d500bf9
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/LocatorJreTest.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.requestfactory.server;
+
+import com.google.web.bindery.requestfactory.shared.LocatorTest;
+
+/**
+ * A JRE version of {@link LocatorTest}.
+ */
+public class LocatorJreTest extends LocatorTest {
+  @Override
+  public String getModuleName() {
+    return null;
+  }
+
+  @Override
+  protected Factory createFactory() {
+    return RequestFactoryJreTest.createInProcess(Factory.class);
+  }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryExceptionHandlerServlet.java b/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryExceptionHandlerServlet.java
new file mode 100644
index 0000000..a76d2b6
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryExceptionHandlerServlet.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.requestfactory.server;
+
+import com.google.web.bindery.requestfactory.shared.ServerFailure;
+
+/**
+ * A RequestFactoryServlet that forwards all exception information.
+ */
+public class RequestFactoryExceptionHandlerServlet
+    extends RequestFactoryServlet {
+  public RequestFactoryExceptionHandlerServlet() {
+    super(new ExceptionHandler() {
+      public ServerFailure createServerFailure(Throwable throwable) {
+        return new ServerFailure(throwable.getMessage(),
+            throwable.getClass().getName(), "my stack trace", true);
+      }
+    });
+  }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryExceptionPropagationJreTest.java b/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryExceptionPropagationJreTest.java
new file mode 100644
index 0000000..90eede7
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryExceptionPropagationJreTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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.requestfactory.server;
+
+import com.google.gwt.core.client.GWT;
+import com.google.web.bindery.requestfactory.gwt.client.RequestFactoryExceptionPropagationTest;
+import com.google.web.bindery.requestfactory.shared.Receiver;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.SimpleRequestFactory;
+
+/**
+ * JRE version of {@link RequestFactoryExceptionPropagationTest}.
+ */
+public class RequestFactoryExceptionPropagationJreTest extends
+    RequestFactoryExceptionPropagationTest {
+  @Override
+  public String getModuleName() {
+    return null;
+  }
+
+  @Override
+  protected SimpleRequestFactory createFactory() {
+    return RequestFactoryJreTest.createInProcess(SimpleRequestFactory.class);
+  }
+  
+  @Override
+  protected void fireContextAndCatch(RequestContext context,
+      Receiver<Void> receiver, GWT.UncaughtExceptionHandler exceptionHandler) {
+    try {
+      if (receiver == null) {
+        context.fire();
+      } else {
+        context.fire(receiver);
+      }
+    } catch (Throwable e) {
+      exceptionHandler.onUncaughtException(e);
+    }
+  }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidatorTest.java b/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
new file mode 100644
index 0000000..698b2b4
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
@@ -0,0 +1,493 @@
+/*
+ * 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.requestfactory.server;
+
+import com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidator.ClassLoaderLoader;
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.InstanceRequest;
+import com.google.web.bindery.requestfactory.shared.Locator;
+import com.google.web.bindery.requestfactory.shared.ProxyFor;
+import com.google.web.bindery.requestfactory.shared.ProxyForName;
+import com.google.web.bindery.requestfactory.shared.Request;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.RequestFactory;
+import com.google.web.bindery.requestfactory.shared.Service;
+import com.google.web.bindery.requestfactory.shared.ServiceName;
+import com.google.web.bindery.requestfactory.shared.SimpleRequestFactory;
+import com.google.web.bindery.requestfactory.shared.SkipInterfaceValidation;
+import com.google.web.bindery.requestfactory.shared.ValueProxy;
+import com.google.web.bindery.requestfactory.shared.impl.FindRequest;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * JRE tests for {@link RequestFactoryInterfaceValidator}.
+ */
+public class RequestFactoryInterfaceValidatorTest extends TestCase {
+  static class ClinitEntity {
+    static ClinitEntity findClinitEntity(String key) {
+      return null;
+    }
+
+    static ClinitEntity request() {
+      return null;
+    }
+
+    Object OBJECT = new Object();
+
+    String getId() {
+      return null;
+    }
+
+    int getVersion() {
+      return 0;
+    }
+  }
+
+  @ProxyFor(ClinitEntity.class)
+  interface ClinitEntityProxy extends EntityProxy {
+    Object OBJECT = new Object();
+  }
+
+  @Service(ClinitEntity.class)
+  interface ClinitRequestContext extends RequestContext {
+    Object OBJECT = new Object();
+
+    Request<ClinitEntityProxy> request();
+  }
+
+  interface ClinitRequestFactory extends RequestFactory {
+    Object OBJECT = new Object();
+
+    ClinitRequestContext context();
+  }
+
+  static class Domain {
+    static int fooStatic(int a) {
+      return 0;
+    }
+
+    int foo(int a) {
+      return 0;
+    }
+
+    java.sql.Date getSqlDate() {
+      return null;
+    }
+  }
+
+  @ProxyFor(Domain.class)
+  interface DomainProxy extends EntityProxy {
+  }
+
+  interface DomainProxyMissingAnnotation extends EntityProxy {
+  }
+
+  static class DomainWithOverloads {
+    void foo() {
+    }
+
+    void foo(int a) {
+    }
+
+    String getId() {
+      return null;
+    }
+
+    int getVersion() {
+      return 0;
+    }
+  }
+
+  @ProxyFor(DomainWithOverloads.class)
+  interface DomainWithOverloadsProxy extends EntityProxy {
+    void foo();
+  }
+
+  @ProxyFor(Domain.class)
+  interface DomainWithSqlDateProxy extends EntityProxy {
+    java.sql.Date getSqlDate();
+  }
+
+  class Foo {
+  }
+
+  @ProxyFor(HasListDomain.class)
+  interface HasList extends EntityProxy {
+    List<ReachableOnlyThroughReturnedList> getList();
+
+    void setList(List<ReachableOnlyThroughParamaterList> list);
+  }
+
+  static class HasListDomain extends Domain {
+    public String getId() {
+      return null;
+    }
+
+    public int getVersion() {
+      return 0;
+    }
+
+    List<Domain> getList() {
+      return null;
+    }
+
+    void setList(List<Domain> value) {
+    }
+  }
+
+  /**
+   * An entity type without the usual boilerplate.
+   */
+  class LocatorEntity {
+  }
+
+  class LocatorEntityLocator extends Locator<LocatorEntity, String> {
+    @Override
+    public LocatorEntity create(Class<? extends LocatorEntity> clazz) {
+      return null;
+    }
+
+    @Override
+    public LocatorEntity find(Class<? extends LocatorEntity> clazz, String id) {
+      return null;
+    }
+
+    @Override
+    public Class<LocatorEntity> getDomainType() {
+      return null;
+    }
+
+    @Override
+    public String getId(LocatorEntity domainObject) {
+      return null;
+    }
+
+    @Override
+    public Class<String> getIdType() {
+      return null;
+    }
+
+    @Override
+    public Object getVersion(LocatorEntity domainObject) {
+      return null;
+    }
+  }
+
+  @ProxyFor(value = LocatorEntity.class, locator = LocatorEntityLocator.class)
+  interface LocatorEntityProxy extends EntityProxy {
+  }
+
+  @ProxyForName(value = "com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidatorTest.LocatorEntity", locator = "badLocator")
+  interface LocatorEntityProxyWithBadLocator extends EntityProxy {
+  }
+
+  @ProxyForName(value = "badDomainType", locator = "com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidatorTest.LocatorEntityProxyWithBadServiceName")
+  interface LocatorEntityProxyWithBadServiceName extends EntityProxy {
+  }
+
+  @ProxyFor(value = Value.class)
+  interface MyValueProxy extends ValueProxy {
+  }
+
+  @ProxyFor(Domain.class)
+  interface ReachableOnlyThroughParamaterList extends EntityProxy {
+  }
+
+  @ProxyFor(Domain.class)
+  interface ReachableOnlyThroughReturnedList extends EntityProxy {
+  }
+
+  interface RequestContextMissingAnnotation extends RequestContext {
+  }
+
+  @Service(Domain.class)
+  interface ServiceRequestMismatchedArity extends RequestContext {
+    InstanceRequest<DomainProxy, Integer> foo(int a, int b);
+
+    Request<Integer> fooStatic(int a, int b);
+  }
+
+  @Service(Domain.class)
+  interface ServiceRequestMismatchedParam extends RequestContext {
+    Request<Integer> foo(long a);
+  }
+  @Service(Domain.class)
+  interface ServiceRequestMismatchedReturn extends RequestContext {
+    Request<Long> foo(int a);
+  }
+
+  @Service(Domain.class)
+  interface ServiceRequestMismatchedStatic extends RequestContext {
+    Request<Integer> foo(int a);
+
+    InstanceRequest<DomainProxy, Integer> fooStatic(int a);
+  }
+
+  @Service(Domain.class)
+  interface ServiceRequestMissingMethod extends RequestContext {
+    Request<Integer> doesNotExist(int a);
+  }
+
+  @Service(Domain.class)
+  interface SkipValidationContext extends RequestContext {
+    @SkipInterfaceValidation
+    Request<Integer> doesNotExist(int a);
+
+    @SkipInterfaceValidation
+    Request<Long> foo(int a);
+  }
+
+  @Service(Domain.class)
+  interface SkipValidationProxy extends ValueProxy {
+    @SkipInterfaceValidation
+    boolean doesNotExist();
+  }
+
+  @Service(Domain.class)
+  interface SkipValidationChecksReferredProxies extends ValueProxy {
+    @SkipInterfaceValidation
+    // still validates other proxies
+    DomainProxyMissingAnnotation getDomainProxyMissingAnnotation();
+  }
+
+  @ProxyFor(Domain.class)
+  @ProxyForName("Domain")
+  @Service(Domain.class)
+  @ServiceName("Domain")
+  interface TooManyAnnotations extends RequestContext {
+  }
+
+  static class UnexpectedIdAndVersionDomain {
+    Random getId() {
+      return null;
+    }
+
+    Random getVersion() {
+      return null;
+    }
+  }
+
+  @ProxyFor(UnexpectedIdAndVersionDomain.class)
+  interface UnexpectedIdAndVersionProxy extends EntityProxy {
+  }
+
+  static class Value {
+  }
+
+  static class VisibleErrorContext extends
+      RequestFactoryInterfaceValidator.ErrorContext {
+    final List<String> logs;
+
+    public VisibleErrorContext(Logger logger) {
+      super(logger);
+      logs = new ArrayList<String>();
+    }
+
+    public VisibleErrorContext(VisibleErrorContext that) {
+      super(that);
+      this.logs = that.logs;
+    }
+
+    @Override
+    public void poison(String msg, Object... args) {
+      logs.add(String.format(msg, args));
+      super.poison(msg, args);
+    }
+
+    @Override
+    public void poison(String msg, Throwable t) {
+      logs.add(msg);
+      super.poison(msg, t);
+    }
+
+    @Override
+    protected VisibleErrorContext fork() {
+      return new VisibleErrorContext(this);
+    }
+  }
+
+  RequestFactoryInterfaceValidator v;;
+
+  private static final boolean DUMP_PAYLOAD = Boolean.getBoolean("gwt.rpc.dumpPayload");;
+
+  private VisibleErrorContext errors;
+
+  /**
+   * Ensure that calling {@link RequestFactoryInterfaceValidator#antidote()}
+   * doesn't cause information to be lost.
+   */
+  public void testAntidote() {
+    v.validateRequestContext(RequestContextMissingAnnotation.class.getName());
+    assertTrue(v.isPoisoned());
+    v.antidote();
+    assertFalse(v.isPoisoned());
+    v.validateRequestContext(RequestContextMissingAnnotation.class.getName());
+    assertTrue(v.isPoisoned());
+  }
+
+  public void testBadLocatorName() {
+    v.validateEntityProxy(LocatorEntityProxyWithBadLocator.class.getName());
+    assertTrue(v.isPoisoned());
+    assertTrue(errors.logs.contains("Cannot find locator named badLocator"));
+  }
+
+  public void testBadServiceName() {
+    v.validateEntityProxy(LocatorEntityProxyWithBadServiceName.class.getName());
+    assertTrue(v.isPoisoned());
+    assertTrue(errors.logs.contains("Cannot find domain type named badDomainType"));
+  }
+
+  /**
+   * Test that subclasses of {@code java.util.Date} are not transportable.
+   */
+  public void testDateSubclass() {
+    v.validateEntityProxy(DomainWithSqlDateProxy.class.getName());
+    assertTrue(v.isPoisoned());
+  }
+
+  /**
+   * Test the {@link FindRequest} context used to implement find().
+   */
+  public void testFindRequestContext() {
+    v.validateRequestContext(FindRequest.class.getName());
+  }
+
+  /**
+   * Make sure that proxy types referenced through type parameters of method
+   * return types and paramaters types are examined.
+   */
+  public void testFollowingTypeParameters() {
+    v.validateEntityProxy(HasList.class.getName());
+    assertNotNull(v.getEntityProxyTypeName(HasListDomain.class.getName(),
+        HasList.class.getName()));
+    assertNotNull(v.getEntityProxyTypeName(Domain.class.getName(),
+        ReachableOnlyThroughParamaterList.class.getName()));
+    assertNotNull(v.getEntityProxyTypeName(Domain.class.getName(),
+        ReachableOnlyThroughReturnedList.class.getName()));
+  }
+
+  /**
+   * Ensure that the &lt;clinit> methods don't interfere with validation.
+   */
+  public void testIntecfacesWithClinits() {
+    v.validateRequestFactory(ClinitRequestFactory.class.getName());
+    assertFalse(v.isPoisoned());
+  }
+
+  public void testLocatorProxy() {
+    v.validateEntityProxy(LocatorEntityProxy.class.getName());
+    assertFalse(v.isPoisoned());
+  }
+
+  public void testMismatchedArity() {
+    v.validateRequestContext(ServiceRequestMismatchedArity.class.getName());
+    assertTrue(v.isPoisoned());
+  }
+
+  public void testMismatchedParamType() {
+    v.validateRequestContext(ServiceRequestMismatchedParam.class.getName());
+    assertTrue(v.isPoisoned());
+  }
+
+  public void testMismatchedReturnType() {
+    v.validateRequestContext(ServiceRequestMismatchedReturn.class.getName());
+    assertTrue(v.isPoisoned());
+  }
+
+  public void testMismatchedStatic() {
+    v.validateRequestContext(ServiceRequestMismatchedStatic.class.getName());
+    assertTrue(v.isPoisoned());
+  }
+
+  public void testMissingDomainAnnotations() {
+    v.validateEntityProxy(DomainProxyMissingAnnotation.class.getName());
+    assertTrue(v.isPoisoned());
+  }
+
+  public void testMissingIdAndVersion() {
+    v.validateEntityProxy(DomainProxy.class.getName());
+    assertTrue(v.isPoisoned());
+  }
+
+  public void testMissingMethod() {
+    v.validateRequestContext(ServiceRequestMissingMethod.class.getName());
+    assertTrue(v.isPoisoned());
+  }
+
+  public void testMissingServiceAnnotations() {
+    v.validateRequestContext(RequestContextMissingAnnotation.class.getName());
+    assertTrue(v.isPoisoned());
+  }
+
+  public void testOverloadedMethod() {
+    v.validateEntityProxy(DomainWithOverloadsProxy.class.getName());
+    assertTrue(v.isPoisoned());
+  }
+
+  public void testSkipValidationChecksReferredProxies() {
+    v.validateValueProxy(SkipValidationChecksReferredProxies.class.getName());
+    assertTrue(v.isPoisoned());
+  }
+
+  public void testSkipValidationContext() {
+    v.validateRequestContext(SkipValidationContext.class.getName());
+    assertFalse(v.isPoisoned());
+  }
+
+  public void testSkipValidationProxy() {
+    v.validateValueProxy(SkipValidationProxy.class.getName());
+    assertFalse(v.isPoisoned());
+  }
+
+  /**
+   * Perform a full test of the RequestFactory used for most tests.
+   */
+  public void testTestCodeFactories() {
+    v.validateRequestFactory(SimpleRequestFactory.class.getName());
+    assertFalse(v.isPoisoned());
+  }
+
+  public void testTooManyAnnotations() {
+    v.validateRequestContext(TooManyAnnotations.class.getName());
+    assertTrue(v.isPoisoned());
+  }
+
+  public void testUnexpectedIdAndVersion() {
+    v.validateEntityProxy(UnexpectedIdAndVersionProxy.class.getName());
+    assertTrue(v.isPoisoned());
+  }
+
+  public void testValueType() {
+    v.validateValueProxy(MyValueProxy.class.getName());
+    assertFalse(v.isPoisoned());
+  }
+
+  @Override
+  protected void setUp() throws Exception {
+    Logger logger = Logger.getLogger("");
+    logger.setLevel(DUMP_PAYLOAD ? Level.ALL : Level.OFF);
+    errors = new VisibleErrorContext(logger);
+    v = new RequestFactoryInterfaceValidator(errors, new ClassLoaderLoader(
+        Thread.currentThread().getContextClassLoader()));
+  }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryJreTest.java b/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryJreTest.java
new file mode 100644
index 0000000..d17b608
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryJreTest.java
@@ -0,0 +1,49 @@
+/*
+ * 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.requestfactory.server;
+
+import com.google.web.bindery.event.shared.EventBus;
+import com.google.web.bindery.event.shared.SimpleEventBus;
+import com.google.web.bindery.requestfactory.gwt.client.RequestFactoryTest;
+import com.google.web.bindery.requestfactory.server.testing.InProcessRequestTransport;
+import com.google.web.bindery.requestfactory.shared.RequestFactory;
+import com.google.web.bindery.requestfactory.shared.SimpleRequestFactory;
+import com.google.web.bindery.requestfactory.vm.RequestFactorySource;
+
+/**
+ * Runs the RequestFactory tests in-process.
+ */
+public class RequestFactoryJreTest extends RequestFactoryTest {
+
+  public static <T extends RequestFactory> T createInProcess(Class<T> clazz) {
+    EventBus eventBus = new SimpleEventBus();
+    T req = RequestFactorySource.create(clazz);
+    ServiceLayer serviceLayer = ServiceLayer.create();
+    SimpleRequestProcessor processor = new SimpleRequestProcessor(serviceLayer);
+    req.initialize(eventBus, new InProcessRequestTransport(processor));
+    return req;
+  }
+
+  @Override
+  public String getModuleName() {
+    return null;
+  }
+
+  @Override
+  protected SimpleRequestFactory createFactory() {
+    return createInProcess(SimpleRequestFactory.class);
+  }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryUnicodeEscapingJreTest.java b/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryUnicodeEscapingJreTest.java
new file mode 100644
index 0000000..16c4601
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryUnicodeEscapingJreTest.java
@@ -0,0 +1,35 @@
+/*
+ * 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.requestfactory.server;
+
+import com.google.web.bindery.requestfactory.gwt.client.RequestFactoryUnicodeEscapingTest;
+import com.google.web.bindery.requestfactory.shared.SimpleRequestFactory;
+
+/**
+ * A JRE implementation of {@link RequestFactoryUnicodeEscapingTest}.
+ */
+public class RequestFactoryUnicodeEscapingJreTest extends
+    RequestFactoryUnicodeEscapingTest {
+  @Override
+  public String getModuleName() {
+    return null;
+  }
+
+  @Override
+  protected SimpleRequestFactory createFactory() {
+    return RequestFactoryJreTest.createInProcess(SimpleRequestFactory.class);
+  }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/ServiceInheritanceJreTest.java b/user/test/com/google/web/bindery/requestfactory/server/ServiceInheritanceJreTest.java
new file mode 100644
index 0000000..8f209de
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/ServiceInheritanceJreTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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.requestfactory.server;
+
+import com.google.web.bindery.requestfactory.shared.ServiceInheritanceTest;
+
+/**
+ * A JRE version of {@link ServiceInheritanceTest}.
+ */
+public class ServiceInheritanceJreTest extends ServiceInheritanceTest {
+  @Override
+  public String getModuleName() {
+    return null;
+  }
+
+  @Override
+  protected Factory createFactory() {
+    return RequestFactoryJreTest.createInProcess(Factory.class);
+  }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/ServiceLocatorTest.java b/user/test/com/google/web/bindery/requestfactory/server/ServiceLocatorTest.java
new file mode 100644
index 0000000..3fee0cd
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/ServiceLocatorTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.requestfactory.server;
+
+import com.google.web.bindery.requestfactory.shared.ServiceLocator;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests creating of ServiceLocators with custom ServiceLayerDecorators
+ */
+public class ServiceLocatorTest extends TestCase {
+
+  static class CustomLocatorLayer extends ServiceLayerDecorator {
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T extends ServiceLocator> T createServiceLocator(Class<T> clazz) {
+      return (T) ServiceLocatorImpl.INSTANCE;
+    }
+  }
+
+  static class ServiceLocatorImpl implements ServiceLocator {
+    static final ServiceLocatorImpl INSTANCE = new ServiceLocatorImpl();
+
+    public Object getInstance(Class<?> clazz) {
+      return new Object();
+    }
+  }
+
+  public void testGetsServiceLocatorFromDecorator() {
+    ServiceLayer layer = ServiceLayer.create(new CustomLocatorLayer());
+    ServiceLocatorImpl locator = layer.createServiceLocator(ServiceLocatorImpl.class);
+    assertSame(ServiceLocatorImpl.INSTANCE, locator);
+  }
+
+  public void testInstantiatesServiceLocatorByDefault() {
+    ServiceLayer layer = ServiceLayer.create();
+    ServiceLocatorImpl locator = layer.createServiceLocator(ServiceLocatorImpl.class);
+    assertNotNull(locator);
+    assertNotSame(ServiceLocatorImpl.INSTANCE, locator);
+  }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/SimpleBar.java b/user/test/com/google/web/bindery/requestfactory/server/SimpleBar.java
new file mode 100644
index 0000000..5b8e40c
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/SimpleBar.java
@@ -0,0 +1,198 @@
+/*
+ * 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.requestfactory.server;
+
+import com.google.gwt.dev.util.collect.HashSet;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Domain object for SimpleFooRequest.
+ */
+public class SimpleBar implements HasId {
+  /**
+   * DO NOT USE THIS UGLY HACK DIRECTLY! Call {@link #get} instead.
+   */
+  private static Map<String, SimpleBar> jreTestSingleton = new HashMap<String, SimpleBar>();
+
+  private static long nextId = 2L;
+
+  static {
+    try {
+    reset();
+    } catch (Throwable t) {
+      t.printStackTrace();
+    }
+  }
+
+  public static Long countSimpleBar() {
+    return (long) get().size();
+  }
+
+  public static List<SimpleBar> findAll() {
+    return new ArrayList<SimpleBar>(get().values());
+  }
+
+  public static Set<SimpleBar> findAsSet() {
+    return new HashSet<SimpleBar>(get().values());
+  }
+
+  public static SimpleBar findSimpleBar(String id) {
+    return findSimpleBarById(id);
+  }
+
+  /**
+   * Returns <code>null</code> if {@link #findFails} is <code>true</code>.
+   */
+  public static SimpleBar findSimpleBarById(String id) {
+    SimpleBar toReturn = get().get(id);
+    return (toReturn == null || toReturn.findFails) ? null : toReturn;
+  }
+
+  @SuppressWarnings("unchecked")
+  public static synchronized Map<String, SimpleBar> get() {
+    HttpServletRequest req = RequestFactoryServlet.getThreadLocalRequest();
+    if (req == null) {
+      // May be in a JRE test case, use the the singleton
+      return jreTestSingleton;
+    } else {
+      /*
+       * This will not behave entirely correctly unless we have a servlet filter
+       * that doesn't allow any requests to be processed unless they're
+       * associated with an existing session.
+       */
+      Map<String, SimpleBar> value = (Map<String, SimpleBar>) req.getSession().getAttribute(
+          SimpleBar.class.getCanonicalName());
+      if (value == null) {
+        value = resetImpl();
+      }
+      return value;
+    }
+  }
+
+  public static SimpleBar getSingleton() {
+    return findSimpleBar("1L");
+  }
+
+  public static void reset() {
+    resetImpl();
+  }
+
+  public static synchronized Map<String, SimpleBar> resetImpl() {
+    Map<String, SimpleBar> instance = new HashMap<String, SimpleBar>();
+    // fixtures
+    SimpleBar s1 = new SimpleBar();
+    s1.setId("1L");
+    s1.isNew = false;
+    instance.put(s1.getId(), s1);
+
+    SimpleBar s2 = new SimpleBar();
+    s2.setId("999L");
+    s2.isNew = false;
+    instance.put(s2.getId(), s2);
+
+    HttpServletRequest req = RequestFactoryServlet.getThreadLocalRequest();
+    if (req == null) {
+      jreTestSingleton = instance;
+    } else {
+      req.getSession().setAttribute(SimpleBar.class.getCanonicalName(),
+          instance);
+    }
+    return instance;
+  }
+
+  public static SimpleBar returnFirst(List<SimpleBar> list) {
+    SimpleBar toReturn = list.get(0);
+    return toReturn;
+  }
+
+  Integer version = 1;
+
+  private String id = "999L";
+  private boolean findFails;
+  private boolean isNew = true;
+  private boolean unpersisted;
+  private String userName;
+
+  public SimpleBar() {
+    version = 1;
+    userName = "FOO";
+  }
+
+  public void delete() {
+    get().remove(getId());
+  }
+
+  public Boolean getFindFails() {
+    return findFails;
+  }
+
+  public String getId() {
+    return unpersisted ? null : id;
+  }
+
+  public Boolean getUnpersisted() {
+    return unpersisted;
+  }
+
+  public String getUserName() {
+    return userName;
+  }
+
+  public Integer getVersion() {
+    return unpersisted ? null : version;
+  }
+
+  public void persist() {
+    if (isNew) {
+      setId(Long.toString(nextId++));
+      isNew = false;
+      get().put(getId(), this);
+    }
+    version++;
+  }
+
+  public SimpleBar persistAndReturnSelf() {
+    persist();
+    return this;
+  }
+
+  public void setFindFails(Boolean fails) {
+    this.findFails = fails;
+  }
+
+  public void setId(String id) {
+    this.id = id;
+  }
+
+  public void setUnpersisted(Boolean unpersisted) {
+    this.unpersisted = unpersisted;
+  }
+
+  public void setUserName(String userName) {
+    this.userName = userName;
+  }
+
+  public void setVersion(Integer version) {
+    this.version = version;
+  }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/SimpleFoo.java b/user/test/com/google/web/bindery/requestfactory/server/SimpleFoo.java
new file mode 100644
index 0000000..1f3e61e
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/SimpleFoo.java
@@ -0,0 +1,875 @@
+/*
+ * 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.requestfactory.server;
+
+import com.google.web.bindery.requestfactory.shared.OnlyUsedByRequestContextMethod;
+import com.google.web.bindery.requestfactory.shared.SimpleEnum;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.validation.constraints.Size;
+
+/**
+ * Domain object for SimpleFooRequest.
+ */
+public class SimpleFoo {
+  /**
+   * DO NOT USE THIS UGLY HACK DIRECTLY! Call {@link #get} instead.
+   */
+  private static Map<Long, SimpleFoo> jreTestSingleton;
+
+  private static Long nextId = 1L;
+
+  public static Integer add(Integer a, int b) {
+    return a + b;
+  }
+
+  public static Long countSimpleFoo() {
+    return (long) get().size();
+  }
+
+  public static SimpleFoo echo(SimpleFoo simpleFoo) {
+    return simpleFoo;
+  }
+
+  public static SimpleFoo echoComplex(SimpleFoo simpleFoo, SimpleBar simpleBar) {
+    simpleFoo.setBarField(simpleBar);
+    return simpleFoo;
+  }
+
+  public static SimpleFoo fetchDoubleReference() {
+    SimpleFoo foo = new SimpleFoo();
+    SimpleFoo foo2 = new SimpleFoo();
+    foo.setFooField(foo2);
+    foo.setSelfOneToManyField(Arrays.asList(foo2));
+    foo.persist();
+    foo2.persist();
+    return foo;
+  }
+
+  public static List<SimpleFoo> findAll() {
+    return new ArrayList<SimpleFoo>(get().values());
+  }
+
+  public static SimpleFoo findSimpleFoo(Long id) {
+    return findSimpleFooById(id);
+  }
+
+  public static SimpleFoo findSimpleFooById(Long id) {
+    return get().get(id);
+  }
+
+  @SuppressWarnings("unchecked")
+  public static synchronized Map<Long, SimpleFoo> get() {
+    HttpServletRequest req = RequestFactoryServlet.getThreadLocalRequest();
+    if (req == null) {
+      // May be in a JRE test case, use the singleton
+      if (jreTestSingleton == null) {
+        jreTestSingleton = resetImpl();
+      }
+      return jreTestSingleton;
+    } else {
+      /*
+       * This will not behave entirely correctly unless we have a servlet filter
+       * that doesn't allow any requests to be processed unless they're
+       * associated with an existing session.
+       */
+      Map<Long, SimpleFoo> value = (Map<Long, SimpleFoo>) req.getSession().getAttribute(
+          SimpleFoo.class.getCanonicalName());
+      if (value == null) {
+        value = resetImpl();
+      }
+      return value;
+    }
+  }
+
+  public static List<Integer> getNumberList() {
+    ArrayList<Integer> list = new ArrayList<Integer>();
+    list.add(1);
+    list.add(2);
+    list.add(3);
+    return list;
+  }
+
+  public static Set<Integer> getNumberSet() {
+    Set<Integer> list = new HashSet<Integer>();
+    list.add(1);
+    list.add(2);
+    list.add(3);
+    return list;
+  }
+
+  /**
+   * This tests that the server detects and disallows the use of persisted
+   * objects with a null version property.
+   */
+  public static SimpleFoo getSimpleFooWithNullVersion() {
+    System.err.println("The following exception about an entity with a null"
+        + " version is expected");
+    SimpleFoo foo = new SimpleFoo();
+    foo.setVersion(null);
+    return foo;
+  }
+
+  public static SimpleFoo getSimpleFooWithSubPropertyCollection() {
+    SimpleFoo foo = new SimpleFoo();
+    SimpleFoo subFoo = new SimpleFoo();
+    SimpleFoo subSubFoo = new SimpleFoo();
+    subFoo.setFooField(subSubFoo);
+    subSubFoo.setUserName("I'm here");
+    subSubFoo.persist();
+    subFoo.persist();
+    foo.persist();
+    foo.setSelfOneToManyField(Arrays.asList(subFoo));
+    return foo;
+  }
+
+  public static SimpleFoo getTripletReference() {
+    SimpleFoo foo1 = new SimpleFoo();
+    SimpleFoo foo2 = new SimpleFoo();
+    SimpleFoo foo3 = new SimpleFoo();
+    ArrayList<SimpleFoo> foos = new ArrayList<SimpleFoo>();
+    foos.add(foo2);
+    ArrayList<SimpleFoo> subFoos = new ArrayList<SimpleFoo>();
+    subFoos.add(foo3);
+    foo1.setSelfOneToManyField(foos);
+    foo2.setSelfOneToManyField(subFoos);
+    foo3.setFooField(foo2);
+    foo1.persist();
+    foo2.persist();
+    foo3.persist();
+    return foo1;
+  }
+
+  public static SimpleFoo getUnpersistedInstance() {
+    SimpleFoo foo = new SimpleFoo();
+    foo.setUnpersisted(true);
+    return foo;
+  }
+
+  public static void pleaseCrash(Integer crashIf42or43) throws Exception {
+    if (crashIf42or43 == 42) {
+      throw new UnsupportedOperationException(
+          "THIS EXCEPTION IS EXPECTED BY A TEST");
+    }
+    if (crashIf42or43 == 43) {
+      throw new Exception("THIS EXCEPTION IS EXPECTED BY A TEST");
+    }
+  }
+
+  /**
+   * Check client-side upcasting to BigDecimal and return a list of BigDecimals
+   * that should be upcast.
+   */
+  public static List<BigDecimal> processBigDecimalList(List<BigDecimal> values) {
+    List<BigDecimal> toReturn = new ArrayList<BigDecimal>();
+    toReturn.add(BigDecimal.TEN);
+    toReturn.add(new BigDecimal("12345.6789") {
+      // This is an anonymous subtype
+    });
+    if (!toReturn.equals(values)) {
+      throw new IllegalArgumentException(toReturn + " != " + values);
+    }
+    return toReturn;
+  }
+
+  /**
+   * Check client-side upcasting to BigInteger and return a list of BigIntegers
+   * that should be upcast.
+   */
+  public static List<BigInteger> processBigIntegerList(List<BigInteger> values) {
+    List<BigInteger> toReturn = new ArrayList<BigInteger>();
+    toReturn.add(BigInteger.TEN);
+    toReturn.add(new BigInteger("12345") {
+      // This is an anonymous subtype
+    });
+    if (!toReturn.equals(values)) {
+      throw new IllegalArgumentException(toReturn + " != " + values);
+    }
+    return toReturn;
+  }
+
+  public static Boolean processBooleanList(List<Boolean> values) {
+    return values.get(0);
+  }
+
+  /**
+   * Check client-side upcasting to Date and return a list of Dates that should
+   * be upcast.
+   */
+  @SuppressWarnings("deprecation")
+  public static List<Date> processDateList(List<Date> values) {
+    // Keep these values in sync with SimpleFoo.processDateList
+    Date date = new Date(90, 0, 1);
+    java.sql.Date sqlDate = new java.sql.Date(90, 0, 2);
+    Time sqlTime = new Time(1, 2, 3);
+    Timestamp sqlTimestamp = new Timestamp(12345L);
+    List<Date> toReturn = Arrays.asList(date, sqlDate, sqlTime, sqlTimestamp);
+
+    if (toReturn.size() != values.size()) {
+      throw new IllegalArgumentException("size");
+    }
+
+    Iterator<Date> expected = toReturn.iterator();
+    Iterator<Date> actual = values.iterator();
+    while (expected.hasNext()) {
+      Date expectedDate = expected.next();
+      long expectedTime = expectedDate.getTime();
+      long actualTime = actual.next().getTime();
+      if (expectedTime != actualTime) {
+        throw new IllegalArgumentException(expectedDate.getClass().getName()
+            + " " + expectedTime + " != " + actualTime);
+      }
+    }
+
+    return toReturn;
+  }
+
+  public static SimpleEnum processEnumList(List<SimpleEnum> values) {
+    return values.get(0);
+  }
+
+  public static String processString(String string) {
+    return string;
+  }
+
+  public static void receiveEnum(OnlyUsedByRequestContextMethod value) {
+    if (value != OnlyUsedByRequestContextMethod.FOO) {
+      throw new IllegalArgumentException("Expecting FOO, received " + value);
+    }
+  }
+
+  public static void receiveNullList(List<SimpleFoo> value) {
+    if (value != null) {
+      throw new IllegalArgumentException(
+          "Expected value to be null. Actual value: \"" + value + "\"");
+    }
+  }
+
+  public static void receiveNullSimpleFoo(SimpleFoo value) {
+    if (value != null) {
+      throw new IllegalArgumentException(
+          "Expected value to be null. Actual value: \"" + value + "\"");
+    }
+  }
+
+  public static void receiveNullString(String value) {
+    if (value != null) {
+      throw new IllegalArgumentException(
+          "Expected value to be null. Actual value: \"" + value + "\"");
+    }
+  }
+
+  public static void receiveNullValueInEntityList(List<SimpleFoo> list) {
+    if (list == null) {
+      throw new IllegalArgumentException("Expected list to be non null.");
+    } else if (list.size() != 2) {
+      throw new IllegalArgumentException("Expected list to contain two items.");
+    } else if (list.get(0) == null) {
+      throw new IllegalArgumentException(
+          "Expected list.get(0) to return non null.");
+    } else if (list.get(1) != null) {
+      throw new IllegalArgumentException(
+          "Expected list.get(1) to return null. Actual: " + list.get(1));
+    }
+  }
+
+  public static void receiveNullValueInIntegerList(List<Integer> list) {
+    if (list == null) {
+      throw new IllegalArgumentException("Expected list to be non null.");
+    } else if (list.size() != 3) {
+      throw new IllegalArgumentException(
+          "Expected list to contain three items.");
+    } else if (list.get(0) == null || list.get(1) == null) {
+      throw new IllegalArgumentException(
+          "Expected list.get(0)/get(1) to return non null.");
+    } else if (list.get(2) != null) {
+      throw new IllegalArgumentException(
+          "Expected list.get(2) to return null. Actual: \"" + list.get(2)
+              + "\"");
+    }
+  }
+
+  public static void receiveNullValueInStringList(List<String> list) {
+    if (list == null) {
+      throw new IllegalArgumentException("Expected list to be non null.");
+    } else if (list.size() != 3) {
+      throw new IllegalArgumentException(
+          "Expected list to contain three items.");
+    } else if (list.get(0) == null || list.get(1) == null) {
+      throw new IllegalArgumentException(
+          "Expected list.get(0)/get(1) to return non null.");
+    } else if (list.get(2) != null) {
+      throw new IllegalArgumentException(
+          "Expected list.get(2) to return null. Actual: \"" + list.get(2)
+              + "\"");
+    }
+  }
+
+  public static void reset() {
+    resetImpl();
+  }
+
+  public static synchronized Map<Long, SimpleFoo> resetImpl() {
+    Map<Long, SimpleFoo> instance = new HashMap<Long, SimpleFoo>();
+    // fixtures
+    SimpleFoo s1 = new SimpleFoo();
+    s1.setId(1L);
+    s1.isNew = false;
+    instance.put(s1.getId(), s1);
+
+    SimpleFoo s2 = new SimpleFoo();
+    s2.setId(999L);
+    s2.isNew = false;
+    instance.put(s2.getId(), s2);
+
+    HttpServletRequest req = RequestFactoryServlet.getThreadLocalRequest();
+    if (req == null) {
+      jreTestSingleton = instance;
+    } else {
+      req.getSession().setAttribute(SimpleFoo.class.getCanonicalName(),
+          instance);
+    }
+    return instance;
+  }
+
+  public static List<SimpleFoo> returnNullList() {
+    return null;
+  }
+
+  public static SimpleFoo returnNullSimpleFoo() {
+    return null;
+  }
+
+  public static String returnNullString() {
+    return null;
+  }
+
+  public static void returnOnlyUsedInParameterization(List<SimpleFoo> values) {
+  }
+
+  public static SimpleFoo returnSimpleFooSubclass() {
+    return new SimpleFoo() {
+    };
+  }
+
+  public static SimpleValue returnValueProxy() {
+    SimpleValue toReturn = new SimpleValue();
+    toReturn.setNumber(42);
+    toReturn.setString("Hello world!");
+    toReturn.setDate(new Date());
+    return toReturn;
+  }
+
+  @SuppressWarnings("unused")
+  private static Integer privateMethod() {
+    return 0;
+  }
+
+  Integer version = 1;
+
+  private Long id = 1L;
+  private boolean isNew = true;
+
+  @Size(min = 3, max = 30)
+  private String userName;
+  private String password;
+
+  private Character charField;
+  private Long longField;
+
+  private BigDecimal bigDecimalField;
+
+  private BigInteger bigIntField;
+  private Integer intId = -1;
+  private Short shortField;
+
+  private Byte byteField;
+
+  private Date created;
+  private Double doubleField;
+
+  private Float floatField;
+
+  private SimpleEnum enumField;
+  private Boolean boolField;
+
+  private Boolean otherBoolField;
+  private Integer pleaseCrash;
+
+  private SimpleBar barField;
+  private SimpleFoo fooField;
+
+  private String nullField;
+  private SimpleBar barNullField;
+
+  private List<SimpleBar> oneToManyField;
+  private List<SimpleFoo> selfOneToManyField;
+  private Set<SimpleBar> oneToManySetField;
+
+  private List<Integer> numberListField;
+
+  private SimpleValue simpleValueField;
+
+  /*
+   * isChanged is just a quick-and-dirty way to get version-ing for now.
+   * Currently, only set by setUserName and setIntId. TODO for later: Use a
+   * cleaner solution to figure out when to increment version numbers.
+   */
+  boolean isChanged;
+
+  private boolean unpersisted;
+
+  public SimpleFoo() {
+    intId = 42;
+    version = 1;
+    userName = "GWT";
+    longField = 8L;
+    enumField = SimpleEnum.FOO;
+    created = new Date();
+    barField = SimpleBar.getSingleton();
+    boolField = true;
+    oneToManyField = new ArrayList<SimpleBar>();
+    oneToManyField.add(barField);
+    oneToManyField.add(barField);
+    numberListField = new ArrayList<Integer>();
+    numberListField.add(42);
+    numberListField.add(99);
+    selfOneToManyField = new ArrayList<SimpleFoo>();
+    selfOneToManyField.add(this);
+    oneToManySetField = new HashSet<SimpleBar>();
+    oneToManySetField.add(barField);
+    nullField = null;
+    barNullField = null;
+    pleaseCrash = 0;
+    isChanged = false;
+  }
+
+  public Long countSimpleFooWithUserNameSideEffect() {
+    findSimpleFoo(1L).setUserName(userName);
+    version++;
+    return countSimpleFoo();
+  }
+
+  public void deleteBar() {
+    if (barField != null) {
+      isChanged = true;
+      barField.delete();
+    }
+    barField = null;
+    persist();
+  }
+
+  public SimpleBar getBarField() {
+    return barField;
+  }
+
+  public SimpleBar getBarNullField() {
+    return barNullField;
+  }
+
+  /**
+   * Returns the bigDecimalField.
+   */
+  public BigDecimal getBigDecimalField() {
+    return bigDecimalField;
+  }
+
+  /**
+   * Returns the bigIntegerField.
+   */
+  public BigInteger getBigIntField() {
+    return bigIntField;
+  }
+
+  public Boolean getBoolField() {
+    return boolField;
+  }
+
+  /**
+   * Returns the byteField.
+   */
+  public Byte getByteField() {
+    return byteField;
+  }
+
+  /**
+   * Returns the charField.
+   */
+  public Character getCharField() {
+    return charField;
+  }
+
+  public Date getCreated() {
+    return created;
+  }
+
+  /**
+   * Returns the doubleField.
+   */
+  public Double getDoubleField() {
+    return doubleField;
+  }
+
+  public SimpleEnum getEnumField() {
+    return enumField;
+  }
+
+  /**
+   * Returns the floatField.
+   */
+  public Float getFloatField() {
+    return floatField;
+  }
+
+  public SimpleFoo getFooField() {
+    return fooField;
+  }
+
+  public Long getId() {
+    return unpersisted ? null : id;
+  }
+
+  public Integer getIntId() {
+    return intId;
+  }
+
+  public Long getLongField() {
+    return longField;
+  }
+
+  public String getNullField() {
+    return nullField;
+  }
+
+  public List<Integer> getNumberListField() {
+    return numberListField;
+  }
+
+  public List<SimpleBar> getOneToManyField() {
+    return oneToManyField;
+  }
+
+  public Set<SimpleBar> getOneToManySetField() {
+    return oneToManySetField;
+  }
+
+  /**
+   * Returns the otherBoolField.
+   */
+  public Boolean getOtherBoolField() {
+    return otherBoolField;
+  }
+
+  public String getPassword() {
+    return password;
+  }
+
+  public Integer getPleaseCrash() {
+    return pleaseCrash;
+  }
+
+  public List<SimpleFoo> getSelfOneToManyField() {
+    return selfOneToManyField;
+  }
+
+  /**
+   * Returns the shortField.
+   */
+  public Short getShortField() {
+    return shortField;
+  }
+
+  public SimpleValue getSimpleValue() {
+    return simpleValueField;
+  }
+
+  public List<SimpleValue> getSimpleValues() {
+    return Arrays.asList(simpleValueField);
+  }
+
+  public boolean getUnpersisted() {
+    return unpersisted;
+  }
+
+  public String getUserName() {
+    return userName;
+  }
+
+  public Integer getVersion() {
+    return unpersisted ? null : version;
+  }
+
+  public String hello(SimpleBar bar) {
+    return "Greetings " + bar.getUserName() + " from " + getUserName();
+  }
+
+  public void persist() {
+    if (isNew) {
+      setId(nextId++);
+      isNew = false;
+      get().put(getId(), this);
+    }
+    if (isChanged) {
+      version++;
+      isChanged = false;
+    }
+  }
+
+  public SimpleFoo persistAndReturnSelf() {
+    persist();
+    return this;
+  }
+
+  public SimpleFoo persistCascadingAndReturnSelf() {
+    persistCascadingAndReturnSelfImpl(new HashSet<SimpleFoo>());
+    return this;
+  }
+
+  public String processList(List<SimpleFoo> values) {
+    String result = "";
+    for (SimpleFoo n : values) {
+      result += n.getUserName();
+    }
+    return result;
+  }
+
+  public void receiveNull(String value) {
+    if (value != null) {
+      throw new IllegalArgumentException(
+          "Expected value to be null. Actual value: \"" + value + "\"");
+    }
+  }
+
+  public void setBarField(SimpleBar barField) {
+    this.barField = barField;
+  }
+
+  public void setBarNullField(SimpleBar barNullField) {
+    this.barNullField = barNullField;
+  }
+
+  /**
+   * @param bigDecimalField the bigDecimalField to set
+   */
+  public void setBigDecimalField(BigDecimal bigDecimalField) {
+    this.bigDecimalField = bigDecimalField;
+  }
+
+  /**
+   * @param bigIntegerField the bigIntegerField to set
+   */
+  public void setBigIntField(BigInteger bigIntegerField) {
+    this.bigIntField = bigIntegerField;
+  }
+
+  public void setBoolField(Boolean bool) {
+    boolField = bool;
+  }
+
+  /**
+   * @param byteField the byteField to set
+   */
+  public void setByteField(Byte byteField) {
+    this.byteField = byteField;
+  }
+
+  /**
+   * @param charField the charField to set
+   */
+  public void setCharField(Character charField) {
+    this.charField = charField;
+  }
+
+  public void setCreated(Date created) {
+    this.created = created;
+  }
+
+  /**
+   * @param doubleField the doubleField to set
+   */
+  public void setDoubleField(Double doubleField) {
+    this.doubleField = doubleField;
+  }
+
+  public void setEnumField(SimpleEnum enumField) {
+    this.enumField = enumField;
+  }
+
+  /**
+   * @param floatField the floatField to set
+   */
+  public void setFloatField(Float floatField) {
+    this.floatField = floatField;
+  }
+
+  public void setFooField(SimpleFoo fooField) {
+    this.fooField = fooField;
+  }
+
+  public void setId(Long id) {
+    this.id = id;
+  }
+
+  public void setIntId(Integer id) {
+    if (!this.intId.equals(id)) {
+      this.intId = id;
+      isChanged = true;
+    }
+  }
+
+  public void setLongField(Long longField) {
+    this.longField = longField;
+  }
+
+  public void setNullField(String nullField) {
+    this.nullField = nullField;
+  }
+
+  public void setNumberListField(List<Integer> numberListField) {
+    this.numberListField = numberListField;
+  }
+
+  public void setOneToManyField(List<SimpleBar> oneToManyField) {
+    this.oneToManyField = oneToManyField;
+  }
+
+  public void setOneToManySetField(Set<SimpleBar> oneToManySetField) {
+    this.oneToManySetField = oneToManySetField;
+  }
+
+  /**
+   * @param otherBoolField the otherBoolField to set
+   */
+  public void setOtherBoolField(Boolean otherBoolField) {
+    this.otherBoolField = otherBoolField;
+  }
+
+  public void setPassword(String password) {
+    this.password = password;
+  }
+
+  public void setPleaseCrash(Integer crashIf42or43) throws Exception {
+    pleaseCrash(crashIf42or43);
+    pleaseCrash = crashIf42or43;
+  }
+
+  public void setSelfOneToManyField(List<SimpleFoo> selfOneToManyField) {
+    this.selfOneToManyField = selfOneToManyField;
+  }
+
+  /**
+   * @param shortField the shortField to set
+   */
+  public void setShortField(Short shortField) {
+    this.shortField = shortField;
+  }
+
+  public void setSimpleValue(SimpleValue simpleValueField) {
+    this.simpleValueField = simpleValueField;
+  }
+
+  public void setSimpleValues(List<SimpleValue> simpleValueField) {
+    this.simpleValueField = simpleValueField.get(0);
+  }
+
+  public void setUnpersisted(boolean unpersisted) {
+    this.unpersisted = unpersisted;
+  }
+
+  public void setUserName(String userName) {
+    if (!this.userName.equals(userName)) {
+      this.userName = userName;
+      isChanged = true;
+    }
+  }
+
+  public void setVersion(Integer version) {
+    this.version = version;
+  }
+
+  public Integer sum(List<Integer> values) {
+    int sum = 0;
+    for (int n : values) {
+      sum += n;
+    }
+    return sum;
+  }
+
+  /**
+   * Persist this entity and all child entities. This method can handle loops.
+   * 
+   * @param processed the entities that have been processed
+   */
+  private void persistCascadingAndReturnSelfImpl(Set<SimpleFoo> processed) {
+    if (processed.contains(this)) {
+      return;
+    }
+
+    // Persist this entity.
+    processed.add(this);
+    persist();
+
+    // Persist SimpleBar children.
+    // We don't need to keep track of the processed SimpleBars because persist()
+    // is a no-op if the SimpleBar has already been persisted.
+    if (barField != null) {
+      barField.persist();
+    }
+    if (barNullField != null) {
+      barNullField.persist();
+    }
+    if (oneToManySetField != null) {
+      for (SimpleBar child : oneToManySetField) {
+        if (child != null) {
+          child.persist();
+        }
+      }
+    }
+    if (oneToManyField != null) {
+      for (SimpleBar child : oneToManyField) {
+        if (child != null) {
+          child.persist();
+        }
+      }
+    }
+
+    // Persist SimpleFoo children.
+    if (fooField != null) {
+      fooField.persistCascadingAndReturnSelfImpl(processed);
+    }
+    if (selfOneToManyField != null) {
+      for (SimpleFoo child : selfOneToManyField) {
+        if (child != null) {
+          child.persistCascadingAndReturnSelfImpl(processed);
+        }
+      }
+    }
+  }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/SimpleValue.java b/user/test/com/google/web/bindery/requestfactory/server/SimpleValue.java
new file mode 100644
index 0000000..5565fec
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/SimpleValue.java
@@ -0,0 +1,85 @@
+/*
+ * 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.requestfactory.server;
+
+import java.util.Date;
+import java.util.List;
+
+import javax.validation.constraints.Null;
+
+/**
+ * A domain object that is used to demonstrate value-object behaviors.
+ */
+public class SimpleValue {
+  private Date date;
+  private int number;
+  private SimpleFoo simpleFoo;
+  /**
+   * Constraint violation testing.
+   */
+  @Null
+  private String shouldBeNull;
+  private List<SimpleValue> simpleValue;
+  private String string;
+
+  public Date getDate() {
+    return date;
+  }
+
+  public int getNumber() {
+    return number;
+  }
+
+  public String getShouldBeNull() {
+    return shouldBeNull;
+  }
+
+  public SimpleFoo getSimpleFoo() {
+    return simpleFoo;
+  }
+
+  public List<SimpleValue> getSimpleValue() {
+    return simpleValue;
+  }
+
+  public String getString() {
+    return string;
+  }
+
+  public void setDate(Date date) {
+    this.date = date;
+  }
+
+  public void setShouldBeNull(String value) {
+    this.shouldBeNull = value;
+  }
+
+  public void setNumber(int number) {
+    this.number = number;
+  }
+
+  public void setSimpleFoo(SimpleFoo simpleFoo) {
+    this.simpleFoo = simpleFoo;
+  }
+
+  public void setSimpleValue(List<SimpleValue> simpleValue) {
+    this.simpleValue = simpleValue;
+  }
+
+  public void setString(String string) {
+    this.string = string;
+  }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/TestContextImpl.java b/user/test/com/google/web/bindery/requestfactory/server/TestContextImpl.java
new file mode 100644
index 0000000..b918077
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/TestContextImpl.java
@@ -0,0 +1,74 @@
+/*
+ * 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.requestfactory.server;
+
+/**
+ * Bad service method declarations.
+ */
+public class TestContextImpl {
+
+  public static Class badReturnType() {
+    return null;
+  }
+
+  public static String mismatchedArityStatic(int x, int y, int z) {
+    return null;
+  }
+
+  public static String mismatchedParamType(String param) {
+    return null;
+  }
+
+  public static Integer mismatchedReturnType() {
+    return null;
+  }
+
+  public static String overloadedMethod() {
+    return null;
+  }
+
+  public static String overloadedMethod(String foo) {
+    return null;
+  }
+  public String getId() {
+    return null;
+  }
+
+  public String getVersion() {
+    return null;
+  }
+
+  public String mismatchedArityInstance(int x, int y) {
+    return null;
+  }
+
+  public String mismatchedStatic(String param) {
+    return null;
+  }
+
+  public static String okMethod() {
+    return null;
+  }
+
+  public static TestContextImpl okMethodProxy() {
+    return null;
+  }
+
+  public static String mismatchedNonStatic(String param) {
+    return null;
+  }
+}
+
diff --git a/user/test/com/google/web/bindery/requestfactory/server/TestContextNoIdImpl.java b/user/test/com/google/web/bindery/requestfactory/server/TestContextNoIdImpl.java
new file mode 100644
index 0000000..5c03704
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/TestContextNoIdImpl.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.
+ */
+package com.google.web.bindery.requestfactory.server;
+
+/**
+ * Bad proxy, no id.
+ */
+public class TestContextNoIdImpl {
+
+  public static TestContextNoIdImpl okMethodProxy() {
+    return null;
+  }
+
+  public String getVersion() {
+    return null;
+  }
+}
+
diff --git a/user/test/com/google/web/bindery/requestfactory/server/TestContextNoVersionImpl.java b/user/test/com/google/web/bindery/requestfactory/server/TestContextNoVersionImpl.java
new file mode 100644
index 0000000..9cc7a83
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/TestContextNoVersionImpl.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.
+ */
+package com.google.web.bindery.requestfactory.server;
+
+/**
+ * Bad proxy, no version.
+ */
+public class TestContextNoVersionImpl {
+
+  public static TestContextNoVersionImpl okMethodProxy() {
+    return null;
+  }
+
+  public String getId() {
+    return null;
+  }
+}
+
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/BaseFooProxy.java b/user/test/com/google/web/bindery/requestfactory/shared/BaseFooProxy.java
new file mode 100644
index 0000000..ca750ec
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/BaseFooProxy.java
@@ -0,0 +1,133 @@
+/*
+ * 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.requestfactory.shared;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A simple proxy used for testing. Has an int field and date field. Add other
+ * data types as their support gets built in.
+ */
+public interface BaseFooProxy extends EntityProxy {
+
+  SimpleBarProxy getBarField();
+
+  SimpleBarProxy getBarNullField();
+
+  BigDecimal getBigDecimalField();
+
+  BigInteger getBigIntField();
+
+  Boolean getBoolField();
+
+  Byte getByteField();
+
+  Character getCharField();
+
+  Date getCreated();
+
+  Double getDoubleField();
+
+  SimpleEnum getEnumField();
+
+  Float getFloatField();
+
+  Integer getIntId();
+
+  Long getLongField();
+
+  String getNullField();
+
+  List<Integer> getNumberListField();
+
+  List<SimpleBarProxy> getOneToManyField();
+
+  Set<SimpleBarProxy> getOneToManySetField();
+
+  Boolean getOtherBoolField();
+
+  String getPassword();
+
+  Integer getPleaseCrash();
+
+  List<SimpleFooProxy> getSelfOneToManyField();
+
+  Short getShortField();
+
+  SimpleValueProxy getSimpleValue();
+
+  List<SimpleValueProxy> getSimpleValues();
+
+  boolean getUnpersisted();
+
+  String getUserName();
+
+  void setBarField(SimpleBarProxy barField);
+
+  void setBarNullField(SimpleBarProxy barNullField);
+
+  void setBigDecimalField(BigDecimal d);
+
+  void setBigIntField(BigInteger i);
+
+  void setBoolField(Boolean boolField);
+
+  void setByteField(Byte b);
+
+  void setCharField(Character c);
+
+  void setCreated(Date created);
+
+  void setDoubleField(Double d);
+
+  void setEnumField(SimpleEnum value);
+
+  void setFloatField(Float f);
+
+  void setIntId(Integer intId);
+
+  void setLongField(Long longField);
+
+  void setNullField(String nullField);
+
+  void setNumberListField(List<Integer> field);
+
+  void setOneToManyField(List<SimpleBarProxy> field);
+
+  void setOneToManySetField(Set<SimpleBarProxy> field);
+
+  void setOtherBoolField(Boolean boolField);
+
+  void setPassword(String password);
+
+  void setPleaseCrash(Integer dummy);
+
+  void setSelfOneToManyField(List<SimpleFooProxy> field);
+
+  void setShortField(Short s);
+
+  void setSimpleValue(SimpleValueProxy value);
+
+  void setSimpleValues(List<SimpleValueProxy> value);
+
+  void setUnpersisted(boolean unpersisted);
+
+  void setUserName(String userName);
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/BasicRequestFactory.java b/user/test/com/google/web/bindery/requestfactory/shared/BasicRequestFactory.java
new file mode 100644
index 0000000..46bb188
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/BasicRequestFactory.java
@@ -0,0 +1,24 @@
+/*
+ * 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.requestfactory.shared;
+
+/**
+ * A base type for {@link SimpleRequestFactory} to ensure that extending
+ * RequestFactory interfaces works correctly.
+ */
+public interface BasicRequestFactory extends RequestFactory {
+  LoggingRequest loggingRequest();
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/BoxesAndPrimitivesTest.java b/user/test/com/google/web/bindery/requestfactory/shared/BoxesAndPrimitivesTest.java
new file mode 100644
index 0000000..87f962e
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/BoxesAndPrimitivesTest.java
@@ -0,0 +1,234 @@
+/*
+ * 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.requestfactory.shared;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.web.bindery.event.shared.SimpleEventBus;
+
+/**
+ * Contains a set of checks of how primitive and boxed method declarations
+ * interact.
+ */
+public class BoxesAndPrimitivesTest extends GWTTestCase {
+
+  /**
+   * The domain type.
+   */
+  protected static class Entity {
+    static final Entity SINGLETON = new Entity();
+
+    public static Entity findEntity(int id) {
+      return SINGLETON;
+    }
+
+    public Integer getBoxed() {
+      return EXPECTED_BOXED;
+    }
+
+    public int getId() {
+      return 0;
+    }
+
+    public int getPrimitive() {
+      return EXPECTED;
+    }
+
+    public int getVersion() {
+      return 0;
+    }
+
+    public boolean hasHas() {
+      return EXPECTED_BOOL;
+    }
+
+    public Boolean hasHasBoxed() {
+      return EXPECTED_BOOL_BOXED;
+    }
+
+    public boolean isIs() {
+      return EXPECTED_BOOL;
+    }
+
+    public Boolean isIsBoxed() {
+      return EXPECTED_BOOL_BOXED;
+    }
+
+    public void setBoxed(Integer value) {
+      assertEquals(EXPECTED_BOXED, value);
+    }
+
+    public void setPrimitive(int value) {
+      assertEquals(EXPECTED, value);
+    }
+  }
+
+  /**
+   * The RequestFactory.
+   */
+  protected interface Factory extends RequestFactory {
+    Context context();
+  }
+
+  /**
+   * The service method implementations.
+   */
+  protected static class ServiceImpl {
+    public static void checkBoxed(Integer value) {
+      assertEquals(EXPECTED_BOXED, value);
+    }
+
+    public static void checkPrimitive(int value) {
+      assertEquals(EXPECTED, value);
+    }
+
+    public static Integer getBoxed() {
+      return EXPECTED_BOXED;
+    }
+
+    public static Entity getEntity() {
+      return Entity.SINGLETON;
+    }
+
+    public static int getPrimitive() {
+      return EXPECTED;
+    }
+  }
+
+  @Service(ServiceImpl.class)
+  interface Context extends RequestContext {
+    Request<Void> checkBoxed(Integer value);
+
+    Request<Void> checkPrimitive(int value);
+
+    Request<Integer> getBoxed();
+
+    Request<Proxy> getEntity();
+
+    Request<Integer> getPrimitive();
+  }
+
+  @ProxyFor(Entity.class)
+  interface Proxy extends EntityProxy {
+    Integer getBoxed();
+
+    int getPrimitive();
+
+    boolean hasHas();
+
+    Boolean hasHasBoxed();
+
+    boolean isIs();
+
+    Boolean isIsBoxed();
+
+    void setBoxed(Integer value);
+
+    void setPrimitive(int value);
+  }
+
+  static abstract class TestReceiver<T> extends Receiver<T> {
+    @Override
+    public void onFailure(ServerFailure error) {
+      fail(error.getMessage());
+    }
+  }
+
+  private static final int EXPECTED = 42;
+  private static final Integer EXPECTED_BOXED = Integer.valueOf(EXPECTED);
+  private static final boolean EXPECTED_BOOL = true;
+  private static final Boolean EXPECTED_BOOL_BOXED = Boolean.TRUE;
+  private static final int TEST_DELAY = 5000;
+
+  private Factory factory;
+
+  @Override
+  public String getModuleName() {
+    return "com.google.web.bindery.requestfactory.gwt.RequestFactorySuite";
+  }
+
+  /**
+   * Tests that domain service methods that return a primitive type are upcast
+   * to the boxed type that the generic declaration requires. Also checks that
+   * primitive and boxed property types can be retrieved and that boxed and
+   * primitive method arguments work.
+   */
+  public void testReturnAndParamTypes() {
+    delayTestFinish(TEST_DELAY);
+    Context ctx = context();
+    // Boxed service method
+    ctx.getBoxed().to(new TestReceiver<Integer>() {
+      @Override
+      public void onSuccess(Integer response) {
+        assertEquals(EXPECTED_BOXED, response);
+      }
+    });
+    // Primitive service method
+    ctx.getPrimitive().to(new TestReceiver<Integer>() {
+      @Override
+      public void onSuccess(Integer response) {
+        assertEquals(EXPECTED_BOXED, response);
+      }
+    });
+    // Boxed and primitive properties
+    ctx.getEntity().to(new TestReceiver<Proxy>() {
+      @Override
+      public void onSuccess(Proxy response) {
+        assertEquals(EXPECTED_BOXED, response.getBoxed());
+        assertEquals(EXPECTED, response.getPrimitive());
+        assertEquals(EXPECTED_BOOL, response.isIs());
+        assertEquals(EXPECTED_BOOL_BOXED, response.isIsBoxed());
+        assertEquals(EXPECTED_BOOL, response.hasHas());
+        assertEquals(EXPECTED_BOOL_BOXED, response.hasHasBoxed());
+      }
+    });
+    // Boxed service argument
+    ctx.checkBoxed(EXPECTED_BOXED).to(new TestReceiver<Void>() {
+      @Override
+      public void onSuccess(Void response) {
+        // OK
+      }
+    });
+    // Primitive service argument
+    ctx.checkPrimitive(EXPECTED).to(new TestReceiver<Void>() {
+      @Override
+      public void onSuccess(Void response) {
+        // OK
+      }
+    });
+    ctx.fire(new TestReceiver<Void>() {
+      @Override
+      public void onSuccess(Void response) {
+        finishTest();
+      }
+    });
+  }
+
+  protected Factory createFactory() {
+    Factory toReturn = GWT.create(Factory.class);
+    toReturn.initialize(new SimpleEventBus());
+    return toReturn;
+  }
+
+  @Override
+  protected void gwtSetUp() {
+    factory = createFactory();
+  }
+
+  private Context context() {
+    return factory.context();
+  }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/ComplexKeysTest.java b/user/test/com/google/web/bindery/requestfactory/shared/ComplexKeysTest.java
new file mode 100644
index 0000000..8c43a16
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/ComplexKeysTest.java
@@ -0,0 +1,223 @@
+/*
+ * 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.requestfactory.shared;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.web.bindery.event.shared.SimpleEventBus;
+
+/**
+ * Tests the use of non-trivial EntityProxy and ValueProxy key types.
+ */
+public class ComplexKeysTest extends GWTTestCase {
+
+  /**
+   * The factory being tested.
+   */
+  protected interface Factory extends RequestFactory {
+    Context context();
+  }
+
+  @Service(ContextImpl.class)
+  interface Context extends RequestContext {
+    Request<DomainWithEntityKeyProxy> createEntity(String key);
+
+    Request<DomainWithValueKeyProxy> createValue(String key);
+  }
+
+  static class ContextImpl {
+    public static DomainWithEntityKey createEntity(String key) {
+      return new DomainWithEntityKey(new EntityKey(key));
+    }
+
+    public static DomainWithValueKey createValue(String key) {
+      return new DomainWithValueKey(new ValueKey(key));
+    }
+  }
+
+  static class DomainWithEntityKey {
+
+    public static DomainWithEntityKey findDomainWithEntityKey(EntityKey key) {
+      return new DomainWithEntityKey(key);
+    }
+
+    private final EntityKey key;
+
+    public DomainWithEntityKey(EntityKey key) {
+      if (key == null) {
+        throw new IllegalArgumentException("Key key");
+      }
+      this.key = key;
+    }
+
+    public EntityKey getId() {
+      return key;
+    }
+
+    public Integer getVersion() {
+      return 0;
+    }
+  }
+
+  @ProxyFor(DomainWithEntityKey.class)
+  interface DomainWithEntityKeyProxy extends EntityProxy {
+    EntityKeyProxy getId();
+
+    EntityProxyId<DomainWithEntityKeyProxy> stableId();
+  }
+
+  static class DomainWithValueKey {
+    public static DomainWithValueKey create(String key) {
+      return new DomainWithValueKey(new ValueKey(key));
+    }
+
+    public static DomainWithValueKey findDomainWithValueKey(ValueKey key) {
+      return new DomainWithValueKey(key);
+    }
+
+    private final ValueKey key;
+
+    public DomainWithValueKey(ValueKey key) {
+      if (key == null) {
+        throw new IllegalArgumentException("Key key");
+      }
+      this.key = key;
+    }
+
+    public ValueKey getId() {
+      return key;
+    }
+
+    public Integer getVersion() {
+      return 0;
+    }
+  }
+
+  @ProxyFor(DomainWithValueKey.class)
+  interface DomainWithValueKeyProxy extends EntityProxy {
+    ValueKeyProxy getId();
+
+    EntityProxyId<DomainWithValueKeyProxy> stableId();
+  }
+
+  static class EntityKey {
+    public static EntityKey findEntityKey(String key) {
+      return new EntityKey(key);
+    }
+
+    private final String key;
+
+    public EntityKey(String key) {
+      assertEquals("key", key);
+      this.key = key;
+    }
+
+    public String getId() {
+      return key;
+    }
+
+    public Integer getVersion() {
+      return 0;
+    }
+  }
+
+  @ProxyFor(EntityKey.class)
+  interface EntityKeyProxy extends EntityProxy {
+  }
+
+  static class ValueKey {
+    private String key;
+
+    public ValueKey() {
+    }
+
+    public ValueKey(String key) {
+      setKey(key);
+    }
+
+    public String getKey() {
+      return key;
+    }
+
+    public void setKey(String key) {
+      assertEquals("key", key);
+      this.key = key;
+    }
+  }
+
+  @ProxyFor(ValueKey.class)
+  interface ValueKeyProxy extends ValueProxy {
+    String getKey();
+  }
+
+  private static final int TEST_DELAY = 5000;
+  private Factory factory;
+
+  @Override
+  public String getModuleName() {
+    return "com.google.web.bindery.requestfactory.gwt.RequestFactorySuite";
+  }
+
+  public void testEntityKey() {
+    delayTestFinish(TEST_DELAY);
+    context().createEntity("key").fire(
+        new Receiver<DomainWithEntityKeyProxy>() {
+          @Override
+          public void onSuccess(final DomainWithEntityKeyProxy response) {
+            factory.find(response.stableId()).fire(
+                new Receiver<DomainWithEntityKeyProxy>() {
+                  @Override
+                  public void onSuccess(DomainWithEntityKeyProxy found) {
+                    assertEquals(response.stableId(), found.stableId());
+                    finishTest();
+                  }
+                });
+          }
+        });
+  }
+
+  public void testValueKey() {
+    delayTestFinish(TEST_DELAY);
+    context().createValue("key").fire(new Receiver<DomainWithValueKeyProxy>() {
+      @Override
+      public void onSuccess(final DomainWithValueKeyProxy response) {
+        factory.find(response.stableId()).fire(
+            new Receiver<DomainWithValueKeyProxy>() {
+              @Override
+              public void onSuccess(DomainWithValueKeyProxy found) {
+                assertEquals(response.stableId(), found.stableId());
+                finishTest();
+              }
+            });
+      }
+    });
+  }
+
+  protected Factory createFactory() {
+    Factory toReturn = GWT.create(Factory.class);
+    toReturn.initialize(new SimpleEventBus());
+    return toReturn;
+  }
+
+  @Override
+  protected void gwtSetUp() {
+    factory = createFactory();
+  }
+
+  private Context context() {
+    return factory.context();
+  }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/InstanceServiceRequest.java b/user/test/com/google/web/bindery/requestfactory/shared/InstanceServiceRequest.java
new file mode 100644
index 0000000..376eeb5
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/InstanceServiceRequest.java
@@ -0,0 +1,27 @@
+/*
+ * 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.requestfactory.shared;
+
+import com.google.web.bindery.requestfactory.server.InstanceService;
+import com.google.web.bindery.requestfactory.server.InstanceServiceLocator;
+
+/**
+ * Used to test the ServiceLocator extension hook.
+ */
+@Service(value = InstanceService.class, locator = InstanceServiceLocator.class)
+public interface InstanceServiceRequest extends RequestContext {
+  Request<Integer> add(int value);
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/InstanceServiceRequestByName.java b/user/test/com/google/web/bindery/requestfactory/shared/InstanceServiceRequestByName.java
new file mode 100644
index 0000000..648abee
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/InstanceServiceRequestByName.java
@@ -0,0 +1,25 @@
+/*
+ * 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.requestfactory.shared;
+
+/**
+ * Used to test the ServiceLocator extension hook.
+ */
+@ServiceName(value = "com.google.web.bindery.requestfactory.server.InstanceService", 
+    locator = "com.google.web.bindery.requestfactory.server.InstanceServiceLocator")
+public interface InstanceServiceRequestByName extends RequestContext {
+  Request<Integer> add(int value);
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/LocatorTest.java b/user/test/com/google/web/bindery/requestfactory/shared/LocatorTest.java
new file mode 100644
index 0000000..632ffb7
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/LocatorTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.requestfactory.shared;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.web.bindery.event.shared.SimpleEventBus;
+
+/**
+ * Tests the use of Locator objects.
+ */
+public class LocatorTest extends GWTTestCase {
+  /**
+   * The locator being tested.
+   */
+  public static class DomainLocator extends Locator<Domain, String> {
+    @Override
+    public Domain create(Class<? extends Domain> clazz) {
+      assertEquals(Domain.class, clazz);
+      return new Domain();
+    }
+
+    @Override
+    public Domain find(Class<? extends Domain> clazz, String id) {
+      assertEquals(ID, id);
+      return Domain.INSTANCE;
+    }
+
+    @Override
+    public Class<Domain> getDomainType() {
+      return Domain.class;
+    }
+
+    @Override
+    public String getId(Domain domainObject) {
+      return ID;
+    }
+
+    @Override
+    public Class<String> getIdType() {
+      return String.class;
+    }
+
+    @Override
+    public Object getVersion(Domain domainObject) {
+      return 0;
+    }
+  }
+
+  /**
+   * The factory under test.
+   */
+  protected interface Factory extends RequestFactory {
+    Context context();
+  }
+
+  @Service(ContextImpl.class)
+  interface Context extends RequestContext {
+    Request<DomainProxy> getDomain();
+  }
+
+  static class ContextImpl {
+    public static Domain getDomain() {
+      return Domain.INSTANCE;
+    }
+  }
+
+  static class Domain {
+    static final Domain INSTANCE = new Domain();
+  }
+
+  @ProxyFor(value = Domain.class, locator = DomainLocator.class)
+  interface DomainProxy extends EntityProxy {
+    EntityProxyId<DomainProxy> stableId();
+  };
+
+  private static final String ID = "DomainId";
+  private static final int TEST_DELAY = 5000;
+
+  private Factory factory;
+
+  @Override
+  public String getModuleName() {
+    return "com.google.web.bindery.requestfactory.gwt.RequestFactorySuite";
+  }
+
+  public void testLocator() {
+    delayTestFinish(TEST_DELAY);
+    context().getDomain().fire(new Receiver<DomainProxy>() {
+      @Override
+      public void onSuccess(final DomainProxy response) {
+        factory.find(response.stableId()).fire(new Receiver<DomainProxy>() {
+          @Override
+          public void onSuccess(DomainProxy found) {
+            assertEquals(response.stableId(), found.stableId());
+            finishTest();
+          }
+        });
+      }
+    });
+  }
+
+  protected Factory createFactory() {
+    Factory toReturn = GWT.create(Factory.class);
+    toReturn.initialize(new SimpleEventBus());
+    return toReturn;
+  }
+
+  @Override
+  protected void gwtSetUp() {
+    factory = createFactory();
+  }
+
+  private Context context() {
+    return factory.context();
+  }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/OnlyUsedByRequestContextMethod.java b/user/test/com/google/web/bindery/requestfactory/shared/OnlyUsedByRequestContextMethod.java
new file mode 100644
index 0000000..8df9169
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/OnlyUsedByRequestContextMethod.java
@@ -0,0 +1,24 @@
+/*
+ * 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.requestfactory.shared;
+
+/**
+ * Ensures that enums used only by a method in a RequestContext are property
+ * emitted into the EnumMap used to encode values.
+ */
+public enum OnlyUsedByRequestContextMethod {
+  FOO, BAR;
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/OnlyUsedInListProxy.java b/user/test/com/google/web/bindery/requestfactory/shared/OnlyUsedInListProxy.java
new file mode 100644
index 0000000..73460ff
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/OnlyUsedInListProxy.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.requestfactory.shared;
+
+import com.google.web.bindery.requestfactory.server.SimpleFoo;
+
+/**
+ * This proxy type should only be used as the parameterization of a list type to
+ * ensure that proxy types reachable only through a parameterization can be
+ * created by a RequestContext.
+ */
+@ProxyFor(SimpleFoo.class)
+public interface OnlyUsedInListProxy extends ValueProxy {
+  String getUserName();
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/ServiceInheritanceTest.java b/user/test/com/google/web/bindery/requestfactory/shared/ServiceInheritanceTest.java
new file mode 100644
index 0000000..ee604b9
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/ServiceInheritanceTest.java
@@ -0,0 +1,169 @@
+/*
+ * 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.requestfactory.shared;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.web.bindery.event.shared.SimpleEventBus;
+
+/**
+ * Tests the ability of instance services to inherit methods
+ * from a base class.
+ */
+public class ServiceInheritanceTest extends GWTTestCase {
+
+  /**
+   * ServiceLocator that returns the base class or subclass implementation
+   * specified in the @{@link Service} annotation.
+   */
+  public static class SumServiceLocator implements ServiceLocator {
+    public Object getInstance(Class<?> clazz) {
+      if (BaseImpl.class.equals(clazz)) {
+        return new BaseImpl();
+      } else if (SubclassImpl.class.equals(clazz)) {
+        return new SubclassImpl();
+      }
+      return null;
+    }
+  }
+
+  /**
+   * The factory under test.
+   */
+  protected interface Factory extends RequestFactory {
+    SumServiceBase baseContext();
+    SumServiceSub subContext();
+  }
+
+  /**
+   * Specifies the base class implementation.
+   */
+  @Service(value = BaseImpl.class, locator = SumServiceLocator.class)
+  interface SumServiceBase extends RequestContext {
+    Request<Integer> add(int n);
+    Request<Integer> subtract(int n);
+  }
+
+  /**
+   * Specifies the subclass implementation.
+   */
+  @Service(value = SubclassImpl.class, locator = SumServiceLocator.class)
+  interface SumServiceSub extends RequestContext {
+    Request<Integer> add(int n);
+    Request<Integer> subtract(int n);
+  }
+
+  /**
+   * Base implementation of {@link SumServiceBase}.
+   */
+  static class BaseImpl {
+    protected int initialValue;
+
+    public BaseImpl() {
+      initialValue = 5;
+    }
+
+    public Integer add(int n) {
+      return initialValue + n;
+    }
+    
+    public Integer subtract(int n) {
+      return initialValue - n;
+    }
+  }
+
+  /**
+   * Subclass implementation of {@link SumServiceSub}
+   * inherits the add() method.
+   */
+  static class SubclassImpl extends BaseImpl {
+    public SubclassImpl() {
+      /*
+       * Init with a different value to distinguish between base & subclass
+       * implementations in the tests
+       */
+      initialValue = 8;
+    }
+    
+    @Override
+    public Integer subtract(int n) {
+      return 0;
+    }
+  }
+
+  private static final int TEST_DELAY = 5000;
+
+  private Factory factory;
+
+  @Override
+  public String getModuleName() {
+    return "com.google.web.bindery.requestfactory.gwt.RequestFactorySuite";
+  }
+
+  /**
+   * Call a method inherited from a base class.
+   */
+  public void testInvokeInheritedMethod() {
+    delayTestFinish(TEST_DELAY);
+    factory.subContext().add(13).fire(new Receiver<Integer>() {
+      @Override
+      public void onSuccess(Integer response) {
+        assertEquals((Integer) 21, response);
+        finishTest();
+      }
+    });
+  }
+
+  /**
+   * Call a method implemented in a base class.
+   */
+  public void testInvokeMethodOnBaseClass() {
+    delayTestFinish(TEST_DELAY);
+    factory.baseContext().add(13).fire(new Receiver<Integer>() {
+      @Override
+      public void onSuccess(Integer response) {
+        assertEquals((Integer) 18, response);
+        finishTest();
+      }
+    });
+  }
+
+  /**
+   * Call a method overridden in a subclass.
+   */
+  public void testInvokeOverriddenMethod() {
+    delayTestFinish(TEST_DELAY);
+    factory.subContext().subtract(3).fire(new Receiver<Integer>() {
+      @Override
+      public void onSuccess(Integer response) {
+        assertEquals((Integer) 0, response);
+        finishTest();
+      }
+    });
+  }
+
+  protected Factory createFactory() {
+    Factory toReturn = GWT.create(Factory.class);
+    toReturn.initialize(new SimpleEventBus());
+    return toReturn;
+  }
+
+  @Override
+  protected void gwtSetUp() {
+    factory = createFactory();
+  }
+
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/SimpleBarProxy.java b/user/test/com/google/web/bindery/requestfactory/shared/SimpleBarProxy.java
new file mode 100644
index 0000000..2995e06
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/SimpleBarProxy.java
@@ -0,0 +1,42 @@
+/*
+ * 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.requestfactory.shared;
+
+/**
+ * A simple entity used for testing. Has an int field and date field. Add other
+ * data types as their support gets built in.
+ */
+@ProxyForName("com.google.web.bindery.requestfactory.server.SimpleBar")
+public interface SimpleBarProxy extends EntityProxy {
+  Boolean getFindFails();
+
+  Boolean getUnpersisted();
+
+  /*
+   * NB: The lack of a getId() here is intentional, to ensure that the system
+   * does not assume that the id property is available to the client.
+   */
+
+  String getUserName();
+
+  void setFindFails(Boolean fails);
+
+  void setUnpersisted(Boolean b);
+
+  void setUserName(String userName);
+
+  EntityProxyId<SimpleBarProxy> stableId();
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/SimpleBarRequest.java b/user/test/com/google/web/bindery/requestfactory/shared/SimpleBarRequest.java
new file mode 100644
index 0000000..3d9bb6f
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/SimpleBarRequest.java
@@ -0,0 +1,42 @@
+/*
+ * 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.requestfactory.shared;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Do nothing test interface.
+ */
+@ServiceName("com.google.web.bindery.requestfactory.server.SimpleBar")
+public interface SimpleBarRequest extends RequestContext {
+
+  Request<Long> countSimpleBar();
+
+  Request<List<SimpleBarProxy>> findAll();
+
+  Request<Set<SimpleBarProxy>> findAsSet();
+  
+  Request<SimpleBarProxy> findSimpleBarById(String id);
+
+  InstanceRequest<SimpleBarProxy, Void> persist();
+
+  InstanceRequest<SimpleBarProxy, SimpleBarProxy> persistAndReturnSelf();
+
+  Request<Void> reset();
+
+  Request<SimpleBarProxy> returnFirst(List<SimpleBarProxy> proxy);
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/SimpleEnum.java b/user/test/com/google/web/bindery/requestfactory/shared/SimpleEnum.java
new file mode 100644
index 0000000..b338e5f
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/SimpleEnum.java
@@ -0,0 +1,23 @@
+/*
+ * 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.requestfactory.shared;
+
+/**
+ * Test enum class.
+ */
+public enum SimpleEnum {
+  FOO, BAR
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/SimpleFooProxy.java b/user/test/com/google/web/bindery/requestfactory/shared/SimpleFooProxy.java
new file mode 100644
index 0000000..9925dde
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/SimpleFooProxy.java
@@ -0,0 +1,32 @@
+/*
+ * 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.requestfactory.shared;
+
+import com.google.web.bindery.requestfactory.server.SimpleFoo;
+
+/**
+ * A simple extension of AbstractFooProxy with Long id.
+ */
+@ProxyFor(SimpleFoo.class)
+public interface SimpleFooProxy extends BaseFooProxy {
+  SimpleFooProxy getFooField();
+
+  Long getId();
+
+  void setFooField(SimpleFooProxy fooField);
+
+  EntityProxyId<SimpleFooProxy> stableId();
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/SimpleFooRequest.java b/user/test/com/google/web/bindery/requestfactory/shared/SimpleFooRequest.java
new file mode 100644
index 0000000..ba305a2
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/SimpleFooRequest.java
@@ -0,0 +1,117 @@
+/*
+ * 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.requestfactory.shared;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Do nothing test interface.
+ */
+@Service(com.google.web.bindery.requestfactory.server.SimpleFoo.class)
+public interface SimpleFooRequest extends RequestContext {
+  Request<Integer> add(Integer a, int b);
+
+  Request<Long> countSimpleFoo();
+
+  InstanceRequest<SimpleFooProxy, Long> countSimpleFooWithUserNameSideEffect();
+
+  InstanceRequest<SimpleFooProxy, Void> deleteBar();
+
+  Request<SimpleFooProxy> echo(SimpleFooProxy proxy);
+
+  Request<SimpleFooProxy> echoComplex(SimpleFooProxy fooProxy,
+      SimpleBarProxy barProxy);
+
+  Request<SimpleFooProxy> fetchDoubleReference();
+
+  Request<List<SimpleFooProxy>> findAll();
+
+  Request<SimpleFooProxy> findSimpleFooById(Long id);
+
+  Request<List<Integer>> getNumberList();
+
+  Request<Set<Integer>> getNumberSet();
+
+  Request<SimpleFooProxy> getSimpleFooWithNullVersion();
+
+  Request<SimpleFooProxy> getSimpleFooWithSubPropertyCollection();
+
+  Request<SimpleFooProxy> getTripletReference();
+
+  Request<SimpleFooProxy> getUnpersistedInstance();
+
+  InstanceRequest<SimpleFooProxy, String> hello(SimpleBarProxy proxy);
+
+  InstanceRequest<SimpleFooProxy, Void> persist();
+
+  InstanceRequest<SimpleFooProxy, SimpleFooProxy> persistAndReturnSelf();
+
+  InstanceRequest<SimpleFooProxy, SimpleFooProxy> persistCascadingAndReturnSelf();
+
+  Request<Void> pleaseCrash(Integer crashIf42or43);
+
+  Request<List<BigDecimal>> processBigDecimalList(List<BigDecimal> values);
+
+  Request<List<BigInteger>> processBigIntegerList(List<BigInteger> values);
+
+  Request<Boolean> processBooleanList(List<Boolean> values);
+
+  Request<List<Date>> processDateList(List<Date> values);
+
+  Request<SimpleEnum> processEnumList(List<SimpleEnum> values);
+
+  InstanceRequest<SimpleFooProxy, String> processList(
+      List<SimpleFooProxy> values);
+
+  Request<String> processString(String value);
+
+  Request<Void> receiveEnum(OnlyUsedByRequestContextMethod value);
+
+  InstanceRequest<SimpleFooProxy, Void> receiveNull(String value);
+
+  Request<Void> receiveNullList(List<SimpleFooProxy> value);
+
+  Request<Void> receiveNullSimpleFoo(SimpleFooProxy value);
+
+  Request<Void> receiveNullString(String value);
+
+  Request<Void> receiveNullValueInEntityList(List<SimpleFooProxy> value);
+
+  Request<Void> receiveNullValueInIntegerList(List<Integer> value);
+
+  Request<Void> receiveNullValueInStringList(List<String> value);
+
+  Request<Void> reset();
+
+  Request<List<SimpleFooProxy>> returnNullList();
+
+  Request<SimpleFooProxy> returnNullSimpleFoo();
+
+  Request<String> returnNullString();
+
+  Request<Void> returnOnlyUsedInParameterization(
+      List<OnlyUsedInListProxy> values);
+
+  Request<SimpleFooProxy> returnSimpleFooSubclass();
+
+  Request<SimpleValueProxy> returnValueProxy();
+
+  InstanceRequest<SimpleFooProxy, Integer> sum(List<Integer> values);
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/SimpleRequestFactory.java b/user/test/com/google/web/bindery/requestfactory/shared/SimpleRequestFactory.java
new file mode 100644
index 0000000..63944e2
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/SimpleRequestFactory.java
@@ -0,0 +1,35 @@
+/*
+ * 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.requestfactory.shared;
+
+/**
+ * Simple RequestFactory interface with two domain objects, and our standard
+ * UserInformation and Logging services.
+ */
+public interface SimpleRequestFactory extends BasicRequestFactory {
+
+  InstanceServiceRequest instanceServiceRequest();
+
+  InstanceServiceRequestByName instanceServiceRequestByName();
+
+  SimpleBarRequest simpleBarRequest();
+
+  SimpleFooRequest simpleFooRequest();
+
+  SimpleValueContext simpleValueContext();
+
+  UnicodeTestRequest unicodeTestRequest();
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/SimpleValueContext.java b/user/test/com/google/web/bindery/requestfactory/shared/SimpleValueContext.java
new file mode 100644
index 0000000..9fdbaf2
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/SimpleValueContext.java
@@ -0,0 +1,26 @@
+/*
+ * 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.requestfactory.shared;
+
+import com.google.web.bindery.requestfactory.server.SimpleValue;
+
+/**
+ * Tests instance method invocations on value objects.
+ */
+@Service(SimpleValue.class)
+public interface SimpleValueContext extends RequestContext {
+  InstanceRequest<SimpleValueProxy, String> getString();
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/SimpleValueProxy.java b/user/test/com/google/web/bindery/requestfactory/shared/SimpleValueProxy.java
new file mode 100644
index 0000000..7ff4a09
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/SimpleValueProxy.java
@@ -0,0 +1,51 @@
+/*
+ * 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.requestfactory.shared;
+
+import com.google.web.bindery.requestfactory.server.SimpleValue;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * A proxy for a non-addressable value object.
+ */
+@ProxyFor(value = SimpleValue.class)
+public interface SimpleValueProxy extends ValueProxy {
+  Date getDate();
+
+  int getNumber();
+
+  String getShouldBeNull();
+
+  SimpleFooProxy getSimpleFoo();
+
+  List<SimpleValueProxy> getSimpleValue();
+
+  String getString();
+
+  void setDate(Date value);
+
+  void setNumber(int value);
+
+  void setShouldBeNull(String value);
+
+  void setSimpleFoo(SimpleFooProxy value);
+
+  void setSimpleValue(List<SimpleValueProxy> value);
+
+  void setString(String value);
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/TestFooPolymorphicRequest.java b/user/test/com/google/web/bindery/requestfactory/shared/TestFooPolymorphicRequest.java
new file mode 100644
index 0000000..5662c40
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/TestFooPolymorphicRequest.java
@@ -0,0 +1,25 @@
+/*
+ * 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.requestfactory.shared;
+
+/**
+ * Just to test the
+ * {@link com.google.web.bindery.requestfactory.gwt.rebind.RequestFactoryGenerator} code.
+ */
+@Service(com.google.web.bindery.requestfactory.server.SimpleFoo.class)
+public interface TestFooPolymorphicRequest extends RequestContext {
+  <P extends SimpleFooProxy> Request<P> echo(P proxy);
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/TestRequestFactory.java b/user/test/com/google/web/bindery/requestfactory/shared/TestRequestFactory.java
new file mode 100644
index 0000000..6ced0f6
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/TestRequestFactory.java
@@ -0,0 +1,23 @@
+/*
+ * 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.requestfactory.shared;
+
+/**
+ * Creates TestFooPolymorphicRequest.
+ */
+public interface TestRequestFactory extends RequestFactory {
+  TestFooPolymorphicRequest testFooPolymorphicRequest();
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/UnicodeTestRequest.java b/user/test/com/google/web/bindery/requestfactory/shared/UnicodeTestRequest.java
new file mode 100644
index 0000000..10fe3c9
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/UnicodeTestRequest.java
@@ -0,0 +1,29 @@
+/*
+ * 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.requestfactory.shared;
+
+import com.google.gwt.user.client.rpc.UnicodeEscapingTest;
+
+/**
+ * Provides access to the static test methods in {@link UnicodeEscapingTes}.
+ */
+@Service(UnicodeEscapingTest.class)
+public interface UnicodeTestRequest extends RequestContext {
+  Request<String> getStringContainingCharacterRange(int start, int end);
+
+  Request<Void> verifyStringContainingCharacterRange(int start, int end,
+      String str);
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/impl/SimpleEntityProxyIdTest.java b/user/test/com/google/web/bindery/requestfactory/shared/impl/SimpleEntityProxyIdTest.java
new file mode 100644
index 0000000..249c658
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/impl/SimpleEntityProxyIdTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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.requestfactory.shared.impl;
+
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.SimpleBarProxy;
+import com.google.web.bindery.requestfactory.shared.SimpleFooProxy;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests {@link SimpleEntityProxyId}.
+ */
+public class SimpleEntityProxyIdTest extends TestCase {
+
+  public void testEquality() {
+    SimpleEntityProxyId<EntityProxy> client1 = id(EntityProxy.class, 1);
+    // equal to self
+    assertTrue(isStable(client1, client1));
+    // equal to identical client id
+    assertTrue(isStable(client1, id(EntityProxy.class, 1)));
+
+    // Persist and check again
+    client1.setServerId("server1");
+    // equal to self
+    assertTrue(isStable(client1, client1));
+    // equal to identical client id
+    assertTrue(isStable(client1, id(EntityProxy.class, 1)));
+
+    SimpleEntityProxyId<EntityProxy> server1 = id(EntityProxy.class, "server1");
+    assertTrue(isStable(server1, id(EntityProxy.class, "server1")));
+
+    /*
+     * Compare a server-only id the persisted client id, this should be false
+     * since the hashcodes would vary.
+     */
+    assertFalse(isStable(client1, server1));
+  }
+
+  public void testInequality() {
+    assertFalse(isStable(id(EntityProxy.class, 1), id(EntityProxy.class, 2)));
+
+    assertFalse(isStable(id(EntityProxy.class, "server1"),
+        id(EntityProxy.class, "server2")));
+
+    // Same client-side id, but different types
+    assertFalse(isStable(id(SimpleFooProxy.class, 1),
+        id(SimpleBarProxy.class, 1)));
+
+    // Same server id, but different types
+    assertFalse(isStable(id(SimpleFooProxy.class, "server1"),
+        id(SimpleBarProxy.class, "server1")));
+  }
+
+  private <T extends EntityProxy> SimpleEntityProxyId<T> id(Class<T> clazz,
+      int clientId) {
+    return new SimpleEntityProxyId<T>(clazz, clientId);
+  }
+
+  private <T extends EntityProxy> SimpleEntityProxyId<T> id(Class<T> clazz,
+      String serverId) {
+    return new SimpleEntityProxyId<T>(clazz, serverId);
+  }
+
+  /**
+   * Assert that the id behaves with the stable sematics that are desired for
+   * client code.
+   */
+  private boolean isStable(SimpleEntityProxyId<?> a, SimpleEntityProxyId<?> b) {
+    return a.equals(b) && b.equals(a) && a.hashCode() == b.hashCode();
+  }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/vm/RequestFactoryJreSuite.java b/user/test/com/google/web/bindery/requestfactory/vm/RequestFactoryJreSuite.java
new file mode 100644
index 0000000..5b7d5ed
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/vm/RequestFactoryJreSuite.java
@@ -0,0 +1,66 @@
+/*
+ * 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.requestfactory.vm;
+
+import com.google.web.bindery.requestfactory.server.BoxesAndPrimitivesJreTest;
+import com.google.web.bindery.requestfactory.server.ComplexKeysJreTest;
+import com.google.web.bindery.requestfactory.server.FindServiceJreTest;
+import com.google.web.bindery.requestfactory.server.LocatorJreTest;
+import com.google.web.bindery.requestfactory.server.RequestFactoryExceptionPropagationJreTest;
+import com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidatorTest;
+import com.google.web.bindery.requestfactory.server.RequestFactoryJreTest;
+import com.google.web.bindery.requestfactory.server.RequestFactoryUnicodeEscapingJreTest;
+import com.google.web.bindery.requestfactory.server.ServiceInheritanceJreTest;
+import com.google.web.bindery.requestfactory.server.ServiceLocatorTest;
+import com.google.web.bindery.requestfactory.shared.impl.SimpleEntityProxyIdTest;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Suite of RequestFactory tests that require the JRE (without GWT).
+ * <p>
+ * Note: these tests require gwt-user src on the classpath. To run in
+ * Eclipse, use Google Plugin for Eclipse to run as a GWT JUnit test
+ * or edit the Eclipse launch config and add the src folder to the classpath
+ * (click Classpath tab, User entries, Advanced..., Add folders)
+ */
+public class RequestFactoryJreSuite {
+  public static Test suite() {
+    TestSuite suite = new TestSuite(
+        "requestfactory package tests that require the JRE");
+    suite.addTestSuite(BoxesAndPrimitivesJreTest.class);
+    suite.addTestSuite(ComplexKeysJreTest.class);
+    suite.addTestSuite(FindServiceJreTest.class);
+    suite.addTestSuite(LocatorJreTest.class);
+    suite.addTestSuite(RequestFactoryExceptionPropagationJreTest.class);
+    suite.addTestSuite(RequestFactoryInterfaceValidatorTest.class);
+    suite.addTestSuite(RequestFactoryJreTest.class);
+    suite.addTestSuite(RequestFactoryUnicodeEscapingJreTest.class);
+    suite.addTestSuite(ServiceInheritanceJreTest.class);
+    suite.addTestSuite(ServiceLocatorTest.class);
+    suite.addTestSuite(SimpleEntityProxyIdTest.class);
+
+    return suite;
+  }
+
+  /**
+   * Used to test the JVM-only client package.
+   */
+  public static void main(String[] args) {
+    junit.textui.TestRunner.run(suite());
+  }
+}