Support up-casting subclasses of java.util.Date (e.g. java.sql.Date).
Issue 5675.
Patch by: bobv
Review by: rjrjr, rchandia

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


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9336 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/tools/api-checker/config/gwt21_22userApi.conf b/tools/api-checker/config/gwt21_22userApi.conf
index f876d49..e700609 100644
--- a/tools/api-checker/config/gwt21_22userApi.conf
+++ b/tools/api-checker/config/gwt21_22userApi.conf
@@ -73,6 +73,7 @@
 :**/server/**\
 :**/tools/**\
 :user/src/com/google/gwt/regexp/shared/**\
+:user/src/com/google/gwt/autobean/shared/ValueCodexHelper.java\
 :user/src/com/google/gwt/autobean/shared/impl/StringQuoter.java\
 :user/src/com/google/gwt/core/client/impl/WeakMapping.java\
 :user/src/com/google/gwt/junit/*.java\
diff --git a/user/src/com/google/gwt/autobean/shared/AutoBeanCodex.java b/user/src/com/google/gwt/autobean/shared/AutoBeanCodex.java
index 9d228f4..2ae2f0e 100644
--- a/user/src/com/google/gwt/autobean/shared/AutoBeanCodex.java
+++ b/user/src/com/google/gwt/autobean/shared/AutoBeanCodex.java
@@ -295,7 +295,8 @@
 
       if (ValueCodex.canDecode(ctx.getElementType())) {
         for (Object element : collection) {
-          sb.append(",").append(encodeValue(element).getPayload());
+          sb.append(",").append(
+              encodeValue(ctx.getElementType(), element).getPayload());
         }
       } else {
         boolean isEncoded = Splittable.class.equals(ctx.getElementType());
@@ -330,15 +331,18 @@
         return false;
       }
 
-      boolean isEncodedKey = Splittable.class.equals(ctx.getKeyType());
-      boolean isEncodedValue = Splittable.class.equals(ctx.getValueType());
-      boolean isValueKey = ValueCodex.canDecode(ctx.getKeyType());
-      boolean isValueValue = ValueCodex.canDecode(ctx.getValueType());
+      Class<?> keyType = ctx.getKeyType();
+      Class<?> valueType = ctx.getValueType();
+      boolean isEncodedKey = Splittable.class.equals(keyType);
+      boolean isEncodedValue = Splittable.class.equals(valueType);
+      boolean isValueKey = ValueCodex.canDecode(keyType);
+      boolean isValueValue = ValueCodex.canDecode(valueType);
 
       if (isValueKey) {
-        writeValueKeyMap(map, isEncodedValue, isValueValue);
+        writeValueKeyMap(map, keyType, valueType, isEncodedValue, isValueValue);
       } else {
-        writeObjectKeyMap(map, isEncodedKey, isEncodedValue, isValueValue);
+        writeObjectKeyMap(map, valueType, isEncodedKey, isEncodedValue,
+            isValueValue);
       }
 
       return false;
@@ -377,7 +381,7 @@
 
       // Special handling for enums if we have an obfuscation map
       Splittable split;
-      split = encodeValue(value);
+      split = encodeValue(type, value);
       sb.append(",\"").append(propertyName).append("\":").append(
           split.getPayload());
       return false;
@@ -409,12 +413,13 @@
      * Encodes a value, with special handling for enums to allow the field name
      * to be overridden.
      */
