Browse Source

Change map serialization to use same scheme as GWT AutoBean (#8602)

tags/7.0.0.alpha3
Leif Åstrand 12 years ago
parent
commit
351e04dd8e

+ 72
- 14
src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java View 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;
}


+ 75
- 13
src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java View 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;
}


+ 143
- 22
src/com/vaadin/terminal/gwt/server/JsonCodec.java View 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;
}


Loading…
Cancel
Save