Support upcasting Date, BigInteger, and BigDecimal types.


git-svn-id: https://google-web-toolkit.googlecode.com/svn/releases/2.1@9339 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/tools/api-checker/config/gwt20_21userApi.conf b/tools/api-checker/config/gwt20_21userApi.conf
index 697b3cc..54313db 100644
--- a/tools/api-checker/config/gwt20_21userApi.conf
+++ b/tools/api-checker/config/gwt20_21userApi.conf
@@ -66,6 +66,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/tools/api-checker/config/gwt21_22userApi.conf b/tools/api-checker/config/gwt21_22userApi.conf
index 9664ee9..3e4f820 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<