From: Leif Åstrand Date: Tue, 5 Jun 2012 12:10:57 +0000 (+0300) Subject: Change map serialization to use same scheme as GWT AutoBean (#8602) X-Git-Tag: 7.0.0.alpha3~200 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=351e04dd8e07d41b23231783910ba41852c62145;p=vaadin-framework.git Change map serialization to use same scheme as GWT AutoBean (#8602) --- diff --git a/src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java b/src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java index c7b33a70db..1459d8ee7d 100644 --- a/src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java +++ b/src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java @@ -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 decodeMap(Type type, JSONObject jsonMap, + private static Map decodeMap(Type type, JSONValue jsonMap, ApplicationConnection connection) { - HashMap map = new HashMap(); - Iterator 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(); + } + } + + 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 decodeObjectMap(Type keyType, + Type valueType, JSONValue jsonValue, + ApplicationConnection connection) { + Map map = new HashMap(); + + 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 decodeConnectorMap(Type valueType, + JSONValue jsonValue, ApplicationConnection connection) { + Map map = new HashMap(); + + 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 decodeStringMap(Type valueType, + JSONValue jsonValue, ApplicationConnection connection) { + Map map = new HashMap(); + + JSONObject jsonMap = (JSONObject) jsonValue; + + for (String key : jsonMap.keySet()) { + Object value = decodeValue(valueType, jsonMap.get(key), null, + connection); + map.put(key, value); + } + return map; } diff --git a/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java b/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java index 541dc631c6..df095833dc 100644 --- a/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java +++ b/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java @@ -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 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 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 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 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; } diff --git a/src/com/vaadin/terminal/gwt/server/JsonCodec.java b/src/com/vaadin/terminal/gwt/server/JsonCodec.java index e76a3d3f3a..feaf78d2e8 100644 --- a/src/com/vaadin/terminal/gwt/server/JsonCodec.java +++ b/src/com/vaadin/terminal/gwt/server/JsonCodec.java @@ -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 decodeMap(Type targetType, - boolean restrictToInternalTypes, JSONObject jsonMap, + boolean restrictToInternalTypes, Object jsonMap, Application application) throws JSONException { - HashMap map = new HashMap(); + 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(); + } + } - Iterator 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 decodeObjectMap(Type keyType, + Type valueType, JSONArray jsonMap, Application application) + throws JSONException { + Map map = new HashMap(); + + 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 decodeConnectorMap(Type valueType, + JSONObject jsonMap, Application application) throws JSONException { + Map map = new HashMap(); + + 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 decodeStringMap(Type valueType, + JSONObject jsonMap, Application application) throws JSONException { + Map map = new HashMap(); + + 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; }