Revision: 5957 Author: r...@google.com Date: Thu Aug 13 08:05:36 2009 Log: 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 http://code.google.com/p/google-web-toolkit/source/detail?r=5957 Modified: /trunk/user/src/com/google/gwt/user/client/rpc/core/java/util/LinkedHashMap_CustomFieldSerializer.java /trunk/user/super/com/google/gwt/user/translatable/com/google/gwt/user/client/rpc/core/java/util/LinkedHashMap_CustomFieldSerializer.java /trunk/user/test/com/google/gwt/user/client/rpc/CollectionsTest.java ======================================= --- /trunk/user/src/com/google/gwt/user/client/rpc/core/java/util/LinkedHashMap_CustomFieldSerializer.java Fri May 23 08:38:33 2008 +++ /trunk/user/src/com/google/gwt/user/client/rpc/core/java/util/LinkedHashMap_CustomFieldSerializer.java Thu Aug 13 08:05:36 2009 @@ -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); } } ======================================= --- /trunk/user/super/com/google/gwt/user/translatable/com/google/gwt/user/client/rpc/core/java/util/LinkedHashMap_CustomFieldSerializer.java Thu Aug 28 17:26:44 2008 +++ /trunk/user/super/com/google/gwt/user/translatable/com/google/gwt/user/client/rpc/core/java/util/LinkedHashMap_CustomFieldSerializer.java Thu Aug 13 08:05:36 2009 @@ -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 instan...@java.util.linkedhashmap::accessOrder; }-*/; ======================================= --- /trunk/user/test/com/google/gwt/user/client/rpc/CollectionsTest.java Mon Jul 6 16:17:17 2009 +++ /trunk/user/test/com/google/gwt/user/client/rpc/CollectionsTest.java Thu Aug 13 08:05:36 2009 @@ -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>>() { --~--~---------~--~----~------------~-------~--~----~ http://groups.google.com/group/Google-Web-Toolkit-Contributors -~----------~----~----~----~------~----~------~--~---