]> source.dussan.org Git - vaadin-framework.git/commitdiff
Change map serialization to use same scheme as GWT AutoBean (#8602)
authorLeif Åstrand <leif@vaadin.com>
Tue, 5 Jun 2012 12:10:57 +0000 (15:10 +0300)
committerLeif Åstrand <leif@vaadin.com>
Wed, 6 Jun 2012 10:33:51 +0000 (13:33 +0300)
src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java
src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java
src/com/vaadin/terminal/gwt/server/JsonCodec.java

index c7b33a70db04f301f5b1244fee61d9e13b765c04..1459d8ee7d1cf871f4a06b97b43c81e764fd4ba3 100644 (file)
@@ -8,14 +8,12 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
 import com.google.gwt.json.client.JSONArray;
 import com.google.gwt.json.client.JSONObject;
-import com.google.gwt.json.client.JSONParser;
 import com.google.gwt.json.client.JSONString;
 import com.google.gwt.json.client.JSONValue;
 import com.vaadin.terminal.gwt.client.ApplicationConnection;
@@ -58,7 +56,7 @@ public class JsonDecoder {
             return decodeArray(type, (JSONArray) jsonValue, connection);
         } else if (Map.class.getName().equals(baseTypeName)
                 || HashMap.class.getName().equals(baseTypeName)) {
-            return decodeMap(type, (JSONObject) jsonValue, connection);
+            return decodeMap(type, jsonValue, connection);
         } else if (List.class.getName().equals(baseTypeName)
                 || ArrayList.class.getName().equals(baseTypeName)) {
             return decodeList(type, (JSONArray) jsonValue, connection);
@@ -105,20 +103,80 @@ public class JsonDecoder {
         }
     }
 
-    private static Map<Object, Object> decodeMap(Type type, JSONObject jsonMap,
+    private static Map<Object, Object> decodeMap(Type type, JSONValue jsonMap,
             ApplicationConnection connection) {
-        HashMap<Object, Object> map = new HashMap<Object, Object>();
-        Iterator<String> it = jsonMap.keySet().iterator();
-        while (it.hasNext()) {
-            String key = it.next();
-            JSONValue encodedKey = JSONParser.parseStrict(key);
-            JSONValue encodedValue = jsonMap.get(key);
-            Object decodedKey = decodeValue(type.getParameterTypes()[0],
-                    encodedKey, null, connection);
-            Object decodedValue = decodeValue(type.getParameterTypes()[1],
-                    encodedValue, null, connection);
+        // Client -> server encodes empty map as an empty array because of
+        // #8906. Do the same for server -> client to maintain symmetry.
+        if (jsonMap instanceof JSONArray) {
+            JSONArray array = (JSONArray) jsonMap;
+            if (array.size() == 0) {
+                return new HashMap<Object, Object>();
+            }
+        }
+
+        Type keyType = type.getParameterTypes()[0];
+        Type valueType = type.getParameterTypes()[1];
+
+        if (keyType.getBaseTypeName().equals(String.class.getName())) {
+            return decodeStringMap(valueType, jsonMap, connection);
+        } else if (keyType.getBaseTypeName().equals(Connector.class.getName())) {
+            return decodeConnectorMap(valueType, jsonMap, connection);
+        } else {
+            return decodeObjectMap(keyType, valueType, jsonMap, connection);
+        }
+    }
+
+    private static Map<Object, Object> decodeObjectMap(Type keyType,
+            Type valueType, JSONValue jsonValue,
+            ApplicationConnection connection) {
+        Map<Object, Object> map = new HashMap<Object, Object>();
+
+        JSONArray mapArray = (JSONArray) jsonValue;
+        JSONArray keys = (JSONArray) mapArray.get(0);
+        JSONArray values = (JSONArray) mapArray.get(1);
+
+        assert (keys.size() == values.size());
+
+        for (int i = 0; i < keys.size(); i++) {
+            Object decodedKey = decodeValue(keyType, keys.get(i), null,
+                    connection);
+            Object decodedValue = decodeValue(valueType, values.get(i), null,
+                    connection);
+
             map.put(decodedKey, decodedValue);
         }
+
+        return map;
+    }
+
+    private static Map<Object, Object> decodeConnectorMap(Type valueType,
+            JSONValue jsonValue, ApplicationConnection connection) {
+        Map<Object, Object> map = new HashMap<Object, Object>();
+
+        JSONObject jsonMap = (JSONObject) jsonValue;
+        ConnectorMap connectorMap = ConnectorMap.get(connection);
+
+        for (String connectorId : jsonMap.keySet()) {
+            Object value = decodeValue(valueType, jsonMap.get(connectorId),
+                    null, connection);
+            map.put(connectorMap.getConnector(connectorId), value);
+        }
+
+        return map;
+    }
+
+    private static Map<Object, Object> decodeStringMap(Type valueType,
+            JSONValue jsonValue, ApplicationConnection connection) {
+        Map<Object, Object> map = new HashMap<Object, Object>();
+
+        JSONObject jsonMap = (JSONObject) jsonValue;
+
+        for (String key : jsonMap.keySet()) {
+            Object value = decodeValue(valueType, jsonMap.get(key), null,
+                    connection);
+            map.put(key, value);
+        }
+
         return map;
     }
 
index 541dc631c6b48015bd533b8fe0832dfae8abae7d..df095833dc8feb5520f022f5a2ae68c1161a725a 100644 (file)
@@ -7,6 +7,7 @@ package com.vaadin.terminal.gwt.client.communication;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 
 import com.google.gwt.json.client.JSONArray;
@@ -129,24 +130,85 @@ public class JsonEncoder {
     private static JSONValue encodeMap(Map<Object, Object> map,
             boolean restrictToInternalTypes, ConnectorMap connectorMap,
             ApplicationConnection connection) {
+        /*
+         * As we have no info about declared types, we instead select encoding
+         * scheme based on actual type of first key. We can't do this if there's
+         * no first key, so instead we send some special value that the
+         * server-side decoding must check for. (see #8906)
+         */
+        if (map.isEmpty()) {
+            return new JSONArray();
+        }
+
+        Object firstKey = map.keySet().iterator().next();
+        if (firstKey instanceof String) {
+            return encodeStringMap(map, restrictToInternalTypes, connectorMap,
+                    connection);
+        } else if (restrictToInternalTypes) {
+            throw new IllegalStateException(
+                    "Only string keys supported for legacy maps");
+        } else if (firstKey instanceof Connector) {
+            return encodeConenctorMap(map, connectorMap, connection);
+        } else {
+            return encodeObjectMap(map, connectorMap, connection);
+        }
+    }
+
+    private static JSONValue encodeObjectMap(Map<Object, Object> map,
+            ConnectorMap connectorMap, ApplicationConnection connection) {
+        JSONArray keys = new JSONArray();
+        JSONArray values = new JSONArray();
+        for (Entry<?, ?> entry : map.entrySet()) {
+            // restrictToInternalTypes always false if we end up here
+            keys.set(keys.size(),
+                    encode(entry.getKey(), false, connectorMap, connection));
+            values.set(values.size(),
+                    encode(entry.getValue(), false, connectorMap, connection));
+        }
+
+        JSONArray keysAndValues = new JSONArray();
+        keysAndValues.set(0, keys);
+        keysAndValues.set(1, values);
+
+        return keysAndValues;
+    }
+
+    private static JSONValue encodeConenctorMap(Map<Object, Object> map,
+            ConnectorMap connectorMap, ApplicationConnection connection) {
         JSONObject jsonMap = new JSONObject();
-        for (Object mapKey : map.keySet()) {
-            Object mapValue = map.get(mapKey);
+
+        for (Entry<?, ?> entry : map.entrySet()) {
+            Connector connector = (Connector) entry.getKey();
+
+            // restrictToInternalTypes always false if we end up here
+            JSONValue encodedValue = encode(entry.getValue(), false,
+                    connectorMap, connection);
+
+            jsonMap.put(connector.getConnectorId(), encodedValue);
+        }
+
+        return jsonMap;
+    }
+
+    private static JSONValue encodeStringMap(Map<Object, Object> map,
+            boolean restrictToInternalTypes, ConnectorMap connectorMap,
+            ApplicationConnection connection) {
+        JSONObject jsonMap = new JSONObject();
+
+        for (Entry<?, ?> entry : map.entrySet()) {
+            String key = (String) entry.getKey();
+            Object value = entry.getValue();
+
             if (restrictToInternalTypes) {
-                if (!(mapKey instanceof String)) {
-                    throw new IllegalStateException(
-                            "Only string keys supported for legacy maps");
-                }
-                // Wrap in UidlValue to send explicit type info
-                mapKey = new UidlValue(mapKey);
-                mapValue = new UidlValue(mapValue);
+                value = new UidlValue(value);
             }
-            JSONValue encodedKey = encode(mapKey, restrictToInternalTypes,
-                    connectorMap, connection);
-            JSONValue encodedValue = encode(mapValue, restrictToInternalTypes,
+
+            JSONValue encodedValue = encode(value, restrictToInternalTypes,
                     connectorMap, connection);
-            jsonMap.put(encodedKey.toString(), encodedValue);
+
+            jsonMap.put(key, encodedValue);
         }
+
         return jsonMap;
     }
 
index e76a3d3f3a35bbd8e945bf70d8a7b03f2d124e5e..feaf78d2e894592b801dbe681361684fd8ec6556 100644 (file)
@@ -13,19 +13,20 @@ import java.lang.reflect.Method;
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 
 import com.vaadin.Application;
 import com.vaadin.external.json.JSONArray;
 import com.vaadin.external.json.JSONException;
 import com.vaadin.external.json.JSONObject;
-import com.vaadin.external.json.JSONTokener;
 import com.vaadin.terminal.gwt.client.Connector;
 import com.vaadin.terminal.gwt.client.communication.JsonEncoder;
 import com.vaadin.terminal.gwt.client.communication.UidlValue;
@@ -178,7 +179,7 @@ public class JsonCodec implements Serializable {
                     (JSONArray) encodedJsonValue, application);
         } else if (JsonEncoder.VTYPE_MAP.equals(transportType)) {
             return decodeMap(targetType, restrictToInternalTypes,
-                    (JSONObject) encodedJsonValue, application);
+                    encodedJsonValue, application);
         }
 
         // Arrays
@@ -243,23 +244,92 @@ public class JsonCodec implements Serializable {
     }
 
     private static Map<Object, Object> decodeMap(Type targetType,
-            boolean restrictToInternalTypes, JSONObject jsonMap,
+            boolean restrictToInternalTypes, Object jsonMap,
             Application application) throws JSONException {
-        HashMap<Object, Object> map = new HashMap<Object, Object>();
+        if (jsonMap instanceof JSONArray) {
+            // Client-side has no declared type information to determine
+            // encoding method for empty maps, so these are handled separately.
+            // See #8906.
+            JSONArray jsonArray = (JSONArray) jsonMap;
+            if (jsonArray.length() == 0) {
+                return new HashMap<Object, Object>();
+            }
+        }
 
-        Iterator<String> it = jsonMap.keys();
-        while (it.hasNext()) {
-            String key = it.next();
-            String keyString = (String) new JSONTokener(key).nextValue();
-            Object encodedKey = new JSONTokener(keyString).nextValue();
-            Object encodedValue = jsonMap.get(key);
+        if (!restrictToInternalTypes && targetType instanceof ParameterizedType) {
+            Type keyType = ((ParameterizedType) targetType)
+                    .getActualTypeArguments()[0];
+            Type valueType = ((ParameterizedType) targetType)
+                    .getActualTypeArguments()[1];
+            if (keyType == String.class) {
+                return decodeStringMap(valueType, (JSONObject) jsonMap,
+                        application);
+            } else if (keyType == Connector.class) {
+                return decodeConnectorMap(valueType, (JSONObject) jsonMap,
+                        application);
+            } else {
+                return decodeObjectMap(keyType, valueType, (JSONArray) jsonMap,
+                        application);
+            }
+        } else {
+            return decodeStringMap(UidlValue.class, (JSONObject) jsonMap,
+                    application);
+        }
+    }
+
+    private static Map<Object, Object> decodeObjectMap(Type keyType,
+            Type valueType, JSONArray jsonMap, Application application)
+            throws JSONException {
+        Map<Object, Object> map = new HashMap<Object, Object>();
+
+        JSONArray keys = jsonMap.getJSONArray(0);
+        JSONArray values = jsonMap.getJSONArray(1);
+
+        assert (keys.length() == values.length());
+
+        for (int i = 0; i < keys.length(); i++) {
+            Object key = decodeInternalOrCustomType(keyType, keys.get(i),
+                    application);
+            Object value = decodeInternalOrCustomType(valueType, values.get(i),
+                    application);
 
-            Object decodedKey = decodeParametrizedType(targetType,
-                    restrictToInternalTypes, 0, encodedKey, application);
-            Object decodedValue = decodeParametrizedType(targetType,
-                    restrictToInternalTypes, 1, encodedValue, application);
-            map.put(decodedKey, decodedValue);
+            map.put(key, value);
         }
+
+        return map;
+    }
+
+    private static Map<Object, Object> decodeConnectorMap(Type valueType,
+            JSONObject jsonMap, Application application) throws JSONException {
+        Map<Object, Object> map = new HashMap<Object, Object>();
+
+        for (Iterator<?> iter = jsonMap.keys(); iter.hasNext();) {
+            String key = (String) iter.next();
+            Object value = decodeInternalOrCustomType(valueType,
+                    jsonMap.get(key), application);
+            if (valueType == UidlValue.class) {
+                value = ((UidlValue) value).getValue();
+            }
+            map.put(application.getConnector(key), value);
+        }
+
+        return map;
+    }
+
+    private static Map<Object, Object> decodeStringMap(Type valueType,
+            JSONObject jsonMap, Application application) throws JSONException {
+        Map<Object, Object> map = new HashMap<Object, Object>();
+
+        for (Iterator<?> iter = jsonMap.keys(); iter.hasNext();) {
+            String key = (String) iter.next();
+            Object value = decodeInternalOrCustomType(valueType,
+                    jsonMap.get(key), application);
+            if (valueType == UidlValue.class) {
+                value = ((UidlValue) value).getValue();
+            }
+            map.put(key, value);
+        }
+
         return map;
     }
 
@@ -429,7 +499,7 @@ public class JsonCodec implements Serializable {
             JSONArray jsonArray = encodeArrayContents(array, application);
             return jsonArray;
         } else if (value instanceof Map) {
-            JSONObject jsonMap = encodeMap(valueType, (Map<?, ?>) value,
+            Object jsonMap = encodeMap(valueType, (Map<?, ?>) value,
                     application);
             return jsonMap;
         } else if (value instanceof Connector) {
@@ -550,7 +620,7 @@ public class JsonCodec implements Serializable {
         }
     }
 
-    private static JSONObject encodeMap(Type mapType, Map<?, ?> map,
+    private static Object encodeMap(Type mapType, Map<?, ?> map,
             Application application) throws JSONException {
         Type keyType, valueType;
 
@@ -561,13 +631,64 @@ public class JsonCodec implements Serializable {
             throw new JSONException("Map is missing generics");
         }
 
+        if (map.isEmpty()) {
+            // Client -> server encodes empty map as an empty array because of
+            // #8906. Do the same for server -> client to maintain symmetry.
+            return new JSONArray();
+        }
+
+        if (keyType == String.class) {
+            return encodeStringMap(valueType, map, application);
+        } else if (keyType == Connector.class) {
+            return encodeConnectorMap(valueType, map, application);
+        } else {
+            return encodeObjectMap(keyType, valueType, map, application);
+        }
+    }
+
+    private static JSONArray encodeObjectMap(Type keyType, Type valueType,
+            Map<?, ?> map, Application application) throws JSONException {
+        JSONArray keys = new JSONArray();
+        JSONArray values = new JSONArray();
+
+        for (Entry<?, ?> entry : map.entrySet()) {
+            Object encodedKey = encode(entry.getKey(), null, keyType,
+                    application);
+            Object encodedValue = encode(entry.getValue(), null, valueType,
+                    application);
+
+            keys.put(encodedKey);
+            values.put(encodedValue);
+        }
+
+        return new JSONArray(Arrays.asList(keys, values));
+    }
+
+    private static JSONObject encodeConnectorMap(Type valueType, Map<?, ?> map,
+            Application application) throws JSONException {
+        JSONObject jsonMap = new JSONObject();
+
+        for (Entry<?, ?> entry : map.entrySet()) {
+            Connector key = (Connector) entry.getKey();
+            Object encodedValue = encode(entry.getValue(), null, valueType,
+                    application);
+            jsonMap.put(key.getConnectorId(), encodedValue);
+        }
+
+        return jsonMap;
+    }
+
+    private static JSONObject encodeStringMap(Type valueType, Map<?, ?> map,
+            Application application) throws JSONException {
         JSONObject jsonMap = new JSONObject();
-        for (Object mapKey : map.keySet()) {
-            Object mapValue = map.get(mapKey);
-            Object encodedKey = encode(mapKey, null, keyType, application);
-            Object encodedValue = encode(mapValue, null, valueType, application);
-            jsonMap.put(JSONObject.quote(encodedKey.toString()), encodedValue);
+
+        for (Entry<?, ?> entry : map.entrySet()) {
+            String key = (String) entry.getKey();
+            Object encodedValue = encode(entry.getValue(), null, valueType,
+                    application);
+            jsonMap.put(key, encodedValue);
         }
+
         return jsonMap;
     }