Adding 2.1.0 tag


git-svn-id: https://google-web-toolkit.googlecode.com/svn/tags/2.1.0@9145 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/build.xml b/dev/build.xml
index 00a5c2b..8af5836 100755
--- a/dev/build.xml
+++ b/dev/build.xml
@@ -41,6 +41,7 @@
         <pathelement location="${gwt.tools.lib}/w3c/sac/sac-1.3.jar" />
         <pathelement location="${gwt.tools.lib}/w3c/flute/flute-1.3-gg1.jar" />
         <pathelement location="${gwt.tools}/redist/json/r2_20080312/json-1.5.jar" />
+        <pathelement location="${gwt.tools.lib}/hibernate/validator/hibernate-validator-4.1.0.Final.jar" />
         <pathelement location="${gwt.tools.lib}/javax/validation/validation-api-1.0.0.GA.jar" />
         <pathelement location="${gwt.build.lib}/gwt-dev-${build.host.platform}.jar" />
         <pathelement location="${alldeps.jar}" />
diff --git a/distro-source/build.xml b/distro-source/build.xml
index 1bb9439..7918e52 100755
--- a/distro-source/build.xml
+++ b/distro-source/build.xml
@@ -22,6 +22,7 @@
       <zipfileset file="${gwt.build.lib}/gwt-dev.jar" prefix="${project.distname}" />
       <zipfileset file="${gwt.build.lib}/gwt-user.jar" prefix="${project.distname}" />
       <zipfileset file="${gwt.build.lib}/gwt-servlet.jar" prefix="${project.distname}" />
+      <zipfileset file="${gwt.build.lib}/gwt-servlet-deps.jar" prefix="${project.distname}" />
       <zipfileset file="${gwt.build.lib}/gwt-benchmark-viewer.war" prefix="${project.distname}" />
       <zipfileset file="${gwt.build.lib}/gwt-soyc-vis.jar" prefix="${project.distname}" />
       <zipfileset file="${gwt.build.lib}/gwt-api-checker.jar" prefix="${project.distname}" />
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/PersonEditorWorkflow.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/PersonEditorWorkflow.java
index 81dae75..51ce66f 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/PersonEditorWorkflow.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/PersonEditorWorkflow.java
@@ -97,6 +97,7 @@
         }
       }
     }, KeyUpEvent.getType());
+    this.favorite.setVisible(false);
   }
 
   /**
@@ -156,8 +157,9 @@
   private void edit(RequestContext requestContext) {
     editorDriver = GWT.create(Driver.class);
     editorDriver.initialize(requestFactory, personEditor);
-
+    
     if (requestContext == null) {
+      this.favorite.setVisible(true);
       fetchAndEdit();
       return;
     }
diff --git a/samples/validation/build.xml b/samples/validation/build.xml
index 5f960a7..844f146 100755
--- a/samples/validation/build.xml
+++ b/samples/validation/build.xml
@@ -20,7 +20,9 @@
     <pathelement location="${gwt.tools.lib}/javax/xml/stream/stax-api-1.0-2.jar" />
   </path>
   <fileset id="sample.server.libs" dir="${gwt.tools.lib}">
+    <include name="javax/validation/validation-api-1.0.0.GA-sources.jar" />
     <include name="hibernate/validator/hibernate-validator-4.1.0.Final.jar" />
+    <include name="hibernate/validator/hibernate-validator-4.1.0.Final-sources.jar" />
     <include name="apache/log4j/log4j-1.2.16.jar" />
     <include name="slf4j/slf4j-api/slf4j-api-1.6.1.jar" />
     <include name="slf4j/slf4j-log4j12/slf4j-log4j12-1.6.1.jar" />
diff --git a/servlet/build.xml b/servlet/build.xml
index 6440445..e30dace 100755
--- a/servlet/build.xml
+++ b/servlet/build.xml
@@ -4,12 +4,29 @@
   <import file="${gwt.root}/common.ant.xml" />
 
   <property.ensure name="gwt.user.root" location="${gwt.root}/user" />
+  <property.ensure name="gwt.dev.build" location="${gwt.build.out}/dev" />
   <property.ensure name="gwt.user.build" location="${gwt.build.out}/user" />
+  <property.ensure name="gwt.dev.bin" location="${gwt.dev.build}/bin" />
   <property.ensure name="gwt.user.bin" location="${gwt.user.build}/bin" />
 
-  <target name="build" description="Packages this project into a jar">
+  <property name="project.lib.deps"
+      location="${gwt.build.lib}/gwt-${ant.project.name}-deps.jar" />
+
+  <target name="build" description="Packages this project into a jar"
+      depends="-servlet, -deps" />
+
+  <target name="clean" description="Cleans this project's intermediate and output files">
+    <delete file="${project.lib}" />
+    <delete file="${project.lib.deps}" />
+  </target>
+
+  <target name="-servlet" description="Packages this project into a jar">
     <mkdir dir="${gwt.build.lib}" />
     <gwt.jar>
+      <fileset dir="${gwt.dev.bin}">
+        <include name="com/google/gwt/dev/asm/**" />
+        <include name="com/google/gwt/dev/util/Name*.class" />
+      </fileset>
       <fileset dir="${gwt.user.bin}">
         <exclude name="**/rebind/**" />
         <exclude name="**/tools/**" />
@@ -21,13 +38,15 @@
         <exclude name="com/google/gwt/junit/server/**" />
         <exclude name="com/google/gwt/benchmarks/*" />
       </fileset>
+    </gwt.jar>
+  </target>
+
+  <target name="-deps" description="Packages this project's dependencies into a jar">
+    <mkdir dir="${gwt.build.lib}" />
+    <gwt.jar destfile="${project.lib.deps}">
       <!-- The following two jars satisfy RequestFactory dependencies. -->
       <zipfileset src="${gwt.tools.redist}/json/r2_20080312/json-1.5.jar" />
       <zipfileset src="${gwt.tools.lib}/javax/validation/validation-api-1.0.0.GA.jar" />
     </gwt.jar>
   </target>
-
-  <target name="clean" description="Cleans this project's intermediate and output files">
-    <delete file="${project.lib}" />
-  </target>
 </project>
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 be744e4..9922620 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/model/ContextMethod.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/model/ContextMethod.java
@@ -52,17 +52,12 @@
     public void setRequestMethods(List<RequestMethod> requestMethods) {
       toReturn.requestMethods = requestMethods;
     }
-
-    public void setServiceClass(Class<?> serviceClass) {
-      toReturn.serviceClass = serviceClass;
-    }
   }
 
   private String interfaceName;
   private String methodName;
   private String packageName;
   private List<RequestMethod> requestMethods;
-  private Class<?> serviceClass;
   private String simpleSourceName;
 
   private ContextMethod() {
@@ -95,10 +90,6 @@
     return Collections.unmodifiableList(requestMethods);
   }
 
-  public Class<?> getServiceClass() {
-    return serviceClass;
-  }
-
   public String getSimpleSourceName() {
     return simpleSourceName;
   }
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 3803b1f..77ce999 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/model/RequestFactoryModel.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/model/RequestFactoryModel.java
@@ -28,13 +28,13 @@
 import com.google.gwt.requestfactory.shared.EntityProxy;
 import com.google.gwt.requestfactory.shared.InstanceRequest;
 import com.google.gwt.requestfactory.shared.ProxyFor;
+import com.google.gwt.requestfactory.shared.ProxyForName;
 import com.google.gwt.requestfactory.shared.Request;
 import com.google.gwt.requestfactory.shared.RequestContext;
 import com.google.gwt.requestfactory.shared.RequestFactory;
 import com.google.gwt.requestfactory.shared.Service;
+import com.google.gwt.requestfactory.shared.ServiceName;
 
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -159,13 +159,12 @@
   private void buildContextMethod(ContextMethod.Builder contextBuilder,
       JClassType contextType) throws UnableToCompleteException {
     Service serviceAnnotation = contextType.getAnnotation(Service.class);
-    if (serviceAnnotation == null) {
+    ServiceName serviceNameAnnotation = contextType.getAnnotation(ServiceName.class);
+    if (serviceAnnotation == null && serviceNameAnnotation == null) {
       poison("RequestContext subtype %s is missing a @%s annotation",
           contextType.getQualifiedSourceName(), Service.class.getSimpleName());
       return;
     }
-    Class<?> serviceClass = serviceAnnotation.value();
-    contextBuilder.setServiceClass(serviceClass);
 
     List<RequestMethod> requestMethods = new ArrayList<RequestMethod>();
     for (JMethod method : contextType.getInheritableMethods()) {
@@ -177,8 +176,7 @@
       RequestMethod.Builder methodBuilder = new RequestMethod.Builder();
       methodBuilder.setDeclarationMethod(method);
 
-      if (!validateContextMethodAndSetDataType(methodBuilder, method,
-          serviceClass)) {
+      if (!validateContextMethodAndSetDataType(methodBuilder, method)) {
         continue;
       }
 
@@ -193,20 +191,6 @@
     throw new UnableToCompleteException();
   }
 
-  /**
-   * Returns a list of public methods that match the given methodName.
-   */
-  private List<Method> findMethods(Class<?> domainType, String methodName) {
-    List<Method> toReturn = new ArrayList<Method>();
-    for (Method method : domainType.getMethods()) {
-      if (methodName.equals(method.getName())
-          && (method.getModifiers() & Modifier.PUBLIC) != 0) {
-        toReturn.add(method);
-      }
-    }
-    return toReturn;
-  }
-
   private EntityProxyModel getEntityProxyType(JClassType entityProxyType)
       throws UnableToCompleteException {
     entityProxyType = ModelUtils.ensureBaseType(entityProxyType);
@@ -225,16 +209,13 @@
 
       // Get the server domain object type
       ProxyFor proxyFor = entityProxyType.getAnnotation(ProxyFor.class);
-      if (proxyFor == null) {
-        poison("The %s type does not have a @%s annotation",
+      ProxyForName proxyForName = entityProxyType.getAnnotation(ProxyForName.class);
+      if (proxyFor == null && proxyForName == null) {
+        poison("The %s type does not have a @%s or @%s annotation",
             entityProxyType.getQualifiedSourceName(),
-            ProxyFor.class.getSimpleName());
+            ProxyFor.class.getSimpleName(), ProxyForName.class.getSimpleName());
         // early exit, because further processing causes NPEs in numerous spots
         die(poisonedMessage());
-      } else {
-        Class<?> domainType = proxyFor.value();
-        builder.setProxyFor(domainType);
-        validateDomainType(domainType);
       }
 
       // Look at the methods declared on the EntityProxy
@@ -267,9 +248,7 @@
         }
         validateTransportableType(methodBuilder, transportedType, false);
         RequestMethod requestMethod = methodBuilder.build();
-        if (validateDomainBeanMethod(requestMethod, builder)) {
-          requestMethods.add(requestMethod);
-        }
+        requestMethods.add(requestMethod);
       }
       builder.setRequestMethods(requestMethods);
 
