]> source.dussan.org Git - vaadin-framework.git/commitdiff
Added support for map keys of any type (#8602)
authorArtur Signell <artur@vaadin.com>
Sun, 13 May 2012 21:49:15 +0000 (00:49 +0300)
committerArtur Signell <artur@vaadin.com>
Sun, 13 May 2012 23:10:37 +0000 (02:10 +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
tests/client-side/com/vaadin/terminal/gwt/server/JSONSerializerTest.java [new file with mode: 0644]

index d7cf764f75f4d4711cd038031f99d784991e9a77..9ed20b6c79969375af4e1b5da29b50c73be473ae 100644 (file)
@@ -14,10 +14,10 @@ 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;
-import com.vaadin.terminal.gwt.client.Connector;
 import com.vaadin.terminal.gwt.client.ConnectorMap;
 import com.vaadin.terminal.gwt.client.ServerConnector;
 
@@ -64,8 +64,6 @@ public class JsonDecoder {
             val = decodeArray((JSONArray) value, idMapper, connection);
         } else if (JsonEncoder.VTYPE_MAP.equals(variableType)) {
             val = decodeMap((JSONObject) value, idMapper, connection);
-        } else if (JsonEncoder.VTYPE_MAP_CONNECTOR.equals(variableType)) {
-            val = decodeConnectorMap((JSONObject) value, idMapper, connection);
         } else if (JsonEncoder.VTYPE_LIST.equals(variableType)) {
             val = decodeList((JSONArray) value, idMapper, connection);
         } else if (JsonEncoder.VTYPE_SET.equals(variableType)) {
@@ -111,30 +109,19 @@ public class JsonDecoder {
         return object;
     }
 
-    private static Map<String, Object> decodeMap(JSONObject jsonMap,
+    private static Map<Object, Object> decodeMap(JSONObject jsonMap,
             ConnectorMap idMapper, ApplicationConnection connection) {
-        HashMap<String, Object> map = new HashMap<String, Object>();
+        HashMap<Object, Object> map = new HashMap<Object, Object>();
         Iterator<String> it = jsonMap.keySet().iterator();
         while (it.hasNext()) {
             String key = it.next();
-            map.put(key,
-                    decodeValue((JSONArray) jsonMap.get(key), null, idMapper,
-                            connection));
-        }
-        return map;
-    }
-
-    private static Map<Connector, Object> decodeConnectorMap(
-            JSONObject jsonMap, ConnectorMap idMapper,
-            ApplicationConnection connection) {
-        HashMap<Connector, Object> map = new HashMap<Connector, Object>();
-        Iterator<String> it = jsonMap.keySet().iterator();
-        while (it.hasNext()) {
-            String connectorId = it.next();
-            Connector connector = idMapper.getConnector(connectorId);
-            map.put(connector,
-                    decodeValue((JSONArray) jsonMap.get(connectorId), null,
-                            idMapper, connection));
+            JSONArray encodedKey = (JSONArray) JSONParser.parseStrict(key);
+            JSONArray encodedValue = (JSONArray) jsonMap.get(key);
+            Object decodedKey = decodeValue(encodedKey, null, idMapper,
+                    connection);
+            Object decodedValue = decodeValue(encodedValue, null, idMapper,
+                    connection);
+            map.put(decodedKey, decodedValue);
         }
         return map;
     }
index 10b6f49a79a688e29d5970e00c92ac08727e88f4..f09536a9f7385ad7a246b542dfc1152c532a1ad3 100644 (file)
@@ -42,10 +42,6 @@ public class JsonEncoder {
     public static final String VTYPE_ARRAY = "a";
     public static final String VTYPE_STRINGARRAY = "S";
     public static final String VTYPE_MAP = "m";
-    // Hack to support Map<Connector,?>. Should be replaced by generic support
-    // for any object as key (#8602)
-    @Deprecated
-    public static final String VTYPE_MAP_CONNECTOR = "M";
     public static final String VTYPE_LIST = "L";
     public static final String VTYPE_SET = "q";
     public static final String VTYPE_NULL = "n";
@@ -93,28 +89,8 @@ public class JsonEncoder {
                 return encodeEnum(e, connectorMap, connection);
             }
         } else if (value instanceof Map) {
-            Map<Object, Object> map = (Map<Object, Object>) value;
-            JSONObject jsonMap = new JSONObject();
-            String type = VTYPE_MAP;
-            for (Object mapKey : map.keySet()) {
-                Object mapValue = map.get(mapKey);
-                if (mapKey instanceof Connector) {
-                    mapKey = ((Connector) mapKey).getConnectorId();
-                    type = VTYPE_MAP_CONNECTOR;
-                }
-
-                if (!(mapKey instanceof String)) {
-                    throw new RuntimeException(
-                            "Only Map<String,?> and Map<Connector,?> is currently supported."
-                                    + " Failed map used "
-                                    + mapKey.getClass().getName() + " as keys");
-                }
-                jsonMap.put(
-                        (String) mapKey,
-                        encode(mapValue, restrictToInternalTypes, connectorMap,
-                                connection));
-            }
-            return combineTypeAndValue(type, jsonMap);
+            return encodeMap((Map) value, restrictToInternalTypes,
+                    connectorMap, connection);
         } else if (value instanceof Connector) {
             Connector connector = (Connector) value;
             return combineTypeAndValue(VTYPE_CONNECTOR, new JSONString(
@@ -141,6 +117,21 @@ public class JsonEncoder {
         }
     }
 
+    private static JSONValue encodeMap(Map<Object, Object> map,
+            boolean restrictToInternalTypes, ConnectorMap connectorMap,
+            ApplicationConnection connection) {
+        JSONObject jsonMap = new JSONObject();
+        for (Object mapKey : map.keySet()) {
+            Object mapValue = map.get(mapKey);
+            JSONValue encodedKey = encode(mapKey, restrictToInternalTypes,
+                    connectorMap, connection);
+            JSONValue encodedValue = encode(mapValue, restrictToInternalTypes,
+                    connectorMap, connection);
+            jsonMap.put(encodedKey.toString(), encodedValue);
+        }
+        return combineTypeAndValue(VTYPE_MAP, jsonMap);
+    }
+
     private static JSONValue encodeEnum(Enum e, ConnectorMap connectorMap,
             ApplicationConnection connection) {
         return combineTypeAndValue(e.getClass().getName(),
index ed2bf66cedcbda70da5f7ef318e1f3739cbf9d85..e082eca47efc2da3fe29e3f7a3045ed51f159849 100644 (file)
@@ -61,6 +61,7 @@ public class JsonCodec implements Serializable {
         registerType(String[].class, JsonEncoder.VTYPE_STRINGARRAY);
         registerType(Object[].class, JsonEncoder.VTYPE_ARRAY);
         registerType(Map.class, JsonEncoder.VTYPE_MAP);
+        registerType(HashMap.class, JsonEncoder.VTYPE_MAP);
         registerType(List.class, JsonEncoder.VTYPE_LIST);
         registerType(Set.class, JsonEncoder.VTYPE_SET);
     }
@@ -201,12 +202,8 @@ public class JsonCodec implements Serializable {
         } else if (JsonEncoder.VTYPE_SET.equals(transportType)) {
             return decodeSet(targetType, restrictToInternalTypes,
                     (JSONArray) encodedJsonValue, application);
-        } else if (JsonEncoder.VTYPE_MAP_CONNECTOR.equals(transportType)) {
-            return decodeConnectorToObjectMap(targetType,
-                    restrictToInternalTypes, (JSONObject) encodedJsonValue,
-                    application);
         } else if (JsonEncoder.VTYPE_MAP.equals(transportType)) {
-            return decodeStringToObjectMap(targetType, restrictToInternalTypes,
+            return decodeMap(targetType, restrictToInternalTypes,
                     (JSONObject) encodedJsonValue, application);
         }
 
@@ -262,37 +259,22 @@ public class JsonCodec implements Serializable {
         return false;
     }
 
-    @Deprecated
-    private static Map<String, Object> decodeStringToObjectMap(Type targetType,
+    private static Map<Object, Object> decodeMap(Type targetType,
             boolean restrictToInternalTypes, JSONObject jsonMap,
             Application application) throws JSONException {
-        HashMap<String, Object> map = new HashMap<String, Object>();
+        HashMap<Object, Object> map = new HashMap<Object, Object>();
+
         Iterator<String> it = jsonMap.keys();
         while (it.hasNext()) {
             String key = it.next();
-            JSONArray encodedValueAndType = jsonMap.getJSONArray(key);
-            Object decodedChild = decodeChild(targetType,
-                    restrictToInternalTypes, 1, encodedValueAndType,
-                    application);
-            map.put(key, decodedChild);
-        }
-        return map;
-    }
+            JSONArray encodedKey = new JSONArray(key);
+            JSONArray encodedValue = jsonMap.getJSONArray(key);
 
-    @Deprecated
-    private static Map<Connector, Object> decodeConnectorToObjectMap(
-            Type targetType, boolean restrictToInternalTypes,
-            JSONObject jsonMap, Application application) throws JSONException {
-        HashMap<Connector, Object> map = new HashMap<Connector, Object>();
-        Iterator<String> it = jsonMap.keys();
-        while (it.hasNext()) {
-            String connectorId = it.next();
-            Connector connector = application.getConnector(connectorId);
-            JSONArray encodedValueAndType = jsonMap.getJSONArray(connectorId);
-            Object decodedChild = decodeChild(targetType,
-                    restrictToInternalTypes, 1, encodedValueAndType,
-                    application);
-            map.put(connector, decodedChild);
+            Object decodedKey = decodeParametrizedType(targetType,
+                    restrictToInternalTypes, 0, encodedKey, application);
+            Object decodedValue = decodeParametrizedType(targetType,
+                    restrictToInternalTypes, 1, encodedValue, application);
+            map.put(decodedKey, decodedValue);
         }
         return map;
     }
@@ -308,7 +290,7 @@ public class JsonCodec implements Serializable {
      * @return
      * @throws JSONException
      */
-    private static Object decodeChild(Type targetType,
+    private static Object decodeParametrizedType(Type targetType,
             boolean restrictToInternalTypes, int typeIndex,
             JSONArray encodedValueAndType, Application application)
             throws JSONException {
@@ -353,7 +335,7 @@ public class JsonCodec implements Serializable {
         for (int i = 0; i < jsonArray.length(); ++i) {
             // each entry always has two elements: type and value
             JSONArray encodedValueAndType = jsonArray.getJSONArray(i);
-            Object decodedChild = decodeChild(targetType,
+            Object decodedChild = decodeParametrizedType(targetType,
                     restrictToInternalTypes, 0, encodedValueAndType,
                     application);
             list.add(decodedChild);
@@ -381,7 +363,7 @@ public class JsonCodec implements Serializable {
      * @return the name to be used or null if both getter and setter are not
      *         found.
      */
-    private static String getTransportFieldName(PropertyDescriptor pd) {
+    static String getTransportFieldName(PropertyDescriptor pd) {
         if (pd.getReadMethod() == null || pd.getWriteMethod() == null) {
             return null;
         }
@@ -476,16 +458,9 @@ public class JsonCodec implements Serializable {
             JSONArray jsonArray = encodeArrayContents(array, application);
             return combineTypeAndValue(JsonEncoder.VTYPE_ARRAY, jsonArray);
         } else if (value instanceof Map) {
-            Map<Object, Object> map = (Map<Object, Object>) value;
-            JSONObject jsonMap = encodeMapContents(map, application);
-            // Hack to support Connector as map key. Should be fixed by #
-            if (!map.isEmpty()
-                    && map.keySet().iterator().next() instanceof Connector) {
-                return combineTypeAndValue(JsonEncoder.VTYPE_MAP_CONNECTOR,
-                        jsonMap);
-            } else {
-                return combineTypeAndValue(JsonEncoder.VTYPE_MAP, jsonMap);
-            }
+            JSONObject jsonMap = encodeMap(valueType, (Map<?, ?>) value,
+                    application);
+            return combineTypeAndValue(JsonEncoder.VTYPE_MAP, jsonMap);
         } else if (value instanceof Connector) {
             Connector connector = (Connector) value;
             if (value instanceof Component
@@ -611,22 +586,24 @@ public class JsonCodec implements Serializable {
         }
     }
 
-    private static JSONObject encodeMapContents(Map<Object, Object> map,
+    private static JSONObject encodeMap(Type mapType, Map<?, ?> map,
             Application application) throws JSONException {
+        Type keyType, valueType;
+
+        if (mapType instanceof ParameterizedType) {
+            keyType = ((ParameterizedType) mapType).getActualTypeArguments()[0];
+            valueType = ((ParameterizedType) mapType).getActualTypeArguments()[1];
+        } else {
+            throw new JSONException("Map is missing generics");
+        }
+
         JSONObject jsonMap = new JSONObject();
         for (Object mapKey : map.keySet()) {
             Object mapValue = map.get(mapKey);
-
-            if (mapKey instanceof ClientConnector) {
-                mapKey = ((ClientConnector) mapKey).getConnectorId();
-            }
-            if (!(mapKey instanceof String)) {
-                throw new JSONException(
-                        "Only maps with String/Connector keys are currently supported (#8602)");
-            }
-
-            jsonMap.put((String) mapKey,
-                    encode(mapValue, null, null, application));
+            JSONArray encodedKey = encode(mapKey, null, keyType, application);
+            JSONArray encodedValue = encode(mapValue, null, valueType,
+                    application);
+            jsonMap.put(encodedKey.toString(), encodedValue);
         }
         return jsonMap;
     }
diff --git a/tests/client-side/com/vaadin/terminal/gwt/server/JSONSerializerTest.java b/tests/client-side/com/vaadin/terminal/gwt/server/JSONSerializerTest.java
new file mode 100644 (file)
index 0000000..926f026
--- /dev/null
@@ -0,0 +1,146 @@
+package com.vaadin.terminal.gwt.server;
+
+/*
+ @VaadinApache2LicenseForJavaFiles@
+ */
+import java.beans.BeanInfo;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+import com.vaadin.external.json.JSONArray;
+import com.vaadin.terminal.gwt.client.communication.JsonDecoder;
+import com.vaadin.terminal.gwt.client.communication.JsonEncoder;
+import com.vaadin.terminal.gwt.client.ui.splitpanel.AbstractSplitPanelState;
+
+/**
+ * Tests for {@link JsonCodec}, {@link JsonEncoder}, {@link JsonDecoder}
+ * 
+ * @author Vaadin Ltd
+ * @version @VERSION@
+ * @since 7.0
+ * 
+ */
+public class JSONSerializerTest extends TestCase {
+    HashMap<String, AbstractSplitPanelState> stringToStateMap;
+    HashMap<AbstractSplitPanelState, String> stateToStringMap;
+
+    public void testStringToBeanMapSerialization() throws Exception {
+        Type mapType = getClass().getDeclaredField("stringToStateMap")
+                .getGenericType();
+        stringToStateMap = new HashMap<String, AbstractSplitPanelState>();
+        AbstractSplitPanelState s = new AbstractSplitPanelState();
+        AbstractSplitPanelState s2 = new AbstractSplitPanelState();
+        s.setCaption("State 1");
+        s.setDebugId("foo");
+        s2.setCaption("State 2");
+        s2.setDebugId("bar");
+        stringToStateMap.put("string - state 1", s);
+        stringToStateMap.put("String - state 2", s2);
+
+        JSONArray encodedMap = JsonCodec.encode(stringToStateMap, null,
+                mapType, null);
+
+        ensureDecodedCorrectly(stringToStateMap, encodedMap, mapType);
+    }
+
+    public void testBeanToStringMapSerialization() throws Exception {
+        Type mapType = getClass().getDeclaredField("stateToStringMap")
+                .getGenericType();
+        stateToStringMap = new HashMap<AbstractSplitPanelState, String>();
+        AbstractSplitPanelState s = new AbstractSplitPanelState();
+        AbstractSplitPanelState s2 = new AbstractSplitPanelState();
+        s.setCaption("State 1");
+        s2.setCaption("State 2");
+        stateToStringMap.put(s, "string - state 1");
+        stateToStringMap.put(s2, "String - state 2");
+
+        JSONArray encodedMap = JsonCodec.encode(stateToStringMap, null,
+                mapType, null);
+
+        ensureDecodedCorrectly(stateToStringMap, encodedMap, mapType);
+    }
+
+    private void ensureDecodedCorrectly(Object original, JSONArray encoded,
+            Type type) throws Exception {
+        Object serverSideDecoded = JsonCodec.decodeInternalOrCustomType(type,
+                encoded, null);
+        assertTrue("Server decoded", equals(original, serverSideDecoded));
+
+        // Object clientSideDecoded = JsonDecoder.decodeValue(
+        // (com.google.gwt.json.client.JSONArray) JSONParser
+        // .parseStrict(encoded.toString()), null, null, null);
+        // assertTrue("Client decoded",
+        // equals(original, clientSideDecoded));
+
+    }
+
+    private boolean equals(Object o1, Object o2) throws Exception {
+        if (o1 == null) {
+            return (o2 == null);
+        }
+        if (o2 == null) {
+            return false;
+        }
+
+        if (o1 instanceof Map) {
+            if (!(o2 instanceof Map)) {
+                return false;
+            }
+            return equalsMap((Map) o1, (Map) o2);
+        }
+
+        if (o1.getClass() != o2.getClass()) {
+            return false;
+        }
+
+        if (o1 instanceof Collection || o1 instanceof Number
+                || o1 instanceof String) {
+            return o1.equals(o2);
+        }
+
+        return equalsBean(o1, o2);
+    }
+
+    private boolean equalsBean(Object o1, Object o2) throws Exception {
+        BeanInfo beanInfo = Introspector.getBeanInfo(o1.getClass());
+        for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) {
+            String fieldName = JsonCodec.getTransportFieldName(pd);
+            if (fieldName == null) {
+                continue;
+            }
+
+            Object c1 = pd.getReadMethod().invoke(o1);
+            Object c2 = pd.getReadMethod().invoke(o2);
+            if (!equals(c1, c2)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private boolean equalsMap(Map o1, Map o2) throws Exception {
+        for (Object key1 : o1.keySet()) {
+            Object key2 = key1;
+            if (!(o2.containsKey(key2))) {
+                // Try to fins a key that is equal
+                for (Object k2 : o2.keySet()) {
+                    if (equals(key1, k2)) {
+                        key2 = k2;
+                        break;
+                    }
+                }
+            }
+            if (!equals(o1.get(key1), o2.get(key2))) {
+                return false;
+            }
+
+        }
+        return true;
+    }
+}