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>>() {