Browse Source

Added support for map keys of any type (#8602)

tags/7.0.0.alpha3
Artur Signell 12 years ago
parent
commit
71e30eb3ef

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

+ 17
- 26
src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java View 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(),

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

+ 146
- 0
tests/client-side/com/vaadin/terminal/gwt/server/JSONSerializerTest.java View File

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

Loading…
Cancel
Save