Improve AutoBean code generation by reducing the total number of types declared by the generated code.
Allow pruning of unused proxy types by having an AutoBeanFactory per RequestContext type.
Patch by: bobv
Review by: rjrjr

Review at http://gwt-code-reviews.appspot.com/1369803


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9801 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/tools/api-checker/config/gwt22_23userApi.conf b/tools/api-checker/config/gwt22_23userApi.conf
index b752dd0..3c94955 100644
--- a/tools/api-checker/config/gwt22_23userApi.conf
+++ b/tools/api-checker/config/gwt22_23userApi.conf
@@ -15,7 +15,7 @@
 :**/server/**\
 :**/tools/**\
 :com/google/gwt/regexp/shared/**\
-:com/google/gwt/autobean/shared/impl/StringQuoter.java\
+:com/google/gwt/autobean/**/impl/**\
 :com/google/gwt/autobean/shared/ValueCodexHelper.java\
 :com/google/gwt/core/client/impl/WeakMapping.java\
 :com/google/gwt/core/ext/**\
@@ -82,7 +82,7 @@
 :**/tools/**\
 :user/src/com/google/gwt/regexp/shared/**\
 :user/src/com/google/gwt/autobean/shared/ValueCodexHelper.java\
-:user/src/com/google/gwt/autobean/shared/impl/StringQuoter.java\
+:user/src/com/google/gwt/autobean/**/impl/**\
 :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\
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 5aa2baf..ce068ca 100644
--- a/user/src/com/google/gwt/autobean/client/impl/AbstractAutoBeanFactory.java
+++ b/user/src/com/google/gwt/autobean/client/impl/AbstractAutoBeanFactory.java
@@ -28,31 +28,20 @@
  */
 public abstract class AbstractAutoBeanFactory implements AutoBeanFactory,
     EnumMap {
-  /**
-   * Implementations generated by subtypes. Used to implement the dynamic create
-   * methods.
-   */
-  protected interface Creator {
-    AutoBean<?> create();
 
-    AutoBean<?> create(Object delegate);
-  }
-
-  protected final Map<Class<?>, Creator> creators = new HashMap<Class<?>, Creator>();
   protected Map<Enum<?>, String> enumToStringMap;
   // This map is almost always one-to-one
   protected Map<String, List<Enum<?>>> stringsToEnumsMap;
+  private JsniCreatorMap creatorMap;
 
-  @SuppressWarnings("unchecked")
   public <T> AutoBean<T> create(Class<T> clazz) {
-    Creator c = creators.get(clazz);
-    return c == null ? null : (AutoBean<T>) c.create();
+    maybeInitializeCreatorMap();
+    return creatorMap.create(clazz, this);
   }
 
-  @SuppressWarnings("unchecked")
   public <T, U extends T> AutoBean<T> create(Class<T> clazz, U delegate) {
-    Creator c = creators.get(clazz);
-    return c == null ? null : (AutoBean<T>) c.create(delegate);
+    maybeInitializeCreatorMap();
+    return creatorMap.create(clazz, this, delegate);
   }
 
   /**
@@ -86,8 +75,17 @@
     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>();
diff --git a/user/src/com/google/gwt/autobean/client/impl/ClientPropertyContext.java b/user/src/com/google/gwt/autobean/client/impl/ClientPropertyContext.java
new file mode 100644
index 0000000..c89ae2d
--- /dev/null
+++ b/user/src/com/google/gwt/autobean/client/impl/ClientPropertyContext.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.autobean.client.impl;
+
+import com.google.gwt.autobean.shared.AutoBeanVisitor.CollectionPropertyContext;
+import com.google.gwt.autobean.shared.AutoBeanVisitor.MapPropertyContext;
+import com.google.gwt.autobean.shared.AutoBeanVisitor.ParameterizationVisitor;
+import com.google.gwt.autobean.shared.AutoBeanVisitor.PropertyContext;
+import com.google.gwt.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 Map#put(Object, Object)}.
+     */
+    public static native Setter mapSetter(Map<String, Object> values, String key) /*-{
+      return function(value) {
+        values.@java.util.Map::put(*)(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/gwt/autobean/client/impl/JsniCreatorMap.java b/user/src/com/google/gwt/autobean/client/impl/JsniCreatorMap.java
new file mode 100644
index 0000000..67f34fe
--- /dev/null
+++ b/user/src/com/google/gwt/autobean/client/impl/JsniCreatorMap.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.autobean.client.impl;
+
+import com.google.gwt.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/gwt/autobean/rebind/AutoBeanFactoryGenerator.java b/user/src/com/google/gwt/autobean/rebind/AutoBeanFactoryGenerator.java
index 3c4efdf..509cadc 100644
--- a/user/src/com/google/gwt/autobean/rebind/AutoBeanFactoryGenerator.java
+++ b/user/src/com/google/gwt/autobean/rebind/AutoBeanFactoryGenerator.java
@@ -16,6 +16,8 @@
 package com.google.gwt.autobean.rebind;
 
 import com.google.gwt.autobean.client.impl.AbstractAutoBeanFactory;
+import com.google.gwt.autobean.client.impl.ClientPropertyContext;
+import com.google.gwt.autobean.client.impl.JsniCreatorMap;
 import com.google.gwt.autobean.rebind.model.AutoBeanFactoryMethod;
 import com.google.gwt.autobean.rebind.model.AutoBeanFactoryModel;
 import com.google.gwt.autobean.rebind.model.AutoBeanMethod;
@@ -25,12 +27,10 @@
 import com.google.gwt.autobean.shared.AutoBeanFactory;
 import com.google.gwt.autobean.shared.AutoBeanUtils;
 import com.google.gwt.autobean.shared.AutoBeanVisitor;
-import com.google.gwt.autobean.shared.AutoBeanVisitor.CollectionPropertyContext;
-import com.google.gwt.autobean.shared.AutoBeanVisitor.MapPropertyContext;
-import com.google.gwt.autobean.shared.AutoBeanVisitor.PropertyContext;
 import com.google.gwt.autobean.shared.impl.AbstractAutoBean;
 import com.google.gwt.autobean.shared.impl.AbstractAutoBean.OneShotContext;
-import com.google.gwt.autobean.shared.impl.AbstractPropertyContext;
+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;
@@ -44,7 +44,6 @@
 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.dev.generator.NameFactory;
 import com.google.gwt.editor.rebind.model.ModelUtils;
 import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
 import com.google.gwt.user.rebind.SourceWriter;
@@ -308,32 +307,75 @@
    * Write an instance initializer block to populate the creators map.
    */
   private void writeDynamicMethods(SourceWriter sw) {
-    sw.println("{");
+    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;
       }
-      sw.println(
-          "creators.put(%s.class, new Creator() {",
-          ModelUtils.ensureBaseType(type.getPeerType()).getQualifiedSourceName());
-      if (type.isSimpleBean()) {
-        sw.indentln("public %1$s create() { return new %1$s(%2$s.this); }",
-            type.getQualifiedSourceName(), simpleSourceName);
+      String classLiteralAccessor;
+      JClassType peer = type.getPeerType();
+      String peerName = ModelUtils.ensureBaseType(peer).getQualifiedSourceName();
+      if (peer.isPublic()) {
+        classLiteralAccessor = peerName + ".class";
       } else {
-        sw.indentln("public %1$s create() { return null; }",
-            type.getQualifiedSourceName());
+        privatePeers.add(peer);
+        classLiteralAccessor = "classLit_" + peerName.replace('.', '_') + "()";
       }
-      // public FooAutoBean create(Object delegate) {
-      // return new FooAutoBean((Foo) delegate); }
-      sw.indentln("public %1$s create(Object delegate) {"
-          + " return new %1$s(%2$s.this, (%3$s) delegate); }",
-          type.getQualifiedSourceName(), simpleSourceName,
-          type.getPeerType().getQualifiedSourceName());
-      sw.println("});");
+      // 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) {
@@ -574,12 +616,20 @@
    * Generate traversal logic.
    */
   private void writeTraversal(SourceWriter sw, AutoBeanType type) {
-    NameFactory names = new NameFactory();
+    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());
+    sw.println("%s<String, Object> values = this.values;",
+        Map.class.getCanonicalName());
 
     for (AutoBeanMethod method : type.getMethods()) {
       if (!method.getAction().equals(JBeanMethod.GET)) {
@@ -602,26 +652,24 @@
 
       // The type of property influences the visitation
       String valueExpression = String.format(
-          "%1$s value = (%1$s) %2$s.getAutoBean(as().%3$s());",
+          "bean = (%1$s) %2$s.getAutoBean(as.%3$s());",
           AbstractAutoBean.class.getCanonicalName(),
           AutoBeanUtils.class.getCanonicalName(), method.getMethod().getName());
       String visitMethod;
-      Class<?> propertyContextType;
+      String visitVariable = "bean";
       if (method.isCollection()) {
-        propertyContextType = CollectionPropertyContext.class;
         visitMethod = "Collection";
       } else if (method.isMap()) {
-        propertyContextType = MapPropertyContext.class;
         visitMethod = "Map";
       } else if (method.isValueType()) {
-        propertyContextType = PropertyContext.class;
-        valueExpression = String.format("Object value = as().%s();",
+        valueExpression = String.format("value = as.%s();",
             method.getMethod().getName());
         visitMethod = "Value";
+        visitVariable = "value";
       } else {
         visitMethod = "Reference";
-        propertyContextType = PropertyContext.class;
       }
+      sw.println(valueExpression);
 
       // Map<List<Foo>, Bar> --> Map, List, Foo, Bar
       List<JType> typeList = new ArrayList<JType>();
@@ -634,86 +682,99 @@
        * payloads to be interpreted as different types). The leading underscore
        * allows purely numeric property names, which are valid JSON map keys.
        */
-      String propertyContextName = names.createName("_"
-          + method.getPropertyName() + "PropertyContext");
-      sw.println("class %s extends %s implements %s {", propertyContextName,
-          AbstractPropertyContext.class.getCanonicalName(),
-          propertyContextType.getCanonicalName());
+      // propertyContext = new CPContext(.....);
+      sw.println("propertyContext = new %s(",
+          ClientPropertyContext.class.getCanonicalName());
       sw.indent();
-      sw.println("%s() {", propertyContextName);
-      sw.indent();
-      sw.print("super(new Class<?>[] {");
-      boolean first = true;
-      for (JType lit : typeList) {
-        if (first) {
-          first = false;
+      // 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 {
-          sw.print(", ");
-        }
-        sw.print("%s.class",
-            ModelUtils.ensureBaseType(lit).getQualifiedSourceName());
-      }
-      sw.println("}, 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));
+          // Create a function that will update the values map
+          // CPContext.mapSetter(values, "foo");
+          sw.println("%s.mapSetter(values, \"%s\"),",
+              ClientPropertyContext.Setter.class.getCanonicalName(),
+              method.getPropertyName());
         }
       }
-      sw.println("});");
-      sw.outdent();
-      sw.println("}");
-      // Base method returns true.
-      if (!type.isSimpleBean() && setter == null) {
-        sw.println("public boolean canSet() { return false; }");
-      }
-      sw.println("public void set(Object obj) { ");
-      if (setter != null) {
-        // Prefer the setter if one exists
-        // as().setFoo((Foo) obj);
-        sw.indentln(
-            "as().%s((%s) obj);",
-            setter.getMethod().getName(),
-            ModelUtils.ensureBaseType(
-                setter.getMethod().getParameters()[0].getType()).getQualifiedSourceName());
-      } else if (type.isSimpleBean()) {
-        // Otherwise, fall back to a map assignment
-        sw.indentln("values.put(\"%s\", obj);", method.getPropertyName());
+      if (typeList.size() == 1) {
+        sw.println("%s.class",
+            ModelUtils.ensureBaseType(typeList.get(0)).getQualifiedSourceName());
       } else {
-        sw.indentln("throw new UnsupportedOperationException(\"No setter\");");
+        // 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.println("}");
       sw.outdent();
-      sw.println("}");
+      sw.println(");");
 
-      sw.print("{");
-      sw.indent();
-      sw.println("%1$s %1$s = new %1$s();", propertyContextName);
-
-      // Call the visit methods
-      sw.println(valueExpression);
       // if (visitor.visitReferenceProperty("foo", value, ctx))
-      sw.println("if (visitor.visit%sProperty(\"%s\", value, %s))",
-          visitMethod, method.getPropertyName(), propertyContextName);
+      sw.println("if (visitor.visit%sProperty(\"%s\", %s, propertyContext)) {",
+          visitMethod, method.getPropertyName(), visitVariable);
       if (!method.isValueType()) {
         // Cycle-detection in AbstractAutoBean.traverse
-        sw.indentln("if (value != null) { value.traverse(visitor, ctx); }");
+        sw.indentln("if (bean != null) { bean.traverse(visitor, ctx); }");
       }
-      // visitor.endVisitorReferenceProperty("foo", value, ctx);
-      sw.println("visitor.endVisit%sProperty(\"%s\", value, %s);", visitMethod,
-          method.getPropertyName(), propertyContextName);
-      sw.outdent();
       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/gwt/autobean/rebind/model/AutoBeanFactoryModel.java b/user/src/com/google/gwt/autobean/rebind/model/AutoBeanFactoryModel.java
index b073335..1ec0452 100644
--- a/user/src/com/google/gwt/autobean/rebind/model/AutoBeanFactoryModel.java
+++ b/user/src/com/google/gwt/autobean/rebind/model/AutoBeanFactoryModel.java
@@ -19,10 +19,12 @@
 import com.google.gwt.autobean.shared.AutoBeanFactory;
 import com.google.gwt.autobean.shared.AutoBeanFactory.Category;
 import com.google.gwt.autobean.shared.AutoBeanFactory.NoWrap;
+import com.google.gwt.autobean.shared.impl.EnumMap.ExtraEnums;
 import com.google.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;
@@ -35,7 +37,6 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
@@ -57,7 +58,7 @@
   private final List<AutoBeanFactoryMethod> methods = new ArrayList<AutoBeanFactoryMethod>();
   private final List<JMethod> objectMethods;
   private final TypeOracle oracle;
-  private final Map<JClassType, AutoBeanType> peers = new HashMap<JClassType, AutoBeanType>();
+  private final Map<JClassType, AutoBeanType> peers = new LinkedHashMap<JClassType, AutoBeanType>();
   private boolean poisoned;
 
   /**
@@ -100,6 +101,17 @@
       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()) {
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 d75e819..e936479 100644
--- a/user/src/com/google/gwt/autobean/rebind/model/AutoBeanMethod.java
+++ b/user/src/com/google/gwt/autobean/rebind/model/AutoBeanMethod.java
@@ -121,18 +121,23 @@
         map = toReturn.enumMap = new LinkedHashMap<JEnumConstant, String>();
       }
       for (JEnumConstant e : enumType.getEnumConstants()) {
-        String name;
-        PropertyName annotation = e.getAnnotation(PropertyName.class);
-        if (annotation == null) {
-          name = e.getName();
-        } else {
-          name = annotation.value();
-        }
+        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;
diff --git a/user/src/com/google/gwt/autobean/shared/impl/AbstractPropertyContext.java b/user/src/com/google/gwt/autobean/shared/impl/AbstractPropertyContext.java
deleted file mode 100644
index b04b0e9..0000000
--- a/user/src/com/google/gwt/autobean/shared/impl/AbstractPropertyContext.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright 2011 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.autobean.shared.impl;
-
-import com.google.gwt.autobean.shared.AutoBeanVisitor.ParameterizationVisitor;
-import com.google.gwt.autobean.shared.AutoBeanVisitor.PropertyContext;
-
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Provides base methods for generated implementations of PropertyContext.
- */
-public abstract class AbstractPropertyContext implements PropertyContext {
-
-  private final Class<?>[] types;
-  private final int[] paramCounts;
-
-  protected AbstractPropertyContext(Class<?>[] types, int[] paramCounts) {
-    this.types = types;
-    this.paramCounts = paramCounts;
-  }
-
-  public void accept(ParameterizationVisitor visitor) {
-    traverse(visitor, 0);
-  }
-
-  public boolean canSet() {
-    return true;
-  }
-
-  /**
-   * @see com.google.gwt.autobean.shared.AutoBeanVisitor.CollectionPropertyContext#getElementType()
-   */
-  public Class<?> getElementType() {
-    assert types.length >= 2;
-    assert List.class.equals(types[0]) || Set.class.equals(types[0]);
-    return types[1];
-  }
-
-  /**
-   * @see com.google.gwt.autobean.shared.AutoBeanVisitor.MapPropertyContext#getKeyType()
-   */
-  public Class<?> getKeyType() {
-    assert types.length >= 2;
-    assert Map.class.equals(types[0]);
-    return types[1];
-  }
-
-  public Class<?> getType() {
-    return types[0];
-  }
-
-  /**
-   * @see com.google.gwt.autobean.shared.AutoBeanVisitor.MapPropertyContext#getValueType()
-   */
-  public Class<?> getValueType() {
-    assert types.length >= 2;
-    assert Map.class.equals(types[0]);
-    return types[2];
-  }
-
-  private int traverse(ParameterizationVisitor visitor, int count) {
-    Class<?> type = types[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/gwt/autobean/shared/impl/EnumMap.java b/user/src/com/google/gwt/autobean/shared/impl/EnumMap.java
index d55d45f..3b53d1b 100644
--- a/user/src/com/google/gwt/autobean/shared/impl/EnumMap.java
+++ b/user/src/com/google/gwt/autobean/shared/impl/EnumMap.java
@@ -20,6 +20,13 @@
  * 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>> E getEnum(Class<E> clazz, String token);
 
   String getToken(Enum<?> e);
diff --git a/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java b/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java
index d8ff630..5c1d109 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java
@@ -21,21 +21,27 @@
 import com.google.gwt.autobean.shared.AutoBeanFactory;
 import com.google.gwt.autobean.shared.AutoBeanFactory.Category;
 import com.google.gwt.autobean.shared.AutoBeanFactory.NoWrap;
+import com.google.gwt.autobean.shared.impl.EnumMap.ExtraEnums;
 import com.google.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.requestfactory.client.impl.AbstractClientRequestFactory;
+import com.google.gwt.requestfactory.rebind.model.AcceptsModelVisitor;
 import com.google.gwt.requestfactory.rebind.model.ContextMethod;
 import com.google.gwt.requestfactory.rebind.model.EntityProxyModel;
 import com.google.gwt.requestfactory.rebind.model.EntityProxyModel.Type;
+import com.google.gwt.requestfactory.rebind.model.ModelVisitor;
 import com.google.gwt.requestfactory.rebind.model.RequestFactoryModel;
 import com.google.gwt.requestfactory.rebind.model.RequestMethod;
 import com.google.gwt.requestfactory.shared.EntityProxyId;
@@ -52,8 +58,12 @@
 import com.google.gwt.user.rebind.SourceWriter;
 
 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
@@ -62,6 +72,72 @@
  */
 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;
+    }
+
+    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;
@@ -93,7 +169,7 @@
     factory.setSuperclass(AbstractClientRequestFactory.class.getCanonicalName());
     factory.addImplementedInterface(typeName);
     SourceWriter sw = factory.createSourceWriter(context, pw);
-    writeAutoBeanFactory(sw);
+    writeAutoBeanFactory(sw, model.getAllProxyModels(), findExtraEnums(model));
     writeContextMethods(sw);
     writeContextImplementations();
     writeTypeMap(sw);
@@ -102,7 +178,79 @@
     return factory.getCreatedClassName();
   }
 
-  private void writeAutoBeanFactory(SourceWriter sw) {
+  /**
+   * 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(),
@@ -116,7 +264,7 @@
         AutoBeanFactory.class.getCanonicalName());
     sw.indent();
 
-    for (EntityProxyModel proxy : model.getAllProxyModels()) {
+    for (EntityProxyModel proxy : models) {
       // AutoBean<FooProxy> com_google_FooProxy();
       sw.println("%s<%s> %s();", AutoBean.class.getCanonicalName(),
           proxy.getQualifiedSourceName(),
@@ -126,11 +274,18 @@
     sw.println("}");
 
     // public static final Factory FACTORY = GWT.create(Factory.class);
-    sw.println("public static final Factory FACTORY=%s.create(Factory.class);",
-        GWT.class.getCanonicalName());
+    sw.println("public static Factory FACTORY;", GWT.class.getCanonicalName());
 
     // Write public accessor
-    sw.println("@Override public Factory getAutoBeanFactory() { return FACTORY; }");
+    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() {
@@ -155,6 +310,10 @@
           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();
diff --git a/user/src/com/google/gwt/requestfactory/rebind/model/AcceptsModelVisitor.java b/user/src/com/google/gwt/requestfactory/rebind/model/AcceptsModelVisitor.java
new file mode 100644
index 0000000..e3fb75c
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/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.gwt.requestfactory.rebind.model;
+
+/**
+ * A common interface for model types.
+ */
+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 ae6ce92..367f5b7 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/model/ContextMethod.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/model/ContextMethod.java
@@ -26,7 +26,7 @@
 /**
  * Represents a service endpoint.
  */
-public class ContextMethod {
+public class ContextMethod implements AcceptsModelVisitor {
 
   /**
    * Builds a {@link ContextMethod}.
@@ -68,6 +68,15 @@
   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;
   }
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 da85d8c..ffcae99 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/model/EntityProxyModel.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/model/EntityProxyModel.java
@@ -21,7 +21,7 @@
 /**
  * Represents an EntityProxy subtype.
  */
-public class EntityProxyModel {
+public class EntityProxyModel implements AcceptsModelVisitor {
   /**
    * Builds {@link EntityProxyModel}.
    */
@@ -83,6 +83,15 @@
   private EntityProxyModel() {
   }
 
+  public void accept(ModelVisitor visitor) {
+    if (visitor.visit(this)) {
+      for (RequestMethod method : requestMethods) {
+        method.accept(visitor);
+      }
+    }
+    visitor.endVisit(this);
+  }
+
   public Class<?> getProxyFor() {
     return proxyFor;
   }
diff --git a/user/src/com/google/gwt/requestfactory/rebind/model/ModelVisitor.java b/user/src/com/google/gwt/requestfactory/rebind/model/ModelVisitor.java
new file mode 100644
index 0000000..a380da8
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/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.gwt.requestfactory.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/gwt/requestfactory/rebind/model/RequestFactoryModel.java b/user/src/com/google/gwt/requestfactory/rebind/model/RequestFactoryModel.java
index d634624..142fdfa 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/model/RequestFactoryModel.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/model/RequestFactoryModel.java
@@ -53,7 +53,7 @@
 /**
  * Represents a RequestFactory interface declaration.
  */
-public class RequestFactoryModel {
+public class RequestFactoryModel implements AcceptsModelVisitor {
   static String badContextReturnType(JMethod method,
       JClassType requestInterface, JClassType instanceRequestInterface) {
     return String.format(
@@ -146,6 +146,18 @@
     }
   }
 
+  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());
   }
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 0761bcf..867764c 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/model/RequestMethod.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/model/RequestMethod.java
@@ -28,7 +28,7 @@
  * be a method declared in a RequestContext or a getter or setter on an
  * EntityProxy.
  */
-public class RequestMethod {
+public class RequestMethod implements AcceptsModelVisitor {
 
   /**
    * Builds a {@link ContextMethod}.
@@ -127,6 +127,13 @@
   private RequestMethod() {
   }
 
+  public void accept(ModelVisitor visitor) {
+    if (visitor.visit(this)) {
+      // Empty
+    }
+    visitor.endVisit(this);
+  }
+
   public String getApiVersion() {
     return apiVersion;
   }
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 7b0ad26..2f74d22 100644
--- a/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestContext.java
+++ b/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestContext.java
@@ -18,6 +18,7 @@
 import com.google.gwt.autobean.server.impl.BeanMethod;
 import com.google.gwt.autobean.server.impl.TypeUtils;
 import com.google.gwt.autobean.shared.AutoBean.PropertyName;
+import com.google.gwt.autobean.shared.AutoBeanFactory;
 import com.google.gwt.requestfactory.shared.InstanceRequest;
 import com.google.gwt.requestfactory.shared.JsonRpcContent;
 import com.google.gwt.requestfactory.shared.JsonRpcWireName;
@@ -183,4 +184,9 @@
     super(factory, dialect);
     this.dialect = dialect;
   }
+
+  @Override
+  protected AutoBeanFactory getAutoBeanFactory() {
+    return ((InProcessRequestFactory) getRequestFactory()).getAutoBeanFactory();
+  }
 }
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 505de0a..501a9d6 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java
@@ -17,9 +17,11 @@
 
 import static com.google.gwt.requestfactory.shared.impl.BaseProxyCategory.stableId;
 import static com.google.gwt.requestfactory.shared.impl.Constants.REQUEST_CONTEXT;
+import static com.google.gwt.requestfactory.shared.impl.Constants.STABLE_ID;
 
 import com.google.gwt.autobean.shared.AutoBean;
 import com.google.gwt.autobean.shared.AutoBeanCodex;
+import com.google.gwt.autobean.shared.AutoBeanFactory;
 import com.google.gwt.autobean.shared.AutoBeanUtils;
 import com.google.gwt.autobean.shared.AutoBeanVisitor;
 import com.google.gwt.autobean.shared.Splittable;
@@ -65,7 +67,7 @@
 /**
  * Base implementations for RequestContext services.
  */
-public class AbstractRequestContext implements RequestContext,
+public abstract class AbstractRequestContext implements RequestContext,
     EntityCodex.EntitySource {
   /**
    * Allows the payload dialect to be injected into the AbstractRequestContext
@@ -162,7 +164,7 @@
       Class<BaseProxy> target = (Class<BaseProxy>) invocations.get(0).getRequestData().getReturnType();
 
       SimpleProxyId<BaseProxy> id = getRequestFactory().allocateId(target);
-      AutoBean<BaseProxy> bean = getRequestFactory().createProxy(target, id);
+      AutoBean<BaseProxy> bean = createProxy(target, id);
       AutoBeanCodex.decodeInto(result, bean);
 
       if (callback != null) {
@@ -300,6 +302,7 @@
       }
     }
   }
+
   private class MyViolation implements Violation {
 
     private final BaseProxy currentProxy;
@@ -356,18 +359,16 @@
       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;
 
+  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.
@@ -397,10 +398,24 @@
     checkLocked();
 
     SimpleProxyId<T> id = requestFactory.allocateId(clazz);
-    AutoBean<T> created = requestFactory.createProxy(clazz, id);
+    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);
   }
@@ -535,14 +550,14 @@
    */
   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
@@ -579,6 +594,12 @@
   }
 
   /**
+   * 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.
    */
@@ -633,7 +654,7 @@
     AutoBean<Q> bean = (AutoBean<Q>) returnedProxies.get(id);
     if (bean == null) {
       Class<Q> proxyClass = id.getProxyClass();
-      bean = requestFactory.createProxy(proxyClass, id);
+      bean = createProxy(proxyClass, id);
       returnedProxies.put(id, bean);
     }
 
@@ -656,7 +677,7 @@
     AutoBean<?> parent;
     if (stableId.isEphemeral()) {
       // Newly-created object, use a blank object to compare against
-      parent = requestFactory.createProxy(stableId.getProxyClass(), stableId);
+      parent = createProxy(stableId.getProxyClass(), stableId);
 
       // Newly-created objects go into the persist operation bucket
       operation.setOperation(WriteOperation.PERSIST);
@@ -665,7 +686,7 @@
       operation.setStrength(Strength.EPHEMERAL);
     } else if (stableId.isSynthetic()) {
       // Newly-created object, use a blank object to compare against
-      parent = requestFactory.createProxy(stableId.getProxyClass(), stableId);
+      parent = createProxy(stableId.getProxyClass(), stableId);
 
       // Newly-created objects go into the persist operation bucket
       operation.setOperation(WriteOperation.PERSIST);
@@ -1040,9 +1061,9 @@
       for (Object o : (Iterable<?>) arg) {
         retainArg(o);
       }
-    } else if (arg instanceof EntityProxy) {
+    } else if (arg instanceof BaseProxy) {
       // Calling edit will validate and set up the tracking we need
-      edit((EntityProxy) arg);
+      edit((BaseProxy) arg);
     }
   }
 
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 25c202b..3c26cf4 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestFactory.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestFactory.java
@@ -15,12 +15,8 @@
  */
 package com.google.gwt.requestfactory.shared.impl;
 
-import static com.google.gwt.requestfactory.shared.impl.Constants.STABLE_ID;
-
-import com.google.gwt.autobean.shared.AutoBean;
 import com.google.gwt.autobean.shared.AutoBeanFactory;
 import com.google.gwt.event.shared.EventBus;
-import com.google.gwt.requestfactory.shared.BaseProxy;
 import com.google.gwt.requestfactory.shared.EntityProxy;
 import com.google.gwt.requestfactory.shared.EntityProxyId;
 import com.google.gwt.requestfactory.shared.ProxySerializer;
@@ -52,27 +48,18 @@
   };
   private RequestTransport transport;
 
-  /**
-   * 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 EntityProxy type "
-          + clazz.getName());
-    }
-    created.setTag(STABLE_ID, id);
-    return created;
-  }
-
   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);
+        AbstractRequestFactory.this, AbstractRequestContext.Dialect.STANDARD) {
+      @Override
+      protected AutoBeanFactory getAutoBeanFactory() {
+        return AbstractRequestFactory.this.getAutoBeanFactory();
+      }
+    };
     return new AbstractRequest<P>(context) {
       {
         requestContext.addInvocation(this);
@@ -132,7 +119,11 @@
 
   /**
    * Implementations of EntityProxies are provided by an AutoBeanFactory, which
-   * is itself a generated type.
+   * 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();
 
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 7e175c2..1e53bbb 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/ProxySerializerImpl.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/ProxySerializerImpl.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.autobean.shared.AutoBean;
 import com.google.gwt.autobean.shared.AutoBeanCodex;
+import com.google.gwt.autobean.shared.AutoBeanFactory;
 import com.google.gwt.autobean.shared.AutoBeanUtils;
 import com.google.gwt.autobean.shared.AutoBeanVisitor;
 import com.google.gwt.autobean.shared.Splittable;
@@ -141,6 +142,11 @@
   }
 
   @Override
+  protected AutoBeanFactory getAutoBeanFactory() {
+    return getRequestFactory().getAutoBeanFactory();
+  }
+
+  @Override
   SimpleProxyId<BaseProxy> getId(IdMessage op) {
     if (Strength.SYNTHETIC.equals(op.getStrength())) {
       return getRequestFactory().allocateSyntheticId(
diff --git a/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java b/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java
index b43e4ae..ad85e81 100644
--- a/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java
+++ b/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java
@@ -18,6 +18,8 @@
 import com.google.gwt.requestfactory.shared.EntityProxy;
 import com.google.gwt.requestfactory.shared.EntityProxyChange;
 import com.google.gwt.requestfactory.shared.EntityProxyId;
+import com.google.gwt.requestfactory.shared.OnlyUsedByRequestContextMethod;
+import com.google.gwt.requestfactory.shared.OnlyUsedInListProxy;
 import com.google.gwt.requestfactory.shared.Receiver;
 import com.google.gwt.requestfactory.shared.Request;
 import com.google.gwt.requestfactory.shared.RequestContext;
@@ -476,6 +478,22 @@
   }
 
   /**
+   * 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() {
@@ -2217,6 +2235,15 @@
   }
 
   /**
+   * 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() {
diff --git a/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java b/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java
index d02ce15..4012b21 100644
--- a/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java
+++ b/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.requestfactory.server;
 
+import com.google.gwt.requestfactory.shared.OnlyUsedByRequestContextMethod;
 import com.google.gwt.requestfactory.shared.SimpleEnum;
 
 import java.math.BigDecimal;
@@ -258,6 +259,12 @@
     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(
@@ -364,6 +371,9 @@
     return null;
   }
 
+  public static void returnOnlyUsedInParameterization(List<SimpleFoo> values) {
+  }
+
   public static SimpleFoo returnSimpleFooSubclass() {
     return new SimpleFoo() {
     };
diff --git a/user/test/com/google/gwt/requestfactory/shared/OnlyUsedByRequestContextMethod.java b/user/test/com/google/gwt/requestfactory/shared/OnlyUsedByRequestContextMethod.java
new file mode 100644
index 0000000..90d1c97
--- /dev/null
+++ b/user/test/com/google/gwt/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.gwt.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/gwt/requestfactory/shared/OnlyUsedInListProxy.java b/user/test/com/google/gwt/requestfactory/shared/OnlyUsedInListProxy.java
new file mode 100644
index 0000000..a96aa9e
--- /dev/null
+++ b/user/test/com/google/gwt/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.gwt.requestfactory.shared;
+
+import com.google.gwt.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/gwt/requestfactory/shared/SimpleFooRequest.java b/user/test/com/google/gwt/requestfactory/shared/SimpleFooRequest.java
index 8fcc205..6afdd34 100644
--- a/user/test/com/google/gwt/requestfactory/shared/SimpleFooRequest.java
+++ b/user/test/com/google/gwt/requestfactory/shared/SimpleFooRequest.java
@@ -82,6 +82,8 @@
 
   Request<String> processString(String value);
 
+  Request<Void> receiveEnum(OnlyUsedByRequestContextMethod value);
+
   InstanceRequest<SimpleFooProxy, Void> receiveNull(String value);
 
   Request<Void> receiveNullList(List<SimpleFooProxy> value);
@@ -104,6 +106,9 @@
 
   Request<String> returnNullString();
 
+  Request<Void> returnOnlyUsedInParameterization(
+      List<OnlyUsedInListProxy> values);
+
   Request<SimpleFooProxy> returnSimpleFooSubclass();
 
   Request<SimpleValueProxy> returnValueProxy();