-    private Splittable encodeValue(Object value) {
+    private Splittable encodeValue(Class<?> expectedType, Object value) {
       Splittable split;
       if (value instanceof Enum<?> && enumMap != null) {
-        split = ValueCodex.encode(enumMap.getToken((Enum<?>) value));
+        split = ValueCodex.encode(String.class,
+            enumMap.getToken((Enum<?>) value));
       } else {
-        split = ValueCodex.encode(value);
+        split = ValueCodex.encode(expectedType, value);
       }
       return split;
     }
@@ -429,8 +434,8 @@
      * encoded as a list of two lists, since it's possible that two distinct
      * objects have the same encoded form.
      */
-    private void writeObjectKeyMap(Map<?, ?> map, boolean isEncodedKey,
-        boolean isEncodedValue, boolean isValueValue) {
+    private void writeObjectKeyMap(Map<?, ?> map, Class<?> valueType,
+        boolean isEncodedKey, boolean isEncodedValue, boolean isValueValue) {
       StringBuilder keys = new StringBuilder();
       StringBuilder values = new StringBuilder();
 
@@ -445,7 +450,8 @@
           values.append(",").append(
               ((Splittable) entry.getValue()).getPayload());
         } else if (isValueValue) {
-          values.append(",").append(encodeValue(entry.getValue()).getPayload());
+          values.append(",").append(
+              encodeValue(valueType, entry.getValue()).getPayload());
         } else {
           encodeToStringBuilder(values.append(","), entry.getValue());
         }
@@ -462,15 +468,15 @@
     /**
      * Writes a map JSON literal where the keys are value types.
      */
-    private void writeValueKeyMap(Map<?, ?> map, boolean isEncodedValue,
-        boolean isValueValue) {
+    private void writeValueKeyMap(Map<?, ?> map, Class<?> keyType,
+        Class<?> valueType, boolean isEncodedValue, boolean isValueValue) {
       for (Map.Entry<?, ?> entry : map.entrySet()) {
-        sb.append(",").append(encodeValue(entry.getKey()).getPayload()).append(
+        sb.append(",").append(encodeValue(keyType, entry.getKey()).getPayload()).append(
             ":");
         if (isEncodedValue) {
           sb.append(((Splittable) entry.getValue()).getPayload());
         } else if (isValueValue) {
-          sb.append(encodeValue(entry.getValue()).getPayload());
+          sb.append(encodeValue(valueType, entry.getValue()).getPayload());
         } else {
           encodeToStringBuilder(sb, entry.getValue());
         }
diff --git a/user/src/com/google/gwt/autobean/shared/ValueCodex.java b/user/src/com/google/gwt/autobean/shared/ValueCodex.java
index d4a452f..ca867eb 100644
--- a/user/src/com/google/gwt/autobean/shared/ValueCodex.java
+++ b/user/src/com/google/gwt/autobean/shared/ValueCodex.java
@@ -20,9 +20,11 @@
 
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Provides unified encoding and decoding of value objects.
@@ -31,6 +33,11 @@
   enum Type {
     BIG_DECIMAL(BigDecimal.class) {
       @Override
+      public boolean canUpcast(Object value) {
+        return value instanceof BigDecimal;
+      }
+
+      @Override
       public BigDecimal decode(Class<?> clazz, String value) {
         return new BigDecimal(value);
       }
@@ -42,6 +49,11 @@
     },
     BIG_INTEGER(BigInteger.class) {
       @Override
+      public boolean canUpcast(Object value) {
+        return value instanceof BigInteger;
+      }
+
+      @Override
       public BigInteger decode(Class<?> clazz, String value) {
         return new BigInteger(value);
       }
@@ -71,6 +83,11 @@
     },
     DATE(Date.class) {
       @Override
+      public boolean canUpcast(Object value) {
+        return value instanceof Date;
+      }
+
+      @Override
       public Date decode(Class<?> clazz, String value) {
         return new Date(Long.valueOf(value));
       }
@@ -158,6 +175,15 @@
       this.defaultValue = defaultValue;
     }
 
+    /**
+     * Determines whether or not the Type can handle the given value via
+     * upcasting semantics.
+     */
+    public boolean canUpcast(Object value) {
+      // Most value types are final, so this method is meaningless
+      return false;
+    }
+
     public abstract Object decode(Class<?> clazz, String value);
 
     public Object getDefaultValue() {
@@ -177,14 +203,18 @@
     }
   }
 
-  private static Map<Class<?>, Type> typesByClass = new HashMap<Class<?>, Type>();
+  private static final Set<Class<?>> ALL_VALUE_TYPES;
+  private static final Map<Class<?>, Type> TYPES_BY_CLASS;
   static {
+    Map<Class<?>, Type> temp = new HashMap<Class<?>, Type>();
     for (Type t : Type.values()) {
-      typesByClass.put(t.getType(), t);
+      temp.put(t.getType(), t);
       if (t.getPrimitiveType() != null) {
-        typesByClass.put(t.getPrimitiveType(), t);
+        temp.put(t.getPrimitiveType(), t);
       }
     }
+    ALL_VALUE_TYPES = Collections.unmodifiableSet(temp.keySet());
+    TYPES_BY_CLASS = Collections.unmodifiableMap(temp);
   }
 
   /**
@@ -194,7 +224,11 @@
    * @return {@code true} if the given object type can be decoded
    */
   public static boolean canDecode(Class<?> clazz) {
-    return findType(clazz) != null;
+    if (findType(clazz) != null) {
+      return true;
+    }
+    // Use other platform-specific tests
+    return ValueCodexHelper.canDecode(clazz);
   }
 
   public static <T> T decode(Class<T> clazz, Splittable split) {
@@ -212,12 +246,42 @@
     return (T) getTypeOrDie(clazz).decode(clazz, string);
   }
 
+  /**
+   * Encode a value object when the wire format type is known. This method
+   * should be preferred over {@link #encode(Object)} when possible.
+   */
+  public static Splittable encode(Class<?> clazz, Object obj) {
+    if (obj == null) {
+      return LazySplittable.NULL;
+    }
+    return new LazySplittable(getTypeOrDie(clazz).toJsonExpression(obj));
+  }
+
   public static Splittable encode(Object obj) {
     if (obj == null) {
       return LazySplittable.NULL;
     }
-    return new LazySplittable(
-        getTypeOrDie(obj.getClass()).toJsonExpression(obj));
+    Type t = findType(obj.getClass());
+    // Try upcasting
+    if (t == null) {
+      for (Type maybe : Type.values()) {
+        if (maybe.canUpcast(obj)) {
+          t = maybe;
+          break;
+        }
+      }
+    }
+    if (t == null) {
+      throw new UnsupportedOperationException(obj.getClass().getName());
+    }
+    return new LazySplittable(t.toJsonExpression(obj));
+  }
+
+  /**
+   * Return all Value types that can be processed by the ValueCodex.
+   */
+  public static Set<Class<?>> getAllValueTypes() {
+    return ALL_VALUE_TYPES;
   }
 
   /**
@@ -235,13 +299,10 @@
    * May return <code>null</code>.
    */
   private static <T> Type findType(Class<T> clazz) {
-    Type type = typesByClass.get(clazz);
-    if (type == null) {
-      if (clazz.isEnum()) {
-        return Type.ENUM;
-      }
+    if (clazz.isEnum()) {
+      return Type.ENUM;
     }
-    return type;
+    return TYPES_BY_CLASS.get(clazz);
   }
 
   private static <T> Type getTypeOrDie(Class<T> clazz) {
diff --git a/user/src/com/google/gwt/autobean/shared/ValueCodexHelper.java b/user/src/com/google/gwt/autobean/shared/ValueCodexHelper.java
new file mode 100644
index 0000000..c6f72c8
--- /dev/null
+++ b/user/src/com/google/gwt/autobean/shared/ValueCodexHelper.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.autobean.shared;
+
+import com.google.gwt.core.client.GWT;
+
+/**
+ * Provides reflection-based operation for server (JVM) implementation. There is
+ * a no-op super-source version for client (dev- and web-mode) code.
+ */
+class ValueCodexHelper {
+  /**
+   * Returns {@code true} if {@code clazz} is assignable to any of the value
+   * types.
+   */
+  static boolean canDecode(Class<?> clazz) {
+    assert !GWT.isClient();
+    for (Class<?> valueType : ValueCodex.getAllValueTypes()) {
+      if (valueType.isAssignableFrom(clazz)) {
+        return true;
+      }
+    }
+    return false;
+  }
+}
diff --git a/user/src/com/google/gwt/editor/rebind/model/ModelUtils.java b/user/src/com/google/gwt/editor/rebind/model/ModelUtils.java
index 583c49d..310b9dd 100644
--- a/user/src/com/google/gwt/editor/rebind/model/ModelUtils.java
+++ b/user/src/com/google/gwt/editor/rebind/model/ModelUtils.java
@@ -15,15 +15,14 @@
  */
 package com.google.gwt.editor.rebind.model;
 
+import com.google.gwt.autobean.shared.ValueCodex;
 import com.google.gwt.core.ext.typeinfo.JArrayType;
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.JParameterizedType;
 import com.google.gwt.core.ext.typeinfo.JType;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 
-import java.util.Arrays;
 import java.util.Collections;
-import java.util.Date;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -32,16 +31,12 @@
  */
 public class ModelUtils {
 
-  @SuppressWarnings("unchecked")
-  static final Set<Class<?>> VALUE_TYPES = Collections.unmodifiableSet(new HashSet<Class<?>>(
-      Arrays.asList(Boolean.class, Character.class, Class.class, Date.class,
-          Enum.class, Number.class, String.class, Void.class)));
-
   static final Set<String> VALUE_TYPE_NAMES;
 
   static {
-    Set<String> names = new HashSet<String>(VALUE_TYPES.size());
-    for (Class<?> clazz : VALUE_TYPES) {
+    Set<Class<?>> valueTypes = ValueCodex.getAllValueTypes();
+    Set<String> names = new HashSet<String>(valueTypes.size());
+    for (Class<?> clazz : valueTypes) {
       names.add(clazz.getName());
     }
     VALUE_TYPE_NAMES = Collections.unmodifiableSet(names);
@@ -110,11 +105,14 @@
     if (classType == null) {
       return true;
     }
+    if (type.isEnum() != null) {
+      return true;
+    }
 
     for (String valueType : VALUE_TYPE_NAMES) {
       JClassType found = oracle.findType(valueType);
       // null check to accommodate limited mock CompilationStates
-      if (found != null && found.isAssignableFrom(classType)) {
+      if (found != null && found.equals(classType)) {
         return true;
       }
     }
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java b/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java
index 2c8f005..f034b1c 100644
--- a/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java
+++ b/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.requestfactory.server;
 
+import com.google.gwt.autobean.shared.ValueCodex;
 import com.google.gwt.dev.asm.AnnotationVisitor;
 import com.google.gwt.dev.asm.ClassReader;
 import com.google.gwt.dev.asm.ClassVisitor;
@@ -44,9 +45,7 @@
 import java.io.InputStream;
 import java.lang.annotation.Annotation;
 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;
@@ -517,10 +516,7 @@
     }
   }
 
-  @SuppressWarnings("unchecked")
-  static final Set<Class<?>> VALUE_TYPES = Collections.unmodifiableSet(new HashSet<Class<?>>(
-      Arrays.asList(Boolean.class, Character.class, Class.class, Date.class,
-          Enum.class, Number.class, String.class, Void.class)));
+  static final Set<Class<?>> VALUE_TYPES = ValueCodex.getAllValueTypes();
 
   public static void main(String[] args) {
     if (args.length == 0) {
@@ -578,6 +574,10 @@
    */
   private final Type entityProxyIntf = Type.getType(EntityProxy.class);
   /**
+   * The type {@link Enum}.
+   */
+  private final Type enumType = Type.getType(Enum.class);
+  /**
    * A placeholder type for client types that could not be resolved to a domain
    * type.
    */
@@ -1361,12 +1361,8 @@
       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;
-      }
+    if (isAssignable(logger, enumType, type)) {
+      return true;
     }
     return false;
   }
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java
index 2a45831..839f4ee 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java
@@ -434,7 +434,10 @@
             if (properties.containsKey(propertyName)) {
               Splittable raw = properties.get(propertyName);
               Object decoded = ValueCodex.decode(ctx.getType(), raw);
-              // Hack for Date, consider generalizing for "custom serializers"
+              /*
+               * Hack for Date subtypes, consider generalizing for
+               * "custom serializers"
+               */
               if (decoded != null && Date.class.equals(ctx.getType())) {
                 decoded = new DatePoser((Date) decoded);
               }
diff --git a/user/super/com/google/gwt/autobean/super/com/google/gwt/autobean/shared/ValueCodexHelper.java b/user/super/com/google/gwt/autobean/super/com/google/gwt/autobean/shared/ValueCodexHelper.java
new file mode 100644
index 0000000..42c87e3
--- /dev/null
+++ b/user/super/com/google/gwt/autobean/super/com/google/gwt/autobean/shared/ValueCodexHelper.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.autobean.shared;
+
+/**
+ * A no-op super-source version of ValueCodexHelper for web-mode compilations.
+ */
+class ValueCodexHelper {
+  /**
+   * Returns {@code false}.
+   */
+  static boolean canDecode(Class<?> clazz) {
+    return false;
+  }
+}
diff --git a/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java b/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java
index 85e1cb7..5d0cb95 100644
--- a/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java
+++ b/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java
@@ -32,11 +32,16 @@
 import com.google.gwt.requestfactory.shared.Violation;
 import com.google.gwt.requestfactory.shared.impl.SimpleEntityProxyId;
 
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.sql.Time;
+import java.sql.Timestamp;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 
@@ -1801,20 +1806,71 @@
     });
   }
 
+  public void testPrimitiveListBigDecimalAsParameter() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    // Keep these values in sync with SimpleFoo.processBigIntegerList
+    final List<BigDecimal> testList = new ArrayList<BigDecimal>();
+    testList.add(BigDecimal.TEN);
+    testList.add(new BigDecimal("12345.6789") {
+      // This is an anonymous subtype
+    });
+    simpleFooRequest().processBigDecimalList(testList).fire(
+        new Receiver<List<BigDecimal>>() {
+          @Override
+          public void onSuccess(List<BigDecimal> response) {
+            // Check upcasted values only
+            assertEquals(testList, response);
+            finishTestAndReset();
+          }
+        });
+  }
+
+  public void testPrimitiveListBigIntegerAsParameter() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    // Keep these values in sync with SimpleFoo.processBigIntegerList
+    final List<BigInteger> testList = new ArrayList<BigInteger>();
+    testList.add(BigInteger.TEN);
+    testList.add(new BigInteger("12345") {
+      // This is an anonymous subtype
+    });
+    simpleFooRequest().processBigIntegerList(testList).fire(
+        new Receiver<List<BigInteger>>() {
+          @Override
+          public void onSuccess(List<BigInteger> response) {
+            // Check upcasted values only
+            assertEquals(testList, response);
+            finishTestAndReset();
+          }
+        });
+  }
+
+  @SuppressWarnings("deprecation")
   public void testPrimitiveListDateAsParameter() {
     delayTestFinish(DELAY_TEST_FINISH);
 
-    @SuppressWarnings("deprecation")
-    final Date date = new Date(90, 0, 1);
-    Request<Date> procReq = simpleFooRequest().processDateList(
-        Arrays.asList(date));
-    procReq.fire(new Receiver<Date>() {
-      @Override
-      public void onSuccess(Date response) {
-        assertEquals(date, response);
-        finishTestAndReset();
-      }
-    });
+    // Keep these values in sync with SimpleFoo.processDateList
+    Date date = new Date(90, 0, 1);
+    java.sql.Date sqlDate = new java.sql.Date(90, 0, 2);
+    Time sqlTime = new Time(1, 2, 3);
+    Timestamp sqlTimestamp = new Timestamp(12345L);
+    final List<Date> testList = Arrays.asList(date, sqlDate, sqlTime,
+        sqlTimestamp);
+    simpleFooRequest().processDateList(testList).fire(
+        new Receiver<List<Date>>() {
+          @Override
+          public void onSuccess(List<Date> response) {
+            // Check upcasted values only
+            assertEquals(testList.size(), response.size());
+            Iterator<Date> expected = testList.iterator();
+            Iterator<Date> actual = response.iterator();
+            while (expected.hasNext()) {
+              assertEquals(expected.next().getTime(), actual.next().getTime());
+            }
+            finishTestAndReset();
+          }
+        });
   }
 
   public void testPrimitiveListEnumAsParameter() {
diff --git a/user/test/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java b/user/test/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
index f6640da..1c6c003 100644
--- a/user/test/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
+++ b/user/test/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
@@ -87,6 +87,10 @@
     int foo(int a) {
       return 0;
     }
+
+    java.sql.Date getSqlDate() {
+      return null;
+    }
   }
 
   @ProxyFor(Domain.class)
@@ -111,11 +115,17 @@
       return 0;
     }
   }
+
   @ProxyFor(DomainWithOverloads.class)
   interface DomainWithOverloadsProxy extends EntityProxy {
     void foo();
   }
 
+  @ProxyFor(Domain.class)
+  interface DomainWithSqlDateProxy extends EntityProxy {
+    java.sql.Date getSqlDate();
+  }
+
   class Foo {
   }
 
@@ -270,6 +280,14 @@
   };
 
   /**
+   * Test that subclasses of {@code java.util.Date} are not transportable.
+   */
+  public void testDateSubclass() {
+    v.validateEntityProxy(DomainWithSqlDateProxy.class.getName());
+    assertTrue(v.isPoisoned());
+  }
+
+  /**
    * Test the {@link FindRequest} context used to implement find().
    */
   public void testFindRequestContext() {
diff --git a/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java b/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java
index 4a1bfb7..1ac5987 100644
--- a/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java
+++ b/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java
@@ -19,11 +19,14 @@
 
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.sql.Time;
+import java.sql.Timestamp;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -157,12 +160,72 @@
     return foo;
   }
 
+  /**
+   * Check client-side upcasting to BigDecimal and return a list of BigDecimals
+   * that should be upcast.
+   */
+  public static List<BigDecimal> processBigDecimalList(List<BigDecimal> values) {
+    List<BigDecimal> toReturn = new ArrayList<BigDecimal>();
+    toReturn.add(BigDecimal.TEN);
+    toReturn.add(new BigDecimal("12345.6789") {
+      // This is an anonymous subtype
+    });
+    if (!toReturn.equals(values)) {
+      throw new IllegalArgumentException(toReturn + " != " + values);
+    }
+    return toReturn;
+  }
+
+  /**
+   * Check client-side upcasting to BigInteger and return a list of BigIntegers
+   * that should be upcast.
+   */
+  public static List<BigInteger> processBigIntegerList(List<BigInteger> values) {
+    List<BigInteger> toReturn = new ArrayList<BigInteger>();
+    toReturn.add(BigInteger.TEN);
+    toReturn.add(new BigInteger("12345") {
+      // This is an anonymous subtype
+    });
+    if (!toReturn.equals(values)) {
+      throw new IllegalArgumentException(toReturn + " != " + values);
+    }
+    return toReturn;
+  }
+
   public static Boolean processBooleanList(List<Boolean> values) {
     return values.get(0);
   }
 
-  public static Date processDateList(List<Date> values) {
-    return values.get(0);
+  /**
+   * Check client-side upcasting to Date and return a list of Dates that should
+   * be upcast.
+   */
+  @SuppressWarnings("deprecation")
+  public static List<Date> processDateList(List<Date> values) {
+    // Keep these values in sync with SimpleFoo.processDateList
+    Date date = new Date(90, 0, 1);
+    java.sql.Date sqlDate = new java.sql.Date(90, 0, 2);
+    Time sqlTime = new Time(1, 2, 3);
+    Timestamp sqlTimestamp = new Timestamp(12345L);
+    List<Date> toReturn = Arrays.asList(date, sqlDate, sqlTime, sqlTimestamp);
+
+    if (toReturn.size() != values.size()) {
+      throw new IllegalArgumentException("size");
+    }
+
+    Iterator<Date> expected = toReturn.iterator();
+    Iterator<Date> actual = values.iterator();
+    while (expected.hasNext()) {
+      Date expectedDate = expected.next();
+      long expectedTime = expectedDate.getTime();
+      long actualTime = actual.next().getTime();
+      if (expectedTime != actualTime) {
+        throw new IllegalArgumentException(expectedDate.getClass().getName()
+            + " " + expectedTime + " != " + actualTime);
+      }
+    }
+
+    return toReturn;
   }
 
   public static SimpleEnum processEnumList(List<SimpleEnum> values) {
@@ -278,7 +341,7 @@
   public static String returnNullString() {
     return null;
   }
-  
+
   public static SimpleFoo returnSimpleFooSubclass() {
     return new SimpleFoo() {
     };
diff --git a/user/test/com/google/gwt/requestfactory/shared/SimpleFooRequest.java b/user/test/com/google/gwt/requestfactory/shared/SimpleFooRequest.java
index cd174be..bbb4c6b 100644
--- a/user/test/com/google/gwt/requestfactory/shared/SimpleFooRequest.java
+++ b/user/test/com/google/gwt/requestfactory/shared/SimpleFooRequest.java
@@ -15,6 +15,8 @@
  */
 package com.google.gwt.requestfactory.shared;
 
+import java.math.BigDecimal;
+import java.math.BigInteger;
 import java.util.Date;
 import java.util.List;
 import java.util.Set;
@@ -61,9 +63,13 @@
 
   InstanceRequest<SimpleFooProxy, SimpleFooProxy> persistCascadingAndReturnSelf();
 
+  Request<List<BigInteger>> processBigIntegerList(List<BigInteger> values);
+
+  Request<List<BigDecimal>> processBigDecimalList(List<BigDecimal> values);
+
   Request<Boolean> processBooleanList(List<Boolean> values);
 
-  Request<Date> processDateList(List<Date> values);
+  Request<List<Date>> processDateList(List<Date> values);
 
   Request<SimpleEnum> processEnumList(List<SimpleEnum> values);