Custom Field Serializer for EnumMap
Contributed by: bradley

Fixes Issue 1634804
Review at http://codereview.appspot.com/6174044/


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@11050 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/client/rpc/core/java/util/EnumMap_CustomFieldSerializer.java b/user/src/com/google/gwt/user/client/rpc/core/java/util/EnumMap_CustomFieldSerializer.java
new file mode 100644
index 0000000..b25b509
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/rpc/core/java/util/EnumMap_CustomFieldSerializer.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2012 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * 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.user.client.rpc.core.java.util;
+
+import com.google.gwt.user.client.rpc.CustomFieldSerializer;
+import com.google.gwt.user.client.rpc.SerializationException;
+import com.google.gwt.user.client.rpc.SerializationStreamReader;
+import com.google.gwt.user.client.rpc.SerializationStreamWriter;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.util.EnumMap;
+
+/**
+ * The super source version is used in web mode. This is only called in hosted mode.
+ * Custom field serializer for {@link java.util.EnumMap}.
+ */
+@SuppressWarnings({"unchecked", "rawtypes"})
+public final class EnumMap_CustomFieldSerializer extends CustomFieldSerializer<EnumMap> {
+
+  public static void deserialize(SerializationStreamReader streamReader, EnumMap instance)
+      throws SerializationException {
+    Map_CustomFieldSerializerBase.deserialize(streamReader, instance);
+  }
+
+  /**
+   * EnumMap has no empty constructor; you must provide the class literal to the constructor.
+   * However GWT doesn't emulate java.lang.Class.forName() nor is java.util.Class GWT-RPC
+   * serializable. In order to get the type across the wire, one enum of the appropriate class is
+   * serialized at the beginning of the stream. Upon deserialization this enum's type is
+   * introspected and used in the constructor for the new EnumMap.
+   */
+  public static EnumMap instantiate(SerializationStreamReader streamReader)
+      throws SerializationException {
+    Object exemplar = streamReader.readObject();
+    Class clazz = exemplar.getClass();
+    return new EnumMap(clazz);
+  }
+  
+  /**
+  * Since this code is only run in hosted mode, reflection can be used.
+   */
+  public static void serialize(SerializationStreamWriter streamWriter, EnumMap instance)
+      throws SerializationException {
+    Class c = instance.getClass();
+    Field keyUniverseField;
+    Object keyUniverse = null;
+
+    try {
+      keyUniverseField = c.getDeclaredField("keyUniverse");
+      keyUniverseField.setAccessible(true);
+      keyUniverse = keyUniverseField.get(instance);
+    } catch (IllegalArgumentException e) {
+      throw new SerializationException(e);
+    } catch (IllegalAccessException e) {
+      throw new SerializationException(e);
+    } catch (SecurityException e) {
+      throw new SerializationException(e);
+    } catch (NoSuchFieldException e) {
+      throw new SerializationException(e);
+    }
+    Object exemplar = Array.get(keyUniverse, 0);
+    streamWriter.writeObject(exemplar);
+    Map_CustomFieldSerializerBase.serialize(streamWriter, instance);
+  }
+
+  @Override
+  public void deserializeInstance(SerializationStreamReader streamReader, EnumMap instance)
+      throws SerializationException {
+    deserialize(streamReader, instance);
+  }
+
+  @Override
+  public boolean hasCustomInstantiateInstance() {
+    return true;
+  }
+
+  @Override
+  public EnumMap instantiateInstance(SerializationStreamReader streamReader)
+      throws SerializationException {
+    return instantiate(streamReader);
+  }
+
+  @Override
+  public void serializeInstance(SerializationStreamWriter streamWriter, EnumMap instance)
+      throws SerializationException {
+    serialize(streamWriter, instance);
+  }
+}
diff --git a/user/src/com/google/gwt/user/server/rpc/core/java/util/EnumMap_ServerCustomFieldSerializer.java b/user/src/com/google/gwt/user/server/rpc/core/java/util/EnumMap_ServerCustomFieldSerializer.java
new file mode 100644
index 0000000..dfe6b2d
--- /dev/null
+++ b/user/src/com/google/gwt/user/server/rpc/core/java/util/EnumMap_ServerCustomFieldSerializer.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * 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.user.server.rpc.core.java.util;
+
+import com.google.gwt.user.client.rpc.SerializationException;
+import com.google.gwt.user.client.rpc.SerializationStreamReader;
+import com.google.gwt.user.client.rpc.SerializationStreamWriter;
+import com.google.gwt.user.client.rpc.core.java.util.EnumMap_CustomFieldSerializer;
+import com.google.gwt.user.client.rpc.core.java.util.Map_CustomFieldSerializerBase;
+import com.google.gwt.user.server.rpc.ServerCustomFieldSerializer;
+import com.google.gwt.user.server.rpc.impl.DequeMap;
+import com.google.gwt.user.server.rpc.impl.ServerSerializationStreamReader;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.util.EnumMap;
+
+/**
+ * Custom field serializer for {@link java.util.EnumMap} for the server.
+ */
+@SuppressWarnings({"unchecked", "rawtypes"})
+public final class EnumMap_ServerCustomFieldSerializer extends ServerCustomFieldSerializer<EnumMap>
+{
+
+    public static void deserialize(ServerSerializationStreamReader streamReader, EnumMap instance,
+        Type[] expectedParameterTypes, DequeMap<TypeVariable< ? >, Type> resolvedTypes)
+        throws SerializationException {
+        Map_ServerCustomFieldSerializerBase.deserialize(streamReader, instance,
+          expectedParameterTypes, resolvedTypes);
+    }
+
+    @Override
+    public void deserializeInstance(SerializationStreamReader streamReader, EnumMap instance)
+        throws SerializationException {
+        EnumMap_CustomFieldSerializer.deserialize(streamReader, instance);
+    }
+
+    @Override
+    public void deserializeInstance(ServerSerializationStreamReader streamReader, EnumMap instance,
+        Type[] expectedParameterTypes, DequeMap<TypeVariable< ? >, Type> resolvedTypes)
+        throws SerializationException {
+        deserialize(streamReader, instance, expectedParameterTypes, resolvedTypes);
+    }
+
+    @Override
+    public boolean hasCustomInstantiateInstance() {
+        return true;
+    }
+
+    @Override
+    public EnumMap instantiateInstance(SerializationStreamReader streamReader)
+        throws SerializationException {
+        return EnumMap_CustomFieldSerializer.instantiate(streamReader);
+    }
+
+    @Override
+    public EnumMap instantiateInstance(ServerSerializationStreamReader streamReader,
+        Type[] expectedParameterTypes, DequeMap<TypeVariable< ? >, Type> resolvedTypes)
+        throws SerializationException {
+      return EnumMap_CustomFieldSerializer.instantiate(streamReader);
+    }
+
+    @Override
+    public void serializeInstance(SerializationStreamWriter streamWriter, EnumMap instance)
+        throws SerializationException {
+       Class c = instance.getClass();
+       Field keyUniverseField;
+       Object keyUniverse = null;
+       try {
+         keyUniverseField = c.getDeclaredField("keyUniverse");
+         keyUniverseField.setAccessible(true);
+         keyUniverse = keyUniverseField.get(instance);
+       } catch (Exception e) {
+         throw new SerializationException(e);
+       }
+       Object exemplar = Array.get(keyUniverse, 0);
+       streamWriter.writeObject(exemplar);
+       Map_CustomFieldSerializerBase.serialize(streamWriter, instance);
+    }
+}
diff --git a/user/super/com/google/gwt/user/translatable/com/google/gwt/user/client/rpc/core/java/util/EnumMap_CustomFieldSerializer.java b/user/super/com/google/gwt/user/translatable/com/google/gwt/user/client/rpc/core/java/util/EnumMap_CustomFieldSerializer.java
new file mode 100644
index 0000000..9324037
--- /dev/null
+++ b/user/super/com/google/gwt/user/translatable/com/google/gwt/user/client/rpc/core/java/util/EnumMap_CustomFieldSerializer.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2012 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software 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.user.client.rpc.core.java.util;
+
+import com.google.gwt.core.client.GwtScriptOnly;
+import com.google.gwt.user.client.rpc.CustomFieldSerializer;
+import com.google.gwt.user.client.rpc.SerializationException;
+import com.google.gwt.user.client.rpc.SerializationStreamReader;
+import com.google.gwt.user.client.rpc.SerializationStreamWriter;
+
+import java.util.EnumMap;
+import java.util.EnumSet;
+
+/**
+ * Custom field serializer for {@link java.util.EnumMap}.
+ */
+@GwtScriptOnly
+@SuppressWarnings({"unchecked", "rawtypes"})
+public final class EnumMap_CustomFieldSerializer extends CustomFieldSerializer<EnumMap> {
+
+  public static void deserialize(SerializationStreamReader streamReader, EnumMap instance)
+      throws SerializationException {
+    Map_CustomFieldSerializerBase.deserialize(streamReader, instance);
+  }
+
+  /**
+   * EnumMap has no empty constructor; you must provide the class literal to the constructor.
+   * However GWT doesn't emulate java.lang.Class.forName() nor is java.util.Class GWT-RPC
+   * serializable. In order to get the type across the wire, one enum of the appropriate class is
+   * serialized at the beginning of the stream. Upon deserialization this enum's type is
+   * introspected and used in the constructor for the new EnumMap.
+   */
+  public static EnumMap instantiate(SerializationStreamReader streamReader)
+      throws SerializationException {
+    Object exemplar = streamReader.readObject();
+    Class clazz = exemplar.getClass();
+    return new EnumMap(clazz);
+  }
+  
+  public static native <E> E[] getKeyUniverse(EnumMap<?, ?> map) /*-{
+    return map.@java.util.EnumMap::keySet.@java.util.EnumSet$EnumSetImpl::all;
+  }-*/;
+
+  /**
+   * A jsni method is used to access the private keySet field of the EnumMap instance.
+   * This EnumSet field is filled with the universe of allowed enums even if the instance is empty.
+   * The first enum is pulled from the set and serialized to the stream in order to allow the other
+   * side to instantiate the correct type of EnumMap.
+   */
+  public static void serialize(SerializationStreamWriter streamWriter, EnumMap instance)
+      throws SerializationException {
+    Object exemplar;
+    Object [] keyUniverse = getKeyUniverse(instance);
+    exemplar = keyUniverse[0];  
+    streamWriter.writeObject(exemplar);
+    Map_CustomFieldSerializerBase.serialize(streamWriter, instance);
+  }
+
+  @Override
+  public void deserializeInstance(SerializationStreamReader streamReader, EnumMap instance)
+      throws SerializationException {
+    deserialize(streamReader, instance);
+  }
+
+  @Override
+  public boolean hasCustomInstantiateInstance() {
+    return true;
+  }
+
+  @Override
+  public EnumMap instantiateInstance(SerializationStreamReader streamReader)
+      throws SerializationException {
+    return instantiate(streamReader);
+  }
+
+  @Override
+  public void serializeInstance(SerializationStreamWriter streamWriter, EnumMap instance)
+      throws SerializationException {
+    serialize(streamWriter, instance);
+  }
+}
\ No newline at end of file
diff --git a/user/test/com/google/gwt/user/client/rpc/CollectionsTest.java b/user/test/com/google/gwt/user/client/rpc/CollectionsTest.java
index b01ed5e..220efe0 100644
--- a/user/test/com/google/gwt/user/client/rpc/CollectionsTest.java
+++ b/user/test/com/google/gwt/user/client/rpc/CollectionsTest.java
@@ -23,9 +23,10 @@
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeEmptySet;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeEmptyValue;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeEnum;
+import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeEnumMapValue;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeHashMapKey;
-import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeHashMapValue;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeHashSet;
+import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeHashMapValue;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeIdentityHashMapKey;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeIdentityHashMapValue;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeLinkedHashMapKey;
@@ -42,8 +43,9 @@
 import java.sql.Timestamp;
 import java.util.ArrayList;
 import java.util.Date;
