import com.google.gwt.json.client.JSONArray; | import com.google.gwt.json.client.JSONArray; | ||||
import com.google.gwt.json.client.JSONObject; | 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.JSONString; | ||||
import com.google.gwt.json.client.JSONValue; | import com.google.gwt.json.client.JSONValue; | ||||
import com.vaadin.terminal.gwt.client.ApplicationConnection; | 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.ConnectorMap; | ||||
import com.vaadin.terminal.gwt.client.ServerConnector; | import com.vaadin.terminal.gwt.client.ServerConnector; | ||||
val = decodeArray((JSONArray) value, idMapper, connection); | val = decodeArray((JSONArray) value, idMapper, connection); | ||||
} else if (JsonEncoder.VTYPE_MAP.equals(variableType)) { | } else if (JsonEncoder.VTYPE_MAP.equals(variableType)) { | ||||
val = decodeMap((JSONObject) value, idMapper, connection); | 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)) { | } else if (JsonEncoder.VTYPE_LIST.equals(variableType)) { | ||||
val = decodeList((JSONArray) value, idMapper, connection); | val = decodeList((JSONArray) value, idMapper, connection); | ||||
} else if (JsonEncoder.VTYPE_SET.equals(variableType)) { | } else if (JsonEncoder.VTYPE_SET.equals(variableType)) { | ||||
return object; | return object; | ||||
} | } | ||||
private static Map<String, Object> decodeMap(JSONObject jsonMap, | |||||
private static Map<Object, Object> decodeMap(JSONObject jsonMap, | |||||
ConnectorMap idMapper, ApplicationConnection connection) { | 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(); | Iterator<String> it = jsonMap.keySet().iterator(); | ||||
while (it.hasNext()) { | while (it.hasNext()) { | ||||
String key = it.next(); | 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; | return map; | ||||
} | } |
public static final String VTYPE_ARRAY = "a"; | public static final String VTYPE_ARRAY = "a"; | ||||
public static final String VTYPE_STRINGARRAY = "S"; | public static final String VTYPE_STRINGARRAY = "S"; | ||||
public static final String VTYPE_MAP = "m"; | 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_LIST = "L"; | ||||
public static final String VTYPE_SET = "q"; | public static final String VTYPE_SET = "q"; | ||||
public static final String VTYPE_NULL = "n"; | public static final String VTYPE_NULL = "n"; | ||||
return encodeEnum(e, connectorMap, connection); | return encodeEnum(e, connectorMap, connection); | ||||
} | } | ||||
} else if (value instanceof Map) { | } 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) { | } else if (value instanceof Connector) { | ||||
Connector connector = (Connector) value; | Connector connector = (Connector) value; | ||||
return combineTypeAndValue(VTYPE_CONNECTOR, new JSONString( | return combineTypeAndValue(VTYPE_CONNECTOR, new JSONString( | ||||
} | } | ||||
} | } | ||||
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, | private static JSONValue encodeEnum(Enum e, ConnectorMap connectorMap, | ||||
ApplicationConnection connection) { | ApplicationConnection connection) { | ||||
return combineTypeAndValue(e.getClass().getName(), | return combineTypeAndValue(e.getClass().getName(), |
registerType(String[].class, JsonEncoder.VTYPE_STRINGARRAY); | registerType(String[].class, JsonEncoder.VTYPE_STRINGARRAY); | ||||
registerType(Object[].class, JsonEncoder.VTYPE_ARRAY); | registerType(Object[].class, JsonEncoder.VTYPE_ARRAY); | ||||
registerType(Map.class, JsonEncoder.VTYPE_MAP); | registerType(Map.class, JsonEncoder.VTYPE_MAP); | ||||
registerType(HashMap.class, JsonEncoder.VTYPE_MAP); | |||||
registerType(List.class, JsonEncoder.VTYPE_LIST); | registerType(List.class, JsonEncoder.VTYPE_LIST); | ||||
registerType(Set.class, JsonEncoder.VTYPE_SET); | registerType(Set.class, JsonEncoder.VTYPE_SET); | ||||
} | } | ||||
} else if (JsonEncoder.VTYPE_SET.equals(transportType)) { | } else if (JsonEncoder.VTYPE_SET.equals(transportType)) { | ||||
return decodeSet(targetType, restrictToInternalTypes, | return decodeSet(targetType, restrictToInternalTypes, | ||||
(JSONArray) encodedJsonValue, application); | (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)) { | } else if (JsonEncoder.VTYPE_MAP.equals(transportType)) { | ||||
return decodeStringToObjectMap(targetType, restrictToInternalTypes, | |||||
return decodeMap(targetType, restrictToInternalTypes, | |||||
(JSONObject) encodedJsonValue, application); | (JSONObject) encodedJsonValue, application); | ||||
} | } | ||||
return false; | return false; | ||||
} | } | ||||
@Deprecated | |||||
private static Map<String, Object> decodeStringToObjectMap(Type targetType, | |||||
private static Map<Object, Object> decodeMap(Type targetType, | |||||
boolean restrictToInternalTypes, JSONObject jsonMap, | boolean restrictToInternalTypes, JSONObject jsonMap, | ||||
Application application) throws JSONException { | 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(); | Iterator<String> it = jsonMap.keys(); | ||||
while (it.hasNext()) { | while (it.hasNext()) { | ||||
String key = it.next(); | 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; | return map; | ||||
} | } | ||||
* @return | * @return | ||||
* @throws JSONException | * @throws JSONException | ||||
*/ | */ | ||||
private static Object decodeChild(Type targetType, | |||||
private static Object decodeParametrizedType(Type targetType, | |||||
boolean restrictToInternalTypes, int typeIndex, | boolean restrictToInternalTypes, int typeIndex, | ||||
JSONArray encodedValueAndType, Application application) | JSONArray encodedValueAndType, Application application) | ||||
throws JSONException { | throws JSONException { | ||||
for (int i = 0; i < jsonArray.length(); ++i) { | for (int i = 0; i < jsonArray.length(); ++i) { | ||||
// each entry always has two elements: type and value | // each entry always has two elements: type and value | ||||
JSONArray encodedValueAndType = jsonArray.getJSONArray(i); | JSONArray encodedValueAndType = jsonArray.getJSONArray(i); | ||||
Object decodedChild = decodeChild(targetType, | |||||
Object decodedChild = decodeParametrizedType(targetType, | |||||
restrictToInternalTypes, 0, encodedValueAndType, | restrictToInternalTypes, 0, encodedValueAndType, | ||||
application); | application); | ||||
list.add(decodedChild); | list.add(decodedChild); | ||||
* @return the name to be used or null if both getter and setter are not | * @return the name to be used or null if both getter and setter are not | ||||
* found. | * found. | ||||
*/ | */ | ||||
private static String getTransportFieldName(PropertyDescriptor pd) { | |||||
static String getTransportFieldName(PropertyDescriptor pd) { | |||||
if (pd.getReadMethod() == null || pd.getWriteMethod() == null) { | if (pd.getReadMethod() == null || pd.getWriteMethod() == null) { | ||||
return null; | return null; | ||||
} | } | ||||
JSONArray jsonArray = encodeArrayContents(array, application); | JSONArray jsonArray = encodeArrayContents(array, application); | ||||
return combineTypeAndValue(JsonEncoder.VTYPE_ARRAY, jsonArray); | return combineTypeAndValue(JsonEncoder.VTYPE_ARRAY, jsonArray); | ||||
} else if (value instanceof Map) { | } 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) { | } else if (value instanceof Connector) { | ||||
Connector connector = (Connector) value; | Connector connector = (Connector) value; | ||||
if (value instanceof Component | if (value instanceof Component | ||||
} | } | ||||
} | } | ||||
private static JSONObject encodeMapContents(Map<Object, Object> map, | |||||
private static JSONObject encodeMap(Type mapType, Map<?, ?> map, | |||||
Application application) throws JSONException { | 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(); | JSONObject jsonMap = new JSONObject(); | ||||
for (Object mapKey : map.keySet()) { | for (Object mapKey : map.keySet()) { | ||||
Object mapValue = map.get(mapKey); | 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; | return jsonMap; | ||||
} | } |
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; | |||||
} | |||||
} |