@@ -280,20 +259,6 @@
     return toReturn;
   }
 
-  private boolean isStatic(Method domainMethod) {
-    return (domainMethod.getModifiers() & Modifier.STATIC) != 0;
-  }
-
-  private String methodLocation(Method domainMethod) {
-    return domainMethod.getDeclaringClass().getName() + "."
-        + domainMethod.getName();
-  }
-
-  private String methodLocation(JMethod proxyMethod) {
-    return proxyMethod.getEnclosingType().getName() + "."
-        + proxyMethod.getName();
-  }
-
   private void poison(String message, Object... args) {
     logger.log(TreeLogger.ERROR, String.format(message, args));
     poisoned = true;
@@ -303,7 +268,7 @@
    * Examine a RequestContext method to see if it returns a transportable type.
    */
   private boolean validateContextMethodAndSetDataType(
-      RequestMethod.Builder methodBuilder, JMethod method, Class<?> serviceClass)
+      RequestMethod.Builder methodBuilder, JMethod method)
       throws UnableToCompleteException {
     JClassType requestReturnType = method.getReturnType().isInterface();
     JClassType invocationReturnType;
@@ -314,40 +279,13 @@
       return false;
     }
 
-    /*
-     * TODO: bad assumption Implicit assumption is that the Service and ProxyFor
-     * classes are the same. This is because an instance method should
-     * technically be looked up on the class that is the instance parameter, and
-     * not on the serviceClass, which consists of static service methods. Can't
-     * be fixed until it is fixed in JsonRequestProcessor.
-     */
-    Method domainMethod = validateExistsAndNotOverriden(method, serviceClass,
-        false);
-    if (domainMethod == null) {
-      return false;
-    }
-
     if (instanceRequestInterface.isAssignableFrom(requestReturnType)) {
-      if (isStatic(domainMethod)) {
-        poison("Method %s.%s is an instance method, "
-            + "while the corresponding method on %s is static",
-            method.getEnclosingType().getName(), method.getName(),
-            serviceClass.getName());
-        return false;
-      }
       // Instance method invocation
       JClassType[] params = ModelUtils.findParameterizationOf(
           instanceRequestInterface, requestReturnType);
       methodBuilder.setInstanceType(getEntityProxyType(params[0]));
       invocationReturnType = params[1];
     } else if (requestInterface.isAssignableFrom(requestReturnType)) {
-      if (!isStatic(domainMethod)) {
-        poison("Method %s.%s is a static method, "
-            + "while the corresponding method on %s is not",
-            method.getEnclosingType().getName(), method.getName(),
-            serviceClass.getName());
-        return false;
-      }
       // Static method invocation
       JClassType[] params = ModelUtils.findParameterizationOf(requestInterface,
           requestReturnType);
@@ -363,152 +301,14 @@
     // Validate the parameters
     boolean paramsOk = true;
     JParameter[] params = method.getParameters();
-    Class<?>[] domainParams = domainMethod.getParameterTypes();
-    if (params.length != domainParams.length) {
-      poison("Method %s.%s parameters do not match same method on %s",
-          method.getEnclosingType().getName(), method.getName(),
-          serviceClass.getName());
-    }
     for (int i = 0; i < params.length; ++i) {
       JParameter param = params[i];
-      Class<?> domainParam = domainParams[i];
       paramsOk = validateTransportableType(new RequestMethod.Builder(),
           param.getType(), false)
           && paramsOk;
-      paramsOk = validateProxyAndDomainTypeEquals(param.getType(), domainParam,
-          i, methodLocation(method), methodLocation(domainMethod)) && paramsOk;
     }
 
-    return validateTransportableType(methodBuilder, invocationReturnType, true)
-        && validateProxyAndDomainTypeEquals(invocationReturnType,
-            domainMethod.getReturnType(), -1, methodLocation(method),
-            methodLocation(domainMethod)) && paramsOk;
-  }
-
-  /**
-   * Examine a domain method to see if it matches the proxy method.
-   */
-  private boolean validateDomainBeanMethod(RequestMethod requestMethod,
-      EntityProxyModel.Builder entityBuilder) throws UnableToCompleteException {
-    JMethod proxyMethod = requestMethod.getDeclarationMethod();
-    // check if method exists on domain object
-    Class<?> domainType = entityBuilder.peek().getProxyFor();
-    Method domainMethod = validateExistsAndNotOverriden(proxyMethod,
-        domainType, true);
-    if (domainMethod == null) {
-      return false;
-    }
-
-    boolean isGetter = proxyMethod.getName().startsWith("get");
-    if (isGetter) {
-      // compare return type of domain to proxy return type
-      String returnTypeName = domainMethod.getReturnType().getName();
-      // isEntityType() returns true for collections, but we want the Collection
-      String propertyTypeName = requestMethod.isCollectionType()
-          || requestMethod.isValueType()
-          ? requestMethod.getDataType().getQualifiedBinaryName()
-          : requestMethod.getEntityType().getProxyFor().getName();
-      if (!returnTypeName.equals(propertyTypeName)) {
-        poison("Method %s.%s return type %s does not match return type %s "
-            + " of method %s.%s", domainType.getName(), domainMethod.getName(),
-            returnTypeName, propertyTypeName,
-            proxyMethod.getEnclosingType().getName(), proxyMethod.getName());
-        return false;
-      }
-    }
-    JParameter[] proxyParams = proxyMethod.getParameters();
-    Class<?>[] domainParams = domainMethod.getParameterTypes();
-    if (proxyParams.length != domainParams.length) {
-      poison("Method %s.%s parameter mismatch with %s.%s",
-          proxyMethod.getEnclosingType().getName(), proxyMethod.getName(),
-          domainType.getName(), domainMethod.getName());
-      return false;
-    }
-    for (int i = 0; i < proxyParams.length; i++) {
-      JType proxyParam = proxyParams[i].getType();
-      Class<?> domainParam = domainParams[i];
-      if (!validateProxyAndDomainTypeEquals(proxyParam, domainParam, i,
-          methodLocation(proxyMethod), methodLocation(domainMethod))) {
-        poison("Parameter %d of %s.%s doesn't match method %s.%s", i,
-            proxyMethod.getEnclosingType().getName(), proxyMethod.getName(),
-            domainType.getName(), domainMethod.getName());
-        return false;
-      }
-    }
-    return true;
-  }
-
-  /**
-   * Examine a domain type and see if it includes a getId() method.
-   */
-  private boolean validateDomainType(Class<?> domainType) {
-    try {
-      domainType.getMethod("getId");
-    } catch (NoSuchMethodException e) {
-      poison("The class %s is missing method getId()", domainType.getName());
-      return false;
-    }
-    try {
-      domainType.getMethod("getVersion");
-    } catch (NoSuchMethodException e) {
-      poison("The class %s is missing method getVersion()",
-          domainType.getName());
-      return false;
-    }
-    return true;
-  }
-
-  private Method validateExistsAndNotOverriden(JMethod clientMethod,
-      Class<?> serverType, boolean isGetterOrSetter) {
-    List<Method> domainMethods = findMethods(serverType, clientMethod.getName());
-    if (domainMethods.size() == 0) {
-      poison("Method %s.%s has no corresponding public method on %s",
-          clientMethod.getEnclosingType().getQualifiedBinaryName(),
-          clientMethod.getName(), serverType.getName());
-      return null;
-    }
-    if (domainMethods.size() > 1) {
-      poison("Method %s.%s is overloaded on %s",
-          clientMethod.getEnclosingType().getQualifiedBinaryName(),
-          clientMethod.getName(), serverType.getName());
-      return null;
-    }
-    Method domainMethod = domainMethods.get(0);
-    if (isGetterOrSetter && isStatic(domainMethod)) {
-      poison("Method %s.%s is declared static", serverType.getName(),
-          domainMethod.getName());
-      return null;
-    }
-    return domainMethod;
-  }
-
-  /**
-   * Compare type from Proxy and Domain.
-   */
-  private boolean validateProxyAndDomainTypeEquals(JType proxyType,
-      Class<?> domainType, int paramNumber, String clientMethod,
-      String serverMethod) throws UnableToCompleteException {
-    boolean matchOk = false;
-    if (ModelUtils.isValueType(oracle, proxyType)
-        || collectionInterface.isAssignableFrom(proxyType.isClassOrInterface())) {
-      // allow int to match int or Integer
-      matchOk = proxyType.getQualifiedSourceName().equals(
-          ModelUtils.maybeAutobox(domainType).getName())
-          || proxyType.getQualifiedSourceName().equals(domainType.getName());
-    } else {
-      matchOk = getEntityProxyType(proxyType.isClassOrInterface()).getProxyFor().equals(
-          domainType);
-    }
-    if (!matchOk) {
-      if (paramNumber < 0) {
-        poison("Return type of method %s does not match method %s",
-            clientMethod, serverMethod);
-      } else {
-        poison("Parameter %d of method %s does not match method %s",
-            paramNumber, clientMethod, serverMethod);
-      }
-    }
-    return matchOk;
+    return validateTransportableType(methodBuilder, invocationReturnType, true);
   }
 
   /**
diff --git a/user/src/com/google/gwt/requestfactory/server/DefaultSecurityProvider.java b/user/src/com/google/gwt/requestfactory/server/DefaultSecurityProvider.java
index 7b60641..cdcb90f 100644
--- a/user/src/com/google/gwt/requestfactory/server/DefaultSecurityProvider.java
+++ b/user/src/com/google/gwt/requestfactory/server/DefaultSecurityProvider.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,25 +16,31 @@
 package com.google.gwt.requestfactory.server;
 
 import com.google.gwt.requestfactory.shared.Service;
+import com.google.gwt.requestfactory.shared.ServiceName;
 
 /**
  * A security provider that enforces
  * {@link com.google.gwt.requestfactory.shared.Service} annotations.
  */