-import java.util.HashMap;
+import java.util.EnumMap;
 import java.util.HashSet;
+import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
@@ -247,6 +249,25 @@
     });
   }
 
+  public void testEmptyEnumMap() {
+    CollectionsTestServiceAsync service = getServiceAsync();
+    final EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue> expected =
+        TestSetFactory.createEmptyEnumMap();
+    delayTestFinishForRpc();
+    service.echoEmptyEnumMap(expected, new AsyncCallback<EnumMap<MarkerTypeEnum,
+      MarkerTypeEnumMapValue>>() {
+        public void onFailure(Throwable caught) {
+          TestSetValidator.rethrowException(caught);
+        }
+
+        public void onSuccess(EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue> result) {
+          assertNotNull(result);
+          assertTrue(TestSetValidator.isValid(expected, result));
+          finishTest();
+        }
+    });
+  }
+  
   public void testEmptyList() {
     CollectionsTestServiceAsync service = getServiceAsync();
     delayTestFinishForRpc();
@@ -320,6 +341,44 @@
     });
   }
 
+  public void testEnumMapEnumKey() {
+    CollectionsTestServiceAsync service = getServiceAsync();
+    final EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue> expected =
+        TestSetFactory.createEnumMapEnumKey();
+    delayTestFinishForRpc();
+    service.echoEnumKey(expected,
+        new AsyncCallback<EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue>>() {
+          public void onFailure(Throwable caught) {
+            TestSetValidator.rethrowException(caught);
+          }
+
+          public void onSuccess(EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue> result) {
+            assertNotNull(result);
+            assertTrue(TestSetValidator.isValidEnumKey(expected, result));
+            finishTest();
+          }
+        });
+  }
+
+  public void testEnumMap() {
+    CollectionsTestServiceAsync service = getServiceAsync();
+    final EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue> expected =
+        TestSetFactory.createEnumMap();
+    delayTestFinishForRpc();
+    service.echo(expected,
+        new AsyncCallback<EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue>>() {
+          public void onFailure(Throwable caught) {
+            TestSetValidator.rethrowException(caught);
+          }
+
+          public void onSuccess(EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue> result) {
+            assertNotNull(result);
+            assertTrue(TestSetValidator.isValid(expected, result));
+            finishTest();
+          }
+        });
+  }
+
   public void testFloatArray() {
     CollectionsTestServiceAsync service = getServiceAsync();
     final Float[] expected = TestSetFactory.createFloatArray();
diff --git a/user/test/com/google/gwt/user/client/rpc/CollectionsTestService.java b/user/test/com/google/gwt/user/client/rpc/CollectionsTestService.java
index a82b60b..6e70c33 100644
--- a/user/test/com/google/gwt/user/client/rpc/CollectionsTestService.java
+++ b/user/test/com/google/gwt/user/client/rpc/CollectionsTestService.java
@@ -22,9 +22,10 @@
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeEmptySet;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeEmptyValue;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeEnum;
+import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeEnumMapValue;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeHashMapKey;
-import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeHashMapValue;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeHashSet;
+import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeHashMapValue;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeIdentityHashMapKey;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeIdentityHashMapValue;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeLinkedHashMapKey;
@@ -40,8 +41,9 @@
 import java.sql.Timestamp;
 import java.util.ArrayList;
 import java.util.Date;
-import java.util.HashMap;
+import java.util.EnumMap;
 import java.util.HashSet;
+import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
@@ -110,6 +112,10 @@
 
   Float[] echo(Float[] value) throws CollectionsTestServiceException;
 
+  EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue> echo(
+      EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue> value)
+      throws CollectionsTestServiceException;
+ 
   HashMap<MarkerTypeHashMapKey, MarkerTypeHashMapValue> echo(
       HashMap<MarkerTypeHashMapKey, MarkerTypeHashMapValue> value)
       throws CollectionsTestServiceException;
@@ -171,7 +177,15 @@
   List<MarkerTypeArraysAsList> echoArraysAsList(
       List<MarkerTypeArraysAsList> value)
       throws CollectionsTestServiceException;
+  
+  EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue> echoEmptyEnumMap(
+      EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue> value)
+      throws CollectionsTestServiceException;
 
+  EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue> echoEnumKey(
+      EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue> value)
+      throws CollectionsTestServiceException;
+  
   IdentityHashMap<MarkerTypeEnum, MarkerTypeIdentityHashMapValue> echoEnumKey(
       IdentityHashMap<MarkerTypeEnum, MarkerTypeIdentityHashMapValue> value)
       throws CollectionsTestServiceException;
diff --git a/user/test/com/google/gwt/user/client/rpc/CollectionsTestServiceAsync.java b/user/test/com/google/gwt/user/client/rpc/CollectionsTestServiceAsync.java
index 226d03f..599c8d0 100644
--- a/user/test/com/google/gwt/user/client/rpc/CollectionsTestServiceAsync.java
+++ b/user/test/com/google/gwt/user/client/rpc/CollectionsTestServiceAsync.java
@@ -21,8 +21,9 @@
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeEmptyList;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeEmptySet;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeEmptyValue;
-import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeEnum;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeHashMapKey;
+import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeEnum;
+import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeEnumMapValue;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeHashMapValue;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeHashSet;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeIdentityHashMapKey;
@@ -40,6 +41,7 @@
 import java.sql.Timestamp;
 import java.util.ArrayList;
 import java.util.Date;
+import java.util.EnumMap;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
@@ -96,6 +98,9 @@
 
   void echo(Float[] value, AsyncCallback<Float[]> callback);
 
+  void echo(EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue> value,
+      AsyncCallback<EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue>> callback);
+
   void echo(HashMap<MarkerTypeHashMapKey, MarkerTypeHashMapValue> value,
       AsyncCallback<HashMap<MarkerTypeHashMapKey, MarkerTypeHashMapValue>> callback);
 
@@ -151,6 +156,12 @@
   void echoArraysAsList(List<MarkerTypeArraysAsList> value,
       AsyncCallback<List<MarkerTypeArraysAsList>> callback);
 
+  void echoEmptyEnumMap(EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue> expected,
+      AsyncCallback<EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue>> asyncCallback);
+  
+  void echoEnumKey(EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue> value,
+      AsyncCallback<EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue>> callback);
+
   void echoEnumKey(IdentityHashMap<MarkerTypeEnum, MarkerTypeIdentityHashMapValue> value,
       AsyncCallback<IdentityHashMap<MarkerTypeEnum, MarkerTypeIdentityHashMapValue>> callback);
 
diff --git a/user/test/com/google/gwt/user/client/rpc/TestSetFactory.java b/user/test/com/google/gwt/user/client/rpc/TestSetFactory.java
index cae6954..6e98e2f 100644
--- a/user/test/com/google/gwt/user/client/rpc/TestSetFactory.java
+++ b/user/test/com/google/gwt/user/client/rpc/TestSetFactory.java
@@ -24,6 +24,7 @@
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.Date;
+import java.util.EnumMap;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
@@ -153,6 +154,21 @@
   public static enum MarkerTypeEnum {
     A, B, C;
   }
+  
+  /**
+   * A single-use marker type to independently check type parameter exposure in
+   * various collections.
+   */
+  public static final class MarkerTypeEnumMapValue extends MarkerBase {
+
+    public MarkerTypeEnumMapValue(String value) {
+      super(value);
+    }
+
+   MarkerTypeEnumMapValue() {
+     super(null);
+   }
+  }
 
   /**
    * A single-use marker type to independently check type parameter exposure in
@@ -556,7 +572,37 @@
         new Float(Float.MIN_VALUE)};
   }
 
+  public static EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue> createEmptyEnumMap() {
+    EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue> map =
+       new EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue>(MarkerTypeEnum.class);
+   return map;
+  }
+
+  public static EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue> createEnumMap() {
+    EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue> map =
+        new EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue>(MarkerTypeEnum.class);
+    map.put(MarkerTypeEnum.A, new MarkerTypeEnumMapValue("A"));
+    map.put(MarkerTypeEnum.B, new MarkerTypeEnumMapValue("B"));
+    map.put(MarkerTypeEnum.C, new MarkerTypeEnumMapValue("C"));
+    return map;
+  }
+
+  public static EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue>
+  createEnumMapEnumKey() {
+    EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue> map =
+        new EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue>(MarkerTypeEnum.class);
+    /*
+     * An EnumMap lets us check that references to Enums remain constant
+     * across RPC send-receive cycles.
+     */
+    map.put(MarkerTypeEnum.A, new MarkerTypeEnumMapValue("A"));
+    map.put(MarkerTypeEnum.B, new MarkerTypeEnumMapValue("B"));
+    map.put(MarkerTypeEnum.C, new MarkerTypeEnumMapValue("C"));
+    return map;
+  }
+
   public static HashMap<MarkerTypeHashMapKey, MarkerTypeHashMapValue> createHashMap() {
+
     HashMap<MarkerTypeHashMapKey, MarkerTypeHashMapValue> map =
         new HashMap<MarkerTypeHashMapKey, MarkerTypeHashMapValue>();
     map.put(new MarkerTypeHashMapKey("foo"), new MarkerTypeHashMapValue("foo"));
diff --git a/user/test/com/google/gwt/user/client/rpc/TestSetValidator.java b/user/test/com/google/gwt/user/client/rpc/TestSetValidator.java
index 5c68518..f5601b0 100644
--- a/user/test/com/google/gwt/user/client/rpc/TestSetValidator.java
+++ b/user/test/com/google/gwt/user/client/rpc/TestSetValidator.java
@@ -21,6 +21,7 @@
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeEmptySet;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeEmptyValue;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeEnum;
+import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeEnumMapValue;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeIdentityHashMapKey;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeIdentityHashMapValue;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeSingleton;
@@ -40,6 +41,7 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.EnumMap;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
@@ -326,6 +328,60 @@
     return set != null && set.size() == 0;
   }
 
+  public static boolean isValidEnumKey(
+    EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue> expected,
+    EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue> map) {
+    if (map == null) {
+      return false;
+    }
+
+    Set<?> entries = expected.entrySet();
+    Iterator<?> entryIter = entries.iterator();
+    while (entryIter.hasNext()) {
+      Entry<?, ?> entry = (Entry<?, ?>) entryIter.next();
+
+      Object value = map.get(entry.getKey());
+
+      if (value != entry.getValue()) {
+        if (value == null || entry.getValue() == null) {
+          return false;
+        } 
+
+        if (!map.get(entry.getKey()).equals(entry.getValue())) {
+          return false;
+        }
+      }
+    } 
+
+    return true;
+  }
+
+  public static boolean isValid(EnumMap<?, ?> expected, EnumMap<?, ?> map) {
+    if (map == null) {
+      return false;
+    }
+
+    Set<?> entries = expected.entrySet();
+    Iterator<?> entryIter = entries.iterator();
+    while (entryIter.hasNext()) {
+      Entry<?, ?> entry = (Entry<?, ?>) entryIter.next();
+
+      Object value = map.get(entry.getKey());
+
+      if (value != entry.getValue()) {
+        if (value == null || entry.getValue() == null) {
+          return false;
+        } 
+
+        if (!map.get(entry.getKey()).equals(entry.getValue())) {
+          return false;
+        }
+      }
+    } 
+
+    return true;
+  }
+
   public static boolean isValid(HashMap<?, ?> expected, HashMap<?, ?> map) {
     if (map == null) {
       return false;
diff --git a/user/test/com/google/gwt/user/server/rpc/CollectionsTestServiceImpl.java b/user/test/com/google/gwt/user/server/rpc/CollectionsTestServiceImpl.java
index 3a7e2b6..cb07bd8 100644
--- a/user/test/com/google/gwt/user/server/rpc/CollectionsTestServiceImpl.java
+++ b/user/test/com/google/gwt/user/server/rpc/CollectionsTestServiceImpl.java
@@ -24,9 +24,10 @@
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeArrayList;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeArraysAsList;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeEnum;
+import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeEnumMapValue;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeHashMapKey;
-import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeHashMapValue;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeHashSet;
+import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeHashMapValue;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeIdentityHashMapKey;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeIdentityHashMapValue;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeLinkedHashMapKey;
@@ -44,6 +45,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Date;
+import java.util.EnumMap;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
@@ -190,6 +192,18 @@
     return actual;
   }
 
+  public EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue> echo(
+      EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue> actual)
+      throws CollectionsTestServiceException {
+    EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue> expected = TestSetFactory.createEnumMap();
+    if (!TestSetValidator.isValid(expected, actual)) {
+      throw new CollectionsTestServiceException("expected: "
+          + expected.toString() + " actual: " + actual.toString());
+    }
+
+    return actual;
+  }
+
   public Float[] echo(Float[] actual) throws CollectionsTestServiceException {
     Float[] expected = TestSetFactory.createFloatArray();
     if (!TestSetValidator.equals(expected, actual)) {
@@ -464,6 +478,18 @@
     return value;
   }
 
+  public EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue> echoEmptyEnumMap(
+      EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue> actual)
+      throws CollectionsTestServiceException {
+    EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue> expected = TestSetFactory.createEmptyEnumMap();
+    if (!TestSetValidator.isValid(expected, actual)) {
+      throw new CollectionsTestServiceException("expected: "
+          + expected.toString() + " actual: " + actual.toString());
+    }
+
+    return actual;
+  }
+  
   public IdentityHashMap<MarkerTypeEnum, MarkerTypeIdentityHashMapValue> echoEnumKey(
       IdentityHashMap<MarkerTypeEnum, MarkerTypeIdentityHashMapValue> actual)
       throws CollectionsTestServiceException {
@@ -476,6 +502,19 @@
     return actual;
   }
 
+  public EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue> echoEnumKey(
+      EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue> actual)
+      throws CollectionsTestServiceException {
+    EnumMap<MarkerTypeEnum, MarkerTypeEnumMapValue> expected =
+      TestSetFactory.createEnumMapEnumKey();
+    if (!TestSetValidator.isValidEnumKey(expected, actual)) {
+      throw new CollectionsTestServiceException("expected: "
+          + expected.toString() + " actual: " + actual.toString());
+    }
+
+    return actual;
+  }
+
   public List<MarkerTypeSingleton> echoSingletonList(
       List<MarkerTypeSingleton> value) throws CollectionsTestServiceException {
     if (!TestSetValidator.isValidSingletonList(value)) {