@@ -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; | |||
} |
@@ -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(), |
@@ -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; | |||
} |
@@ -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; | |||
} | |||
} |