-public class DefaultSecurityProvider implements RequestSecurityProvider {
+class DefaultSecurityProvider implements RequestSecurityProvider {
 
   public void checkClass(Class<?> clazz) throws SecurityException {
     Service service = clazz.getAnnotation(Service.class);
-    if (service == null) {
-      throw new SecurityException(
-          "Class " + clazz.getName() + " does not have a @Service annotation.");
+    ServiceName serviceName = clazz.getAnnotation(ServiceName.class);
+    String className;
+    if (service != null) {
+      className = service.value().getName();
+    } else if (serviceName != null) {
+      className = serviceName.value();
+    } else {
+      throw new SecurityException("Class " + clazz.getName()
+          + " does not have a @Service annotation.");
     }
     try {
-      Class.forName(service.value().getCanonicalName());
+      Class.forName(className);
     } catch (ClassNotFoundException e) {
-      throw new SecurityException(
-          "Class " + service.value() + " from @Service annotation on " + clazz
-              + " cannot be loaded.");
+      throw new SecurityException("Class " + className
+          + " from @Service annotation on " + clazz + " cannot be loaded.");
     }
   }
 
diff --git a/user/src/com/google/gwt/requestfactory/server/JsonRequestProcessor.java b/user/src/com/google/gwt/requestfactory/server/JsonRequestProcessor.java
index 5257e62..d067044 100644
--- a/user/src/com/google/gwt/requestfactory/server/JsonRequestProcessor.java
+++ b/user/src/com/google/gwt/requestfactory/server/JsonRequestProcessor.java
@@ -18,6 +18,7 @@
 import com.google.gwt.requestfactory.shared.EntityProxy;
 import com.google.gwt.requestfactory.shared.EntityProxyId;
 import com.google.gwt.requestfactory.shared.ProxyFor;
+import com.google.gwt.requestfactory.shared.ProxyForName;
 import com.google.gwt.requestfactory.shared.ServerFailure;
 import com.google.gwt.requestfactory.shared.WriteOperation;
 import com.google.gwt.requestfactory.shared.impl.CollectionProperty;
@@ -59,7 +60,7 @@
 /**
  * An implementation of RequestProcessor for JSON encoded payloads.
  */
-public class JsonRequestProcessor implements RequestProcessor<String> {
+class JsonRequestProcessor implements RequestProcessor<String> {
 
   // TODO should we consume String, InputStream, or JSONObject?
   private class DvsData {
@@ -159,7 +160,12 @@
 
   private static final Logger log = Logger.getLogger(JsonRequestProcessor.class.getName());
 
-  // Decodes a string encoded as web-safe base64
+  /**
+   * Decodes a String encoded as web-safe base64.
+   * 
+   * @param encoded the encoded String
+   * @return a decoded String
+   */
   public static String base64Decode(String encoded) {
     byte[] decodedBytes;
     try {
@@ -171,7 +177,12 @@
     return new String(decodedBytes);
   }
 
-  // Encodes a string with web-safe base64
+  /**
+   * Encodes a String as web-safe base64.
+   * 
+   * @param decoded the decoded String
+   * @return an encoded String
+   */
   public static String base64Encode(String decoded) {
     byte[] decodedBytes = decoded.getBytes();
     return Base64Utils.toBase64(decodedBytes);
@@ -218,7 +229,7 @@
 
   private Map<EntityKey, EntityData> afterDvsDataMap = new HashMap<EntityKey, EntityData>();
 
-  @SuppressWarnings(value = {"unchecked", "rawtypes"})
+  // TODO - document
   public Collection<Property<?>> allProperties(
       Class<? extends EntityProxy> clazz) throws IllegalArgumentException {
     return getPropertiesFromRecordProxyType(clazz).values();
@@ -344,8 +355,9 @@
        * 2. Merge the following and the object resolution code in getEntityKey.
        * 3. Update the involvedKeys set.
        */
-      ProxyFor service = parameterType.getAnnotation(ProxyFor.class);
-      if (service != null) {
+      ProxyFor proxyFor = parameterType.getAnnotation(ProxyFor.class);
+      ProxyForName proxyForName = parameterType.getAnnotation(ProxyForName.class);
+      if (proxyFor != null || proxyForName != null) {
         EntityKey entityKey = getEntityKey(parameterValue.toString());
 
         DvsData dvsData = dvsDataMap.get(entityKey);
@@ -361,8 +373,9 @@
     }
     if (EntityProxyId.class.isAssignableFrom(parameterType)) {
       EntityKey entityKey = getEntityKey(parameterValue.toString());
-      ProxyFor service = entityKey.proxyType.getAnnotation(ProxyFor.class);
-      if (service == null) {
+      ProxyFor proxyFor = entityKey.proxyType.getAnnotation(ProxyFor.class);
+      ProxyForName proxyForName = entityKey.proxyType.getAnnotation(ProxyForName.class);
+      if (proxyFor == null && proxyForName == null) {
         throw new IllegalArgumentException("Unknown service, unable to decode "
             + parameterValue);
       }
@@ -375,6 +388,9 @@
 
   /*
    * Encode a property value to be sent across the wire.
+   * 
+   * @param value a value Object
+   * @return an encoded value Object
    */
   public Object encodePropertyValue(Object value) {
     if (value == null) {
@@ -568,6 +584,16 @@
     if (dtoAnn != null) {
       return (Class<Object>) dtoAnn.value();
     }
+    ProxyForName name = record.getAnnotation(ProxyForName.class);
+    if (name != null) {
+      try {
+        return (Class<Object>) Class.forName(name.value(), false,
+            Thread.currentThread().getContextClassLoader());
+      } catch (ClassNotFoundException e) {
+        throw new IllegalArgumentException("Could not find ProxyForName class",
+            e);
+      }
+    }
     throw new IllegalArgumentException("Proxy class " + record.getName()
         + " missing ProxyFor annotation");
   }
@@ -710,7 +736,6 @@
   /**
    * Returns the property fields (name => type) for a record.
    */
-  @SuppressWarnings("unchecked")
   public Map<String, Property<?>> getPropertiesFromRecordProxyType(
       Class<? extends EntityProxy> record) throws SecurityException {
     if (!EntityProxy.class.isAssignableFrom(record)) {
@@ -722,7 +747,7 @@
     for (Method method : methods) {
       String methodName = method.getName();
       String propertyName = null;
-      Property newProperty = null;
+      Property<?> newProperty = null;
       if (methodName.startsWith("get")) {
         propertyName = Introspector.decapitalize(methodName.substring(3));
         if (propertyName.length() == 0) {
@@ -743,7 +768,7 @@
       if (newProperty == null) {
         continue;
       }
-      Property existing = properties.put(propertyName, newProperty);
+      Property<?> existing = properties.put(propertyName, newProperty);
       if (existing != null && !existing.equals(newProperty)) {
         throw new IllegalStateException(String.format(
             "In %s, mismatched getter and setter types for property %s, "
@@ -754,8 +779,8 @@
     return properties;
   }
 
-  @SuppressWarnings("unchecked")
-  public Property getPropertyFromGenericType(String propertyName, Type type) {
+  @SuppressWarnings({"unchecked", "rawtypes"})
+  public Property<?> getPropertyFromGenericType(String propertyName, Type type) {
     if (type instanceof ParameterizedType) {
       ParameterizedType pType = (ParameterizedType) type;
       Class<?> rawType = (Class<Object>) pType.getRawType();
@@ -765,7 +790,7 @@
           Type leafType = typeArgs[0];
           if (leafType instanceof Class) {
             return new CollectionProperty(propertyName, rawType,
-                (Class) leafType);
+                (Class<?>) leafType);
           }
         }
       }
@@ -830,6 +855,21 @@
 
     operation = getOperation(operationName);
     Class<?> domainClass = Class.forName(operation.getDomainClassName());
+
+    /*
+     * The use of JRP's classloader mirrors the use of Class.forName elsewhere.
+     * It's wrong, but it's consistently wrong.
+     */
+    RequestFactoryInterfaceValidator validator = new RequestFactoryInterfaceValidator(
+        log, new RequestFactoryInterfaceValidator.ClassLoaderLoader(
+            JsonRequestProcessor.class.getClassLoader()));
+    validator.validateRequestContext(operationName.substring(0,
+        operationName.indexOf("::")));
+    if (validator.isPoisoned()) {
+      log.severe("Unable to validate RequestContext");
+      throw new RuntimeException();
+    }
+
     Method domainMethod = domainClass.getMethod(
         operation.getDomainMethod().getName(), operation.getParameterTypes());
     if (Modifier.isStatic(domainMethod.getModifiers()) == operation.isInstance()) {
@@ -1210,9 +1250,8 @@
     Object newId = getRawPropertyValueFromDatastore(entityInstance,
         Constants.ENTITY_ID_PROPERTY);
     if (newId == null) {
-      log.warning("Record with futureId " + originalEntityKey.encodedId
-          + " not persisted");
-      return null; // no changeRecord for this CREATE.
+      // no changeRecord for this CREATE.
+      return null; 
     }
 
     newId = encodeId(newId);
@@ -1562,6 +1601,14 @@
           if (pFor != null) {
             fieldType = pFor.value();
           }
+          ProxyForName pFN = fieldType.getAnnotation(ProxyForName.class);
+          if (pFN != null) {
+            try {
+              fieldType = Class.forName(pFN.value(), false,
+                  Thread.currentThread().getContextClassLoader());
+            } catch (ClassNotFoundException ignored) {
+            }
+          }
           // TODO: remove override declared method return type with field type
           if (!fieldType.equals(field.getType())) {
             fieldType = field.getType();
diff --git a/user/src/com/google/gwt/requestfactory/server/OperationRegistry.java b/user/src/com/google/gwt/requestfactory/server/OperationRegistry.java
index c769ee0..0b82f15 100644
--- a/user/src/com/google/gwt/requestfactory/server/OperationRegistry.java
+++ b/user/src/com/google/gwt/requestfactory/server/OperationRegistry.java
@@ -18,7 +18,7 @@
 /**
  * Maps operation name to {RequestDefinition}.
  */
-public interface OperationRegistry {
+interface OperationRegistry {
 
   RequestDefinition getOperation(String operationName);
 
diff --git a/user/src/com/google/gwt/requestfactory/server/ReflectionBasedOperationRegistry.java b/user/src/com/google/gwt/requestfactory/server/ReflectionBasedOperationRegistry.java
index b2c89bf..0c81170 100644
--- a/user/src/com/google/gwt/requestfactory/server/ReflectionBasedOperationRegistry.java
+++ b/user/src/com/google/gwt/requestfactory/server/ReflectionBasedOperationRegistry.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,8 +18,10 @@
 import com.google.gwt.requestfactory.shared.EntityProxy;
 import com.google.gwt.requestfactory.shared.InstanceRequest;
 import com.google.gwt.requestfactory.shared.ProxyFor;
+import com.google.gwt.requestfactory.shared.ProxyForName;
 import com.google.gwt.requestfactory.shared.Request;
 import com.google.gwt.requestfactory.shared.Service;
+import com.google.gwt.requestfactory.shared.ServiceName;
 
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
@@ -30,13 +32,12 @@
 
 /**
  * OperationRegistry which uses the operation name as a convention for
- * reflection to a method on a class, and returns an appropriate {@link
- * com.google.gwt.requestfactory.server.RequestDefinition}.
+ * reflection to a method on a class, and returns an appropriate
+ * {@link com.google.gwt.requestfactory.server.RequestDefinition}.
  */
-public class ReflectionBasedOperationRegistry implements OperationRegistry {
+class ReflectionBasedOperationRegistry implements OperationRegistry {
 
-  class ReflectiveRequestDefinition
-      implements RequestDefinition {
+  class ReflectiveRequestDefinition implements RequestDefinition {
 
     private Class<?> requestClass;
 
@@ -49,7 +50,8 @@
     private boolean isInstance;
 
     public ReflectiveRequestDefinition(Class<?> requestClass,
-        Method requestMethod, Class<?> domainClass, Method domainMethod, boolean isInstance) {
+        Method requestMethod, Class<?> domainClass, Method domainMethod,
+        boolean isInstance) {
       this.requestClass = requestClass;
       this.requestMethod = requestMethod;
       this.domainClass = domainClass;
@@ -94,20 +96,29 @@
       Class<?> requestReturnType = getReturnTypeFromParameter(requestMethod,
           requestMethod.getGenericReturnType());
       if (EntityProxy.class.isAssignableFrom(requestReturnType)) {
-        ProxyFor annotation =
-            requestReturnType.getAnnotation(ProxyFor.class);
+        ProxyFor annotation = requestReturnType.getAnnotation(ProxyFor.class);
+        ProxyForName nameAnnotation = requestReturnType.getAnnotation(ProxyForName.class);
+
+        Class<?> dtoClass = null;
         if (annotation != null) {
-          Class<?> dtoClass = annotation.value();
-          if (!dtoClass.equals(domainReturnType)) {
+          dtoClass = annotation.value();
+        } else if (nameAnnotation != null) {
+          try {
+            dtoClass = Class.forName(nameAnnotation.value(), false,
+                Thread.currentThread().getContextClassLoader());
+          } catch (ClassNotFoundException e) {
             throw new IllegalArgumentException(
-                "Type mismatch between " + domainMethod + " return type, and "
-                    + requestReturnType + "'s ProxyFor annotation "
-                    + dtoClass);
+                "Unknown type specified in ProxyForName annotation", e);
           }
         } else {
           throw new IllegalArgumentException(
               "Missing ProxyFor annotation on proxy type " + requestReturnType);
         }
+        if (!dtoClass.equals(domainReturnType)) {
+          throw new IllegalArgumentException("Type mismatch between "
+              + domainMethod + " return type, and " + requestReturnType
+              + "'s ProxyFor annotation " + dtoClass);
+        }
         return requestReturnType;
       }
       // primitive ?
@@ -172,7 +183,7 @@
       } else {
         return null;
       }
-      
+
       if (toExamine instanceof ParameterizedType) {
         // if type is for example, RequestObject<List<T>> we return T
         return getTypeArgument((ParameterizedType) toExamine);
@@ -186,13 +197,18 @@
 
   private RequestSecurityProvider securityProvider;
 
+  /**
+   * Constructs a {@link ReflectionBasedOperationRegistry} instance with a given
+   * {@link RequestSecurityProvider}.
+   * 
+   * @param securityProvider a {@link RequestSecurityProvider} instance.
+   */
   public ReflectionBasedOperationRegistry(
       RequestSecurityProvider securityProvider) {
     this.securityProvider = securityProvider;
   }
 
   /**
-
    * Turns an operation in the form of package.requestClass::method into a
    * RequestDefinition via reflection.
    */
@@ -208,25 +224,34 @@
           this.getClass().getClassLoader());
       securityProvider.checkClass(requestClass);
       Service domainClassAnnotation = requestClass.getAnnotation(Service.class);
+      ServiceName domainClassNameAnnotation = requestClass.getAnnotation(ServiceName.class);
+      Class<?> domainClass;
       if (domainClassAnnotation != null) {
-        Class<?> domainClass = domainClassAnnotation.value();
-        Method requestMethod = findMethod(requestClass, domainMethodName);
-        Method domainMethod = findMethod(domainClass, domainMethodName);
-        if (requestMethod != null && domainMethod != null) {
-          boolean isInstance = InstanceRequest.class.isAssignableFrom(requestMethod.getReturnType());
-          if (isInstance == Modifier.isStatic(domainMethod.getModifiers())) {
-            throw new IllegalArgumentException("domain method " + domainMethod
-                + " and interface method " + requestMethod
-                + " don't match wrt instance/static");
-          }
-          return new ReflectiveRequestDefinition(requestClass, requestMethod,
-              domainClass, domainMethod, isInstance);
-        }
+        domainClass = domainClassAnnotation.value();
+      } else if (domainClassNameAnnotation != null) {
+        domainClass = Class.forName(domainClassNameAnnotation.value(), false,
+            Thread.currentThread().getContextClassLoader());
+      } else {
+        return null;
       }
+
+      Method requestMethod = findMethod(requestClass, domainMethodName);
+      Method domainMethod = findMethod(domainClass, domainMethodName);
+      if (requestMethod != null && domainMethod != null) {
+        boolean isInstance = InstanceRequest.class.isAssignableFrom(requestMethod.getReturnType());
+        if (isInstance == Modifier.isStatic(domainMethod.getModifiers())) {
+          throw new IllegalArgumentException("domain method " + domainMethod
+              + " and interface method " + requestMethod
+              + " don't match wrt instance/static");
+        }
+        return new ReflectiveRequestDefinition(requestClass, requestMethod,
+            domainClass, domainMethod, isInstance);
+      }
+
       return null;
     } catch (ClassNotFoundException e) {
-      throw new SecurityException(
-          "Access to non-existent class " + reqClassName);
+      throw new SecurityException("Access to non-existent class "
+          + reqClassName);
     }
   }
 
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestDefinition.java b/user/src/com/google/gwt/requestfactory/server/RequestDefinition.java
index 1e0db57..48ed9be 100644
--- a/user/src/com/google/gwt/requestfactory/server/RequestDefinition.java
+++ b/user/src/com/google/gwt/requestfactory/server/RequestDefinition.java
@@ -22,7 +22,7 @@
  * Implemented by enums that define the mapping between request objects and
  * service methods.
  */
-public interface RequestDefinition {
+interface RequestDefinition {
   /**
    * Returns the name of the (domain) class that contains the method to be
    * invoked on the server.
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java b/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java
new file mode 100644
index 0000000..4903bea
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java
@@ -0,0 +1,1070 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS 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.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.gwt.requestfactory.client.impl.FindRequest;
+import com.google.gwt.requestfactory.shared.EntityProxy;
+import com.google.gwt.requestfactory.shared.InstanceRequest;
+import com.google.gwt.requestfactory.shared.ProxyFor;
+import com.google.gwt.requestfactory.shared.ProxyForName;
+import com.google.gwt.requestfactory.shared.Request;
+import com.google.gwt.requestfactory.shared.RequestContext;
+import com.google.gwt.requestfactory.shared.RequestFactory;
+import com.google.gwt.requestfactory.shared.Service;
+import com.google.gwt.requestfactory.shared.ServiceName;
+
+import java.io.IOException;
+import java.io.InputStream;
+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.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.
+ */
+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.
+     */
+    boolean exists(String resource);
+
+    /**
+     * Returns an InputStream to access the specified resource, or
+     * <code>null</code> if no such resource exists.
+     */
+    InputStream getResourceAsStream(String resource);
+  }
+
+  /**
+   * 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;
+
+    public DomainMapper(ErrorContext logger) {
+      this.logger = logger;
+      logger.spam("Finding domain mapping annotation");
+    }
+
+    public String getDomainInternalName() {
+      return domainInternalName;
+    }
+
+    @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");
+      }
+    }
+
+    @Override
+    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+      boolean foundProxy = desc.equals(Type.getDescriptor(ProxyFor.class));
+      boolean foundProxyName = desc.equals(Type.getDescriptor(ProxyForName.class));
+      boolean foundService = desc.equals(Type.getDescriptor(Service.class));
+      boolean foundServiceName = desc.equals(Type.getDescriptor(ServiceName.class));
+
+      if (foundProxy || foundService) {
+        return new EmptyVisitor() {
+
+          @Override
+          public void visit(String name, Object value) {
+            domainInternalName = ((Type) value).getInternalName();
+          }
+
+        };
+      }
+
+      if (foundProxyName || foundServiceName) {
+        return new EmptyVisitor() {
+          @Override
+          public void visit(String name, Object value) {
+            String sourceName = (String) value;
+
+            /*
+             * 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) {
+                return;
+              }
+              desc.setCharAt(idx, '$');
+            }
+
+            domainInternalName = desc.toString();
+            logger.spam(domainInternalName);
+          }
+        };
+      }
+      return null;
+    }
+  }
+
+  /**
+   * Improves error messages by providing context for the user.
+   */
+  private class ErrorContext {
+    private final Logger logger;
+    private final ErrorContext parent;
+    private Type currentType;
+    private Method currentMethod;
+
+    public ErrorContext(Logger logger) {
+      this.logger = logger;
+      this.parent = null;
+    }
+
+    private ErrorContext(ErrorContext parent) {
+      this.logger = parent.logger;
+      this.parent = parent;
+    }
+
+    public void poison(String msg, Object... args) {
+      logger.logp(Level.SEVERE, currentType(), currentMethod(),
+          String.format(msg, args));
+      poisoned = true;
+    }
+
+    public void poison(String msg, Throwable t) {
+      logger.logp(Level.SEVERE, currentType(), currentMethod(), msg, t);
+      poisoned = true;
+    }
+
+    public ErrorContext setMethod(Method method) {
+      ErrorContext toReturn = new ErrorContext(this);
+      toReturn.currentMethod = method;
+      return toReturn;
+    }
+
+    public ErrorContext setType(Type type) {
+      ErrorContext toReturn = new ErrorContext(this);
+      toReturn.currentType = type;
+      return toReturn;
+    }
+
+    public void spam(String msg, Object... args) {
+      logger.logp(Level.FINEST, currentType(), currentMethod(),
+          String.format(msg, args));
+    }
+
+    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;
+    }
+  }
+
+  /**
+   * 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);
+      return methods;
+    }
+
+    @Override
+    public void visit(int version, int access, String name, String signature,
+        String superName, String[] interfaces) {
+      if (!seen.add(name)) {
+        return;
+      }
+      if (!"java/lang/Object".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) {
+      RFMethod method = new RFMethod(name, desc);
+      method.setDeclaredStatic((access & Opcodes.ACC_STATIC) != 0);
+      method.setDeclaredSignature(signature);
+      methods.add(method);
+      return null;
+    }
+  }
+
+  private static class RFMethod extends Method {
+    private boolean isDeclaredStatic;
+    private String signature;
+
+    public RFMethod(String name, String desc) {
+      super(name, desc);
+    }
+
+    public String getSignature() {
+      return signature;
+    }
+
+    public boolean isDeclaredStatic() {
+      return isDeclaredStatic;
+    }
+
+    public void setDeclaredSignature(String signature) {
+      this.signature = signature;
+    }
+
+    public void setDeclaredStatic(boolean value) {
+      isDeclaredStatic = value;
+    }
+
+    @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> supertypes = new ArrayList<Type>();
+
+    public SupertypeCollector(ErrorContext logger) {
+      this.logger = logger;
+    }
+
+    public List<Type> exec(Type type) {
+      RequestFactoryInterfaceValidator.this.visit(logger,
+          type.getInternalName(), this);
+      return supertypes;
+    }
+
+    @Override
+    public void visit(int version, int access, String name, String signature,
+        String superName, String[] interfaces) {
+      if (!seen.add(name)) {
+        return;
+      }
+      supertypes.add(Type.getObjectType(name));
+      if (!"java/lang/Object".equals(name)) {
+        RequestFactoryInterfaceValidator.this.visit(logger, superName, this);
+      }
+      if (interfaces != null) {
+        for (String intf : interfaces) {
+          RequestFactoryInterfaceValidator.this.visit(logger, intf, this);
+        }
+      }
+    }
+  }
+
+  @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)));
+
+  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);
+  }
+
+  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());
+  }
+
+  /**
+   * Maps client types (e.g. FooProxy) to server domain types (e.g. Foo).
+   */
+  private final Map<Type, Type> clientToDomainType = new HashMap<Type, Type>();
+  /**
+   * The type {@link EntityProxy}.
+   */
+  private final Type entityProxyIntf = Type.getType(EntityProxy.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>>();
+  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>>();
+  /**
+   * 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>();
+
+  public RequestFactoryInterfaceValidator(Logger logger, Loader loader) {
+    this.parentLogger = new ErrorContext(logger);
+    this.loader = loader;
+    for (Class<?> clazz : VALUE_TYPES) {
+      valueTypes.add(Type.getType(clazz));
+    }
+  }
+
+  /**
+   * 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</li>
+   * <li>All referenced EntityProxy types are valid</li>
+   * </ul>
+   * 
+   * @param binaryName the binary name (e.g. {@link Class#getName()}) of the
+   *          EntityProxy subtype
+   */
+  public void validateEntityProxy(String binaryName) {
+    if (!Name.isBinaryName(binaryName)) {
+      parentLogger.poison("%s is not a binary name", binaryName);
+      return;
+    }
+
+    // Don't revalidate the same type
+    if (!validatedTypes.add(binaryName)) {
+      return;
+    }
+
+    Type proxyType = Type.getObjectType(BinaryName.toInternalName(binaryName));
+    ErrorContext logger = parentLogger.setType(proxyType);
+
+    // Quick sanity check for calling code
+    if (!isAssignable(logger, entityProxyIntf, proxyType)) {
+      parentLogger.poison("%s is not a %s", print(proxyType),
+          EntityProxy.class.getSimpleName());
+      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
+    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);
+    }
+  }
+
+  /**
+   * 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</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 (!Name.isBinaryName(binaryName)) {
+      parentLogger.poison("%s is not a binary name", binaryName);
+      return;
+    }
+
+    // Don't revalidate the same type
+    if (!validatedTypes.add(binaryName)) {
+      return;
+    }
+
+    if (FindRequest.class.getName().equals(binaryName)) {
+      // Ignore FindRequest, it's a huge hack
+      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) != null) {
+        continue;
+      }
+
+      // Check the client method against the domain
+      checkClientMethodInDomain(logger, method, domainServiceType);
+      maybeCheckReferredProxies(logger, method);
+    }
+  }
+
+  /**
+   * 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 (!Name.isBinaryName(binaryName)) {
+      parentLogger.poison("%s is not a binary name", binaryName);
+      return;
+    }
+
+    // Don't revalidate the same type
+    if (!validatedTypes.add(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());
+      }
+    }
+  }
+
+  /**
+   * 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) {
+    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 = findCompatibleMethod(logger, domainServiceType, searchFor);
+
+    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 (!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) {
+    logger = logger.setType(domainType);
+    Method getIdString = new Method("getId", "()Ljava/lang/String;");
+    Method getIdLong = new Method("getId", "()Ljava/lang/Long;");
+    if (findCompatibleMethod(logger, domainType, getIdString, false, true) == null
+        && findCompatibleMethod(logger, domainType, getIdLong, false, true) == null) {
+      logger.poison("Did not find a getId() method that"
+          + " returns a String or a Long");
+    }
+
+    Method getVersion = new Method("getVersion", "()Ljava/lang/Integer;");
+    if (findCompatibleMethod(logger, domainType, getVersion) == null) {
+      logger.poison("Did not find a getVersion() method"
+          + " that returns an Integer");
+    }
+  }
+
+  /**
+   * Ensure that the given property method on an EntityProxy exists on the
+   * domain object.
+   */
+  private void checkPropertyMethod(ErrorContext logger,
+      Method clientPropertyMethod, Type domainType) {
+    logger = logger.setMethod(clientPropertyMethod);
+
+    findCompatibleMethod(logger, domainType,
+        createDomainMethod(logger, clientPropertyMethod));
+  }
+
+  /**
+   * 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);
+  }
+
+  /**
+   * 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) {
+    return findCompatibleMethod(logger, domainType, searchFor, true, 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) {
+    String methodName = searchFor.getName();
+    Type[] clientArgs = searchFor.getArgumentTypes();
+    Type clientReturnType = searchFor.getReturnType();
+    // 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) {
+      // Box any primitive types to simplify compotibility check
+      Type[] domainArgs = domainMethod.getArgumentTypes();
+      for (int i = 0, j = domainArgs.length; i < j; i++) {
+        domainArgs[i] = maybeBoxType(domainArgs[i]);
+      }
+      Type domainReturnType = maybeBoxType(domainMethod.getReturnType());
+
+      /*
+       * 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) {
+      StringBuilder sb = new StringBuilder();
+      sb.append(String.format(
+          "Could not find matching method in %s:\nPossible matches:\n",
+          print(domainType)));
+      for (RFMethod domainMethod : methods) {
+        sb.append("  ").append(print(domainMethod)).append("\n");
+      }
+      logger.poison(sb.toString());
+    }
+    return null;
+  }
+
+  /**
+   * 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 toReturn = clientToDomainType.get(clientType);
+    if (toReturn != null) {
+      return toReturn;
+    }
+    if (isValueType(logger, clientType) || isCollectionType(logger, clientType)) {
+      toReturn = clientType;
+    } 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());
+        toReturn = errorType;
+      } else {
+        toReturn = Type.getObjectType(pv.getDomainInternalName());
+      }
+    }
+    clientToDomainType.put(clientType, toReturn);
+    return toReturn;
+  }
+
+  /**
+   * 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 = {"java/lang/Object"};
+    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;
+    }
+
+    // Box primitive types to make this simple
+    if (possibleSupertype.getSort() != Type.OBJECT) {
+      possibleSupertype = getBoxedType(possibleSupertype);
+    }
+    if (possibleSubtype.getSort() != Type.OBJECT) {
+      possibleSubtype = getBoxedType(possibleSubtype);
+    }
+
+    // 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(ErrorContext logger, Type type) {
+    return "java/util/List".equals(type.getInternalName())
+        || "java/util/Set".equals(type.getInternalName());
+  }
+
+  private boolean isValueType(ErrorContext logger, Type type) {
+    if (type.getSort() != Type.OBJECT) {
+      return true;
+    }
+    if (valueTypes.contains(type)) {
+      return true;
+    }
+    logger = logger.setType(type);
+    List<Type> types = getSupertypes(logger, type);
+    for (Type t : types) {
+      if (valueTypes.contains(t)) {
+        valueTypes.add(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)} if
+   * the type is an EntityProxy.
+   */
+  private void maybeCheckEntityProxyType(ErrorContext logger, Type... types) {
+    for (Type type : types) {
+      if (isAssignable(logger, entityProxyIntf, type)) {
+        validateEntityProxy(type.getClassName());
+      }
+    }
+  }
+
+  /**
+   * Examine the arguments ond return value of a method and check any
+   * EntityProxies referred.
+   */
+  private void maybeCheckReferredProxies(ErrorContext logger, RFMethod method) {
+    Type[] argTypes = method.getArgumentTypes();
+    Type returnType = getReturnType(logger, method);
+
+    // Check EntityProxy args ond return types against the domain
+    maybeCheckEntityProxyType(logger, argTypes);
+    maybeCheckEntityProxyType(logger, returnType);
+  }
+
+  /**
+   * 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/gwt/requestfactory/server/RequestProcessingException.java b/user/src/com/google/gwt/requestfactory/server/RequestProcessingException.java
index 91485a2..16e81f3 100644
--- a/user/src/com/google/gwt/requestfactory/server/RequestProcessingException.java
+++ b/user/src/com/google/gwt/requestfactory/server/RequestProcessingException.java
@@ -20,7 +20,7 @@
  * an unexpected exception is caught. Includes an appropriate
  * response of T to send to the client.
  */
-public class RequestProcessingException extends Exception {
+class RequestProcessingException extends Exception {
   private final Object response;
 
   /**
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestProcessor.java b/user/src/com/google/gwt/requestfactory/server/RequestProcessor.java
index 93dd64e..ed72beb 100644
--- a/user/src/com/google/gwt/requestfactory/server/RequestProcessor.java
+++ b/user/src/com/google/gwt/requestfactory/server/RequestProcessor.java
@@ -21,7 +21,7 @@
  * requests, and a serialized return value of the same type is returned.
  * @param <T> the type of encoding used to serialize the request (e.g. String)
  */
-public interface RequestProcessor<T> {
+interface RequestProcessor<T> {
   /**
    * Decodes request, invokes methods, and re-encoded resulting return values.
    */
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestProperty.java b/user/src/com/google/gwt/requestfactory/server/RequestProperty.java
index b1aeb0c..31a948c 100644
--- a/user/src/com/google/gwt/requestfactory/server/RequestProperty.java
+++ b/user/src/com/google/gwt/requestfactory/server/RequestProperty.java
@@ -23,7 +23,7 @@
 /**
  * Represents one piece in a property reference sequence.
  */
-public class RequestProperty implements Iterable<RequestProperty> {
+class RequestProperty implements Iterable<RequestProperty> {
 
   /**
    * Merge two property chains.
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestSecurityProvider.java b/user/src/com/google/gwt/requestfactory/server/RequestSecurityProvider.java
index 9a404af..439f997 100644
--- a/user/src/com/google/gwt/requestfactory/server/RequestSecurityProvider.java
+++ b/user/src/com/google/gwt/requestfactory/server/RequestSecurityProvider.java
@@ -19,7 +19,7 @@
  * Enforces security policy for operations and classes, as well as permitting
  * request obfuscation.
  */
-public interface RequestSecurityProvider {
+interface RequestSecurityProvider {
 
   /**
    * Throws exception if argument is not accessible via remote requests.
diff --git a/user/src/com/google/gwt/requestfactory/server/SampleDataPopulator.java b/user/src/com/google/gwt/requestfactory/server/SampleDataPopulator.java
deleted file mode 100644
index 2ffd72e..0000000
--- a/user/src/com/google/gwt/requestfactory/server/SampleDataPopulator.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.requestfactory.server;
-
-import com.google.gwt.requestfactory.client.DefaultRequestTransport;
-import com.google.gwt.requestfactory.shared.impl.Constants;
-
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpResponse;
-import org.apache.http.HttpStatus;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.entity.StringEntity;
-import org.apache.http.impl.client.DefaultHttpClient;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-
-/**
- * Class to populate the datastore with sample data in a JSON file.
- */
-public class SampleDataPopulator {
-
-  public static void main(String args[]) {
-    // TODO: cleanup argument processing and error reporting.
-    if (args.length < 2) {
-      printHelp();
-      System.exit(-1);
-    }
-    try {
-      if (!args[0].endsWith(DefaultRequestTransport.URL)) {
-        System.err.println("Please check your URL string " + args[0]
-            + ", it should end with " + DefaultRequestTransport.URL + ", exiting");
-        System.exit(-1);
-      }
-      SampleDataPopulator populator = new SampleDataPopulator(args[0], args[1]);
-      populator.populate();
-    } catch (Exception ex) {
-      ex.printStackTrace();
-      printHelp();
-    }
-  }
-
-  private static void printHelp() {
-    StringBuffer sb = new StringBuffer();
-    sb.append("\n");
-    sb.append("Requires two arguments: the URL to post the JSON data and the path to the JSON data file.");
-    System.err.println(sb.toString());
-  }
-
-  private final String url;
-
-  private final String filePathName;
-
-  SampleDataPopulator(String url, String filePathName) {
-    this.url = url;
-    this.filePathName = filePathName;
-  }
-
-  public void populate() throws JSONException, IOException {
-    JSONObject jsonObject = readAsJsonObject(readFileAsString(filePathName));
-    postJsonFile(jsonObject);
-  }
-
-  private void postJsonFile(JSONObject contentData) throws IOException,
-      JSONException {
-    HttpPost post = new HttpPost(url);
-    JSONObject request = new JSONObject();
-    request.put(Constants.OPERATION_TOKEN, "DOESNT_WORK");
-    request.put(Constants.CONTENT_TOKEN, contentData);
-    StringEntity reqEntity = new StringEntity(request.toString());
-    post.setEntity(reqEntity);
-    HttpClient client = new DefaultHttpClient();
-    HttpResponse response = client.execute(post);
-    HttpEntity resEntity = response.getEntity();
-    if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
-      System.out.println("SUCCESS: Put " + resEntity.getContentLength()
-          + " records in the datastore!");
-      return;
-    }
-    System.err.println("POST failed: Status line " + response.getStatusLine()
-        + ", please check your URL");
-  }
-
-  private JSONObject readAsJsonObject(String string) throws JSONException {
-    JSONObject jsonObject = new JSONObject(string);
-    return jsonObject;
-  }
-
-  // ugly method, refactor later when cleaning up this class.
-  private byte[] readFileAsBytes(String filePathName) {
-    File file = new File(filePathName);
-    FileInputStream fileInputStream = null;
-    byte bytes[] = null;
-    try {
-      fileInputStream = new FileInputStream(file);
-      int byteLength = (int) file.length();
-      bytes = new byte[byteLength];
-      int byteOffset = 0;
-      while (byteOffset < byteLength) {
-        int bytesReadCount = fileInputStream.read(bytes, byteOffset, byteLength
-            - byteOffset);
-        if (bytesReadCount == -1) {
-          return null;
-        }
-        byteOffset += bytesReadCount;
-      }
-    } catch (IOException e) {
-      // Ignored.
-    } finally {
-      try {
-        if (fileInputStream != null) {
-          fileInputStream.close();
-        }
-      } catch (IOException e) {
-        // ignored
-      }
-    }
-    return bytes;
-  }
-  
-  private String readFileAsString(String filePathName) {
-    byte bytes[] = readFileAsBytes(filePathName);
-    if (bytes != null) {
-      try {
-        return new String(bytes, "UTF-8");
-      } catch (UnsupportedEncodingException e) {
-        // Ignored.
-      }
-      return null;
-    }
-    return null;
-  }
-
-}
diff --git a/user/src/com/google/gwt/requestfactory/server/SignatureAdapter.java b/user/src/com/google/gwt/requestfactory/server/SignatureAdapter.java
new file mode 100644
index 0000000..ebd6299
--- /dev/null
+++ b/user/src/com/google/gwt/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.gwt.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/gwt/requestfactory/shared/ProxyFor.java b/user/src/com/google/gwt/requestfactory/shared/ProxyFor.java
index 7a8c747..4da1afe 100644
--- a/user/src/com/google/gwt/requestfactory/shared/ProxyFor.java
+++ b/user/src/com/google/gwt/requestfactory/shared/ProxyFor.java
@@ -21,12 +21,13 @@
 import java.lang.annotation.Target;
 
 /**
- * Annotation on Record classes specifying 'type'. 'type'
- * represents the server-side counterpart of the Record.
+ * Annotation on EntityProxy classes specifying the domain (server-side) object
+ * type.
+ * 
+ * @see ProxyForName
  */
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.TYPE)
 public @interface ProxyFor {
-
   Class<?> value();
 }
diff --git a/user/src/com/google/gwt/requestfactory/shared/ProxyForName.java b/user/src/com/google/gwt/requestfactory/shared/ProxyForName.java
new file mode 100644
index 0000000..fa3f5bd
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/ProxyForName.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.gwt.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 {
+  String value();
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/Service.java b/user/src/com/google/gwt/requestfactory/shared/Service.java
index 59f2cf5..29d8829 100644
--- a/user/src/com/google/gwt/requestfactory/shared/Service.java
+++ b/user/src/com/google/gwt/requestfactory/shared/Service.java
@@ -23,10 +23,11 @@
 /**
  * Annotation on Request classes specifying the server side implementations that
  * back them.
+ * 
+ * @see ServiceName
  */
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.TYPE)
 public @interface Service {
-
   Class<?> value();
 }
diff --git a/user/src/com/google/gwt/requestfactory/shared/ServiceName.java b/user/src/com/google/gwt/requestfactory/shared/ServiceName.java
new file mode 100644
index 0000000..beb72b6
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/ServiceName.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.gwt.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 {
+  String value();
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/CollectionProperty.java b/user/src/com/google/gwt/requestfactory/shared/impl/CollectionProperty.java
index 69ca68b..7c2ff23 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/CollectionProperty.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/CollectionProperty.java
@@ -18,11 +18,6 @@
 import java.util.Collection;
 
 /**
- * <p> <span style="color:red">Experimental API: This class is still under rapid
- * development, and is very likely to be deleted. Use it at your own risk.
- * </span> </p> Defines a property of a {@link com.google.gwt.requestfactory.shared.EntityProxy} that contains a
- * one-to-many set of related values.
- *
  * @param <C> the type of the Container, must be List or Set
  * @param <E> the type of the element the container contains
  */
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/EnumProperty.java b/user/src/com/google/gwt/requestfactory/shared/impl/EnumProperty.java
index 7a0e743..842d745 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/EnumProperty.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/EnumProperty.java
@@ -16,11 +16,6 @@
 package com.google.gwt.requestfactory.shared.impl;
 
 /**
- * <p>
- * <span style="color:red">Experimental API: This class is still under rapid
- * development, and is very likely to be deleted. Use it at your own risk.
- * </span>
- * </p>
  * Defines a property of a {@link com.google.gwt.requestfactory.shared.EntityProxy}.
  *
  * @param <V> the type of the property's value
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/Property.java b/user/src/com/google/gwt/requestfactory/shared/impl/Property.java
index 06bd8ba..15e8b84 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/Property.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/Property.java
@@ -16,11 +16,6 @@
 package com.google.gwt.requestfactory.shared.impl;
 
 /**
- * <p>
- * <span style="color:red">Experimental API: This class is still under rapid
- * development, and is very likely to be deleted. Use it at your own risk.
- * </span>
- * </p>
  * Defines a property of an {@link EntityProxy}.
  *
  * @param <V> the type of the property's value
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 6431b51..7234528 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/TypeLibrary.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/TypeLibrary.java
@@ -25,9 +25,6 @@
 import java.util.Set;
 
 /**
- * <p> <span style="color:red">Experimental API: This class is still under rapid
- * development, and is very likely to be deleted. Use it at your own risk.
- * </span> </p>
  * Utility methods for querying, encoding, and decoding typed
  * payload data.
  */
diff --git a/user/src/com/google/gwt/user/tools/WebAppCreator.java b/user/src/com/google/gwt/user/tools/WebAppCreator.java
index 8c5f373..c73c69c 100644
--- a/user/src/com/google/gwt/user/tools/WebAppCreator.java
+++ b/user/src/com/google/gwt/user/tools/WebAppCreator.java
@@ -371,6 +371,15 @@
     replacements.put("@testFolder", testFolder);
     replacements.put("@warFolder", warFolder);
 
+    // Add command to copy gwt-servlet-deps.jar into libs, unless this is a
+    // maven project. Maven projects should include libs as maven dependencies.
+    String copyServletDeps = "";
+    if (!maven) {
+      copyServletDeps = "<copy todir=\"" + warFolder + "/WEB-INF/lib\" "
+          + "file=\"${gwt.sdk}/gwt-servlet-deps.jar\" />";
+    }
+    replacements.put("@copyServletDeps", copyServletDeps);
+
     // Collect the list of server libs to include on the eclipse classpath.
     StringBuilder serverLibs = new StringBuilder();
     if (libDir.exists()) {
diff --git a/user/src/com/google/gwt/user/tools/project.ant.xmlsrc b/user/src/com/google/gwt/user/tools/project.ant.xmlsrc
index a1dd654..dacddf7 100644
--- a/user/src/com/google/gwt/user/tools/project.ant.xmlsrc
+++ b/user/src/com/google/gwt/user/tools/project.ant.xmlsrc
@@ -17,6 +17,7 @@
   <target name="libs" description="Copy libs to WEB-INF/lib">
     <mkdir dir="@warFolder/WEB-INF/lib" />
     <copy todir="@warFolder/WEB-INF/lib" file="${gwt.sdk}/gwt-servlet.jar" />
+    @copyServletDeps
     <!-- Add any additional server libs that need to be copied -->
   </target>
 
diff --git a/user/test/com/google/gwt/requestfactory/RequestFactoryJreSuite.java b/user/test/com/google/gwt/requestfactory/RequestFactoryJreSuite.java
index a6ad74a..3aa7f1b 100644
--- a/user/test/com/google/gwt/requestfactory/RequestFactoryJreSuite.java
+++ b/user/test/com/google/gwt/requestfactory/RequestFactoryJreSuite.java
@@ -19,6 +19,7 @@
 import com.google.gwt.requestfactory.rebind.model.RequestFactoryModelTest;
 import com.google.gwt.requestfactory.server.JsonRequestProcessorTest;
 import com.google.gwt.requestfactory.server.ReflectionBasedOperationRegistryTest;
+import com.google.gwt.requestfactory.server.RequestFactoryInterfaceValidatorTest;
 import com.google.gwt.requestfactory.server.RequestPropertyTest;
 
 import junit.framework.Test;
@@ -34,6 +35,7 @@
     suite.addTestSuite(SimpleEntityProxyIdTest.class);
     suite.addTestSuite(JsonRequestProcessorTest.class);
     suite.addTestSuite(ReflectionBasedOperationRegistryTest.class);
+    suite.addTestSuite(RequestFactoryInterfaceValidatorTest.class);
     suite.addTestSuite(RequestFactoryModelTest.class);
     suite.addTestSuite(RequestPropertyTest.class);
     return suite;
diff --git a/user/test/com/google/gwt/requestfactory/rebind/model/RequestFactoryModelTest.java b/user/test/com/google/gwt/requestfactory/rebind/model/RequestFactoryModelTest.java
index 42a821b..74d268a 100644
--- a/user/test/com/google/gwt/requestfactory/rebind/model/RequestFactoryModelTest.java
+++ b/user/test/com/google/gwt/requestfactory/rebind/model/RequestFactoryModelTest.java
@@ -26,8 +26,6 @@
 import com.google.gwt.dev.util.Util;
 import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
 import com.google.gwt.requestfactory.server.TestContextImpl;
-import com.google.gwt.requestfactory.server.TestContextNoIdImpl;
-import com.google.gwt.requestfactory.server.TestContextNoVersionImpl;
 import com.google.gwt.requestfactory.shared.EntityProxy;
 import com.google.gwt.requestfactory.shared.InstanceRequest;
 import com.google.gwt.requestfactory.shared.ProxyFor;
@@ -49,8 +47,9 @@
 import java.util.SortedSet;
 
 /**
- * Test case for {@link com.google.gwt.requestfactory.rebind.model.RequestFactoryModel}
- * that uses mock CompilationStates.
+ * Test case for
+ * {@link com.google.gwt.requestfactory.rebind.model.RequestFactoryModel} that
+ * uses mock CompilationStates.
  */
 public class RequestFactoryModelTest extends TestCase {
 
@@ -64,8 +63,7 @@
     public EmptyMockJavaResource(Class<?> clazz) {
       super(clazz.getName());
 
-      code.append("package ").append(clazz.getPackage().getName())
-          .append(";\n");
+      code.append("package ").append(clazz.getPackage().getName()).append(";\n");
       code.append("public interface ").append(clazz.getSimpleName());
 
       int numParams = clazz.getTypeParameters().length;
@@ -103,15 +101,15 @@
     @Override
     protected CharSequence getContent() {
       String resourceName = getTypeName().replace('.', '/') + ".java";
-      InputStream stream = Thread.currentThread().getContextClassLoader()
-          .getResourceAsStream(resourceName);
+      InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(
+          resourceName);
       return Util.readStreamAsString(stream);
     }
   }
 
   private static TreeLogger createCompileLogger() {
-    PrintWriterTreeLogger logger = new PrintWriterTreeLogger(
-        new PrintWriter(System.err, true));
+    PrintWriterTreeLogger logger = new PrintWriterTreeLogger(new PrintWriter(
+        System.err, true));
     logger.setMaxDetail(TreeLogger.ERROR);
     return logger;
   }
@@ -125,7 +123,8 @@
   }
 
   public void testBadCollectionType() {
-    testModelWithMethodDecl("Request<SortedSet<Integer>> badReturnType();",
+    testModelWithMethodDecl(
+        "Request<SortedSet<Integer>> badReturnType();",
         "Requests that return collections may be declared with java.util.List or java.util.Set only");
   }
 
@@ -139,55 +138,10 @@
         "Invalid Request parameterization java.lang.Iterable");
   }
 
-  public void testMismatchedArityInstance() {
-    testModelWithMethodDecl(
-        "InstanceRequest<TestProxy, String> mismatchedArityInstance(TestProxy p, int x);",
-        "Parameter 0 of method TestContext.mismatchedArityInstance does not match method com.google.gwt.requestfactory.server.TestContextImpl.mismatchedArityInstance");
-  }
-
-  public void testMismatchedArityStatic() {
-    testModelWithMethodDecl("Request<String> mismatchedArityStatic(int x);",
-        "Method TestContext.mismatchedArityStatic parameters do not match same method on com.google.gwt.requestfactory.server.TestContextImpl");
-  }
-
-  public void testMismatchedModifierNonStatic() {
-    testModelWithMethodDecl(
-        "InstanceRequest<TestProxy, String> mismatchedNonStatic();",
-        "Method TestContext.mismatchedNonStatic is an instance method, while the corresponding method on com.google.gwt.requestfactory.server.TestContextImpl is static");
-  }
-
-  public void testMismatchedModifierStatic() {
-    testModelWithMethodDecl("Request<String> mismatchedStatic();",
-        "Method TestContext.mismatchedStatic is a static method, while the corresponding method on com.google.gwt.requestfactory.server.TestContextImpl is not");
-  }
-
-  public void testMismatchedParamType() {
-    testModelWithMethodDecl("Request<String> mismatchedParamType(Integer x);",
-        "Parameter 0 of method TestContext.mismatchedParamType does not match method com.google.gwt.requestfactory.server.TestContextImpl.mismatchedParamType");
-  }
-
-  public void testMismatchedReturnType() {
-    testModelWithMethodDecl("Request<String> mismatchedReturnType();",
-        "Return type of method TestContext.mismatchedReturnType does not match method com.google.gwt.requestfactory.server.TestContextImpl.mismatchedReturnType");
-  }
-
-  public void testMissingId() {
-    testModelWithMethodDeclArgs("Request<TestProxy> okMethodProxy();",
-        TestContextNoIdImpl.class.getName(),
-        TestContextNoIdImpl.class.getName(),
-        "The class com.google.gwt.requestfactory.server.TestContextNoIdImpl is missing method getId()");
-  }
-
-  public void testMissingMethod() {
-    testModelWithMethodDecl("Request<String> missingMethod();",
-        "Method t.TestContext.missingMethod has no corresponding public method on"
-            + " com.google.gwt.requestfactory.server.TestContextImpl");
-  }
-
   public void testMissingProxyFor() {
     testModelWithMethodDeclArgs("Request<TestProxy> okMethodProxy();",
         TestContextImpl.class.getName(), null,
-        "The t.TestProxy type does not have a @ProxyFor annotation");
+        "The t.TestProxy type does not have a @ProxyFor or @ProxyForName annotation");
   }
 
   public void testMissingService() {
@@ -196,13 +150,6 @@
         "RequestContext subtype t.TestContext is missing a @Service annotation");
   }
 
-  public void testMissingVersion() {
-    testModelWithMethodDeclArgs("Request<TestProxy> okMethodProxy();",
-        TestContextNoVersionImpl.class.getName(),
-        TestContextNoVersionImpl.class.getName(),
-        "The class com.google.gwt.requestfactory.server.TestContextNoVersionImpl is missing method getVersion()");
-  }
-
   public void testModelWithMethodDecl(final String clientMethodDecls,
       String... expected) {
     testModelWithMethodDeclArgs(clientMethodDecls,
@@ -250,8 +197,8 @@
       }
     });
 
-    CompilationState state = CompilationStateBuilder
-        .buildFrom(logger, javaResources);
+    CompilationState state = CompilationStateBuilder.buildFrom(logger,
+        javaResources);
 
     UnitTestTreeLogger.Builder builder = new UnitTestTreeLogger.Builder();
     builder.setLowestLogLevel(TreeLogger.ERROR);
@@ -261,19 +208,14 @@
     builder.expectError(RequestFactoryModel.poisonedMessage(), null);
     UnitTestTreeLogger testLogger = builder.createLogger();
     try {
-      new RequestFactoryModel(testLogger,
-          state.getTypeOracle().findType("t.TestRequestFactory"));
+      new RequestFactoryModel(testLogger, state.getTypeOracle().findType(
+          "t.TestRequestFactory"));
       fail("Should have complained");
     } catch (UnableToCompleteException e) {
     }
     testLogger.assertCorrectLogEntries();
   }
 
-  public void testOverloadedMethod() {
-    testModelWithMethodDecl("Request<String> overloadedMethod();",
-        "Method t.TestContext.overloadedMethod is overloaded on com.google.gwt.requestfactory.server.TestContextImpl");
-  }
-
   private Set<Resource> getJavaResources(final String proxyClass) {
     MockJavaResource[] javaFiles = {new MockJavaResource("t.AddressProxy") {
       @Override
@@ -290,7 +232,7 @@
         return code;
       }
     }, new MockJavaResource("java.util.List") {
-      // Tests a Driver interface that extends more than RFED
+        // Tests a Driver interface that extends more than RFED
       @Override
       protected CharSequence getContent() {
         StringBuilder code = new StringBuilder();
@@ -300,7 +242,7 @@
         return code;
       }
     }, new MockJavaResource("java.util.Set") {
-      // Tests a Driver interface that extends more than RFED
+        // Tests a Driver interface that extends more than RFED
       @Override
       protected CharSequence getContent() {
         StringBuilder code = new StringBuilder();
@@ -310,7 +252,7 @@
         return code;
       }
     }, new MockJavaResource("java.util.SortedSet") {
-      // Tests a Driver interface that extends more than RFED
+        // Tests a Driver interface that extends more than RFED
       @Override
       protected CharSequence getContent() {
         StringBuilder code = new StringBuilder();
@@ -323,18 +265,18 @@
 
     Set<Resource> toReturn = new HashSet<Resource>(Arrays.asList(javaFiles));
 
-    toReturn.addAll(Arrays.asList(
-        new Resource[]{new EmptyMockJavaResource(Iterable.class),
-            new EmptyMockJavaResource(Property.class),
-            new EmptyMockJavaResource(EntityProxy.class),
-            new EmptyMockJavaResource(InstanceRequest.class),
-            new EmptyMockJavaResource(RequestFactory.class),
-            new EmptyMockJavaResource(Receiver.class),
+    toReturn.addAll(Arrays.asList(new Resource[] {
+        new EmptyMockJavaResource(Iterable.class),
+        new EmptyMockJavaResource(Property.class),
+        new EmptyMockJavaResource(EntityProxy.class),
+        new EmptyMockJavaResource(InstanceRequest.class),
+        new EmptyMockJavaResource(RequestFactory.class),
+        new EmptyMockJavaResource(Receiver.class),
 
-            new RealJavaResource(Request.class),
-            new RealJavaResource(Service.class),
-            new RealJavaResource(ProxyFor.class),
-            new EmptyMockJavaResource(RequestContext.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/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java b/user/test/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
new file mode 100644
index 0000000..0b1bad7
--- /dev/null
+++ b/user/test/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS 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.requestfactory.server.RequestFactoryInterfaceValidator.ClassLoaderLoader;
+import com.google.gwt.requestfactory.shared.EntityProxy;
+import com.google.gwt.requestfactory.shared.InstanceRequest;
+import com.google.gwt.requestfactory.shared.ProxyFor;
+import com.google.gwt.requestfactory.shared.Request;
+import com.google.gwt.requestfactory.shared.RequestContext;
+import com.google.gwt.requestfactory.shared.Service;
+import com.google.gwt.requestfactory.shared.SimpleRequestFactory;
+
+import junit.framework.TestCase;
+
+import java.util.logging.Logger;
+
+/**
+ * JRE tests for {@link RequestFactoryInterfaceValidator}.
+ */
+public class RequestFactoryInterfaceValidatorTest extends TestCase {
+  static class Domain {
+    static int fooStatic(int a) {
+      return 0;
+    }
+
+    int foo(int a) {
+      return 0;
+    }
+  }
+
+  @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();
+  }
+
+  class Foo {
+  }
+
+  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);
+  }
+
+  RequestFactoryInterfaceValidator v;
+
+  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 testMissingServiceAnnotations() {
+    v.validateRequestContext(RequestContextMissingAnnotation.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 testOverloadedMethod() {
+    v.validateEntityProxy(DomainWithOverloadsProxy.class.getName());
+    assertTrue(v.isPoisoned());
+  }
+
+  /**
+   * Perform a full test of the RequestFactory used for most tests.
+   */
+  public void testTestCodeFactories() {
+    v.validateRequestFactory(SimpleRequestFactory.class.getName());
+    assertFalse(v.isPoisoned());
+  }
+
+  @Override
+  protected void setUp() throws Exception {
+    Logger logger = Logger.getLogger("");
+    v = new RequestFactoryInterfaceValidator(logger, new ClassLoaderLoader(
+        Thread.currentThread().getContextClassLoader()));
+  }
+}
diff --git a/user/test/com/google/gwt/requestfactory/shared/SimpleBarProxy.java b/user/test/com/google/gwt/requestfactory/shared/SimpleBarProxy.java
index e08a18d..6c8c1ae 100644
--- a/user/test/com/google/gwt/requestfactory/shared/SimpleBarProxy.java
+++ b/user/test/com/google/gwt/requestfactory/shared/SimpleBarProxy.java
@@ -15,13 +15,11 @@
  */
 package com.google.gwt.requestfactory.shared;
 
-import com.google.gwt.requestfactory.server.SimpleBar;
-
 /**
  * A simple entity used for testing. Has an int field and date field. Add other
  * data types as their support gets built in.
  */
-@ProxyFor(SimpleBar.class)
+@ProxyForName("com.google.gwt.requestfactory.server.SimpleBar")
 public interface SimpleBarProxy extends EntityProxy {
   Boolean getFindFails();
 
diff --git a/user/test/com/google/gwt/requestfactory/shared/SimpleBarRequest.java b/user/test/com/google/gwt/requestfactory/shared/SimpleBarRequest.java
index 0a8e7f2..f0196ad 100644
--- a/user/test/com/google/gwt/requestfactory/shared/SimpleBarRequest.java
+++ b/user/test/com/google/gwt/requestfactory/shared/SimpleBarRequest.java
@@ -21,7 +21,7 @@
 /**
  * Do nothing test interface.
  */
-@Service(com.google.gwt.requestfactory.server.SimpleBar.class)
+@ServiceName("com.google.gwt.requestfactory.server.SimpleBar")
 public interface SimpleBarRequest extends RequestContext {
 
   Request<Long> countSimpleBar();