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;
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);
}
}
- 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;
}
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;
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;
}
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;
(JSONArray) encodedJsonValue, application);
} else if (JsonEncoder.VTYPE_MAP.equals(transportType)) {
return decodeMap(targetType, restrictToInternalTypes,
- (JSONObject) encodedJsonValue, application);
+ encodedJsonValue, application);
}
// Arrays
}
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;
}
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) {
}
}
- private static JSONObject encodeMap(Type mapType, Map<?, ?> map,
+ private static Object encodeMap(Type mapType, Map<?, ?> map,
Application application) throws JSONException {
Type keyType, valueType;
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;
}