@@ -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; | |||
} | |||
@@ -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; | |||
} | |||
@@ -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; | |||
} | |||