Allow LinkedHashMap to be serialized in the AppEngine environment,
where it is not possible to use reflection to query the accessOrder
field.

Review by: jlabanca



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5957 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/client/rpc/core/java/util/LinkedHashMap_CustomFieldSerializer.java b/user/src/com/google/gwt/user/client/rpc/core/java/util/LinkedHashMap_CustomFieldSerializer.java
index 250f4fb..cfa4728 100644
--- a/user/src/com/google/gwt/user/client/rpc/core/java/util/LinkedHashMap_CustomFieldSerializer.java
+++ b/user/src/com/google/gwt/user/client/rpc/core/java/util/LinkedHashMap_CustomFieldSerializer.java
@@ -34,6 +34,54 @@
     Map_CustomFieldSerializerBase.deserialize(streamReader, instance);

   }

 

+  /**

+   * Infers the value of the private accessOrder field of instance by examining

+   * its behavior on a set of test inputs, without using reflection. Note that

+   * this implementation clones the instance, which could be slow.

+   * 

+   * @param instance the instance to check

+   * @return the value of instance.accessOrder

+   */

+  @SuppressWarnings("unchecked") // raw LinkedHashMap

+  public static boolean getAccessOrderNoReflection(LinkedHashMap instance) {    

+    /*

+     * Clone the instance so our modifications won't affect the original.

+     * In particular, if the original overrides removeEldestEntry, adding

+     * elements to the map could cause existing elements to be removed.

+     */

+    instance = (LinkedHashMap) instance.clone();

+    instance.clear();

+

+    /*

+     * We insert key1, then key2, after which we access key1. We then iterate

+     * over the key set and observe the order in which keys are returned. The

+     * iterator will return keys in the order of least recent insertion or

+     * access, depending on the value of the accessOrder field within the

+     * LinkedHashMap instance. If the iterator is ordered by least recent

+     * insertion (accessOrder = false), we will encounter key1 first since key2

+     * has been inserted more recently. If it is ordered by least recent access

+     * (accessOrder = true), we will encounter key2 first, since key1 has been

+     * accessed more recently.

+     */

+    Object key1 = new Object();

+    Object key2 = new Object();

+    instance.put(key1, key1); // INSERT key1

+    instance.put(key2, key2); // INSERT key2

+    instance.get(key1);       // ACCESS key1

+    boolean accessOrder = false;

+    for (Object key : instance.keySet()) {

+      if (key == key1) {

+        break;

+      }

+      if (key == key2) {

+        accessOrder = true;

+        break;

+      }

+    }

+

+    return accessOrder;

+  }

+

   @SuppressWarnings("unchecked") // raw LinkedHashMap

   public static LinkedHashMap instantiate(SerializationStreamReader streamReader)

       throws SerializationException {

@@ -49,21 +97,23 @@
   }

 

   @SuppressWarnings("unchecked") // raw LinkedHashMap

-  private static boolean getAccessOrder(LinkedHashMap instance)

-      throws SerializationException {

+  private static boolean getAccessOrder(LinkedHashMap instance) {

     Field accessOrderField;

     try {

       accessOrderField = LinkedHashMap.class.getDeclaredField("accessOrder");

       accessOrderField.setAccessible(true);

       return ((Boolean) accessOrderField.get(instance)).booleanValue();

     } catch (SecurityException e) {

-      throw new SerializationException("Can't get accessOrder field", e);

+      // fall through

     } catch (NoSuchFieldException e) {

-      throw new SerializationException("Can't get accessOrder field", e);

+      // fall through

     } catch (IllegalArgumentException e) {

-      throw new SerializationException("Can't get accessOrder field", e);

+      // fall through

     } catch (IllegalAccessException e) {

-      throw new SerializationException("Can't get accessOrder field", e);

+      // fall through

     }

+    

+    // Use a (possibly slower) technique that does not require reflection.

+    return getAccessOrderNoReflection(instance);

   }

 }

diff --git a/user/super/com/google/gwt/user/translatable/com/google/gwt/user/client/rpc/core/java/util/LinkedHashMap_CustomFieldSerializer.java b/user/super/com/google/gwt/user/translatable/com/google/gwt/user/client/rpc/core/java/util/LinkedHashMap_CustomFieldSerializer.java
index 1129ce4..eee075b 100644
--- a/user/super/com/google/gwt/user/translatable/com/google/gwt/user/client/rpc/core/java/util/LinkedHashMap_CustomFieldSerializer.java
+++ b/user/super/com/google/gwt/user/translatable/com/google/gwt/user/client/rpc/core/java/util/LinkedHashMap_CustomFieldSerializer.java
@@ -31,6 +31,12 @@
       LinkedHashMap instance) throws SerializationException {
     Map_CustomFieldSerializerBase.deserialize(streamReader, instance);
   }
+  
+  @SuppressWarnings("unchecked") // raw LinkedHashMap
+  // Included for testability
+  public static boolean getAccessOrderNoReflection(LinkedHashMap instance) {  
+    return getAccessOrder(instance);
+  }
 
   public static LinkedHashMap instantiate(SerializationStreamReader streamReader)
       throws SerializationException {
@@ -44,6 +50,7 @@
     Map_CustomFieldSerializerBase.serialize(streamWriter, instance);
   }
 
+  @SuppressWarnings("unchecked") // raw LinkedHashMap
   private static native boolean getAccessOrder(LinkedHashMap instance) /*-{
     return instance.@java.util.LinkedHashMap::accessOrder;
   }-*/;
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 6513cb2..6cfb9e5 100644
--- a/user/test/com/google/gwt/user/client/rpc/CollectionsTest.java
+++ b/user/test/com/google/gwt/user/client/rpc/CollectionsTest.java
@@ -25,6 +25,7 @@
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeTreeMap;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeTreeSet;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeVector;
+import com.google.gwt.user.client.rpc.core.java.util.LinkedHashMap_CustomFieldSerializer;
 
 import java.sql.Time;
 import java.sql.Timestamp;
@@ -304,6 +305,7 @@
     CollectionsTestServiceAsync service = getServiceAsync();
 
     final LinkedHashMap<String, MarkerTypeLinkedHashMap> expected = TestSetFactory.createLinkedHashMap();
+    assertFalse(LinkedHashMap_CustomFieldSerializer.getAccessOrderNoReflection(expected));
 
     service.echo(expected,
         new AsyncCallback<LinkedHashMap<String, MarkerTypeLinkedHashMap>>() {
@@ -327,6 +329,7 @@
     CollectionsTestServiceAsync service = getServiceAsync();
 
     final LinkedHashMap<String, MarkerTypeLinkedHashMap> expected = TestSetFactory.createLRULinkedHashMap();
+    assertTrue(LinkedHashMap_CustomFieldSerializer.getAccessOrderNoReflection(expected));
 
     service.echo(expected,
         new AsyncCallback<LinkedHashMap<String, MarkerTypeLinkedHashMap>>() {