From a2f79d1d362b0d535655bd9309fd85a2b03d92d1 Mon Sep 17 00:00:00 2001 From: Henri Sara Date: Mon, 30 Jan 2012 15:47:49 +0200 Subject: [PATCH] Basic JSON encoding and decoding utilities from server to client (#8304) Also includes minor refactoring in preparation for shared state support. --- .../communication}/JsonDecoder.java | 62 +++-- .../gwt/client/communication/JsonEncoder.java | 3 - .../server/AbstractCommunicationManager.java | 43 ++-- .../vaadin/terminal/gwt/server/JsonCodec.java | 217 ++++++++++++++++++ .../gwt/server/PaintableIdMapper.java | 10 + 5 files changed, 280 insertions(+), 55 deletions(-) rename src/com/vaadin/terminal/gwt/{server => client/communication}/JsonDecoder.java (63%) create mode 100644 src/com/vaadin/terminal/gwt/server/JsonCodec.java diff --git a/src/com/vaadin/terminal/gwt/server/JsonDecoder.java b/src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java similarity index 63% rename from src/com/vaadin/terminal/gwt/server/JsonDecoder.java rename to src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java index 35cbe52b15..e7fea08bbe 100644 --- a/src/com/vaadin/terminal/gwt/server/JsonDecoder.java +++ b/src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java @@ -2,50 +2,49 @@ @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.terminal.gwt.server; +package com.vaadin.terminal.gwt.client.communication; -import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; -import com.vaadin.external.json.JSONArray; -import com.vaadin.external.json.JSONException; -import com.vaadin.external.json.JSONObject; -import com.vaadin.terminal.Paintable; -import com.vaadin.terminal.gwt.client.communication.JsonEncoder; +import com.google.gwt.json.client.JSONArray; +import com.google.gwt.json.client.JSONObject; +import com.google.gwt.json.client.JSONString; +import com.vaadin.terminal.gwt.client.VPaintable; +import com.vaadin.terminal.gwt.client.VPaintableMap; /** - * Decoder for converting RPC parameters and other values from JSON in transfer - * between the client and the server. + * Client side decoder for converting shared state and other values from JSON + * received from the server. * - * TODO support bi-directional codec functionality + * Currently, basic data types as well as Map, String[] and Object[] are + * supported, where maps and Object[] can contain other supported data types. + * + * TODO extensible type support * * @since 7.0 */ -public class JsonDecoder implements Serializable { - +public class JsonDecoder { /** * Convert a JSON array with two elements (type and value) into a - * server-side type, recursively if necessary. + * client-side type, recursively if necessary. * * @param value * JSON array with two elements * @param idMapper - * mapper from paintable ID to {@link Paintable} objects + * mapper between paintable ID and {@link VPaintable} objects * @return converted value (does not contain JSON types) - * @throws JSONException - * if the conversion fails */ public static Object convertVariableValue(JSONArray value, - PaintableIdMapper idMapper) throws JSONException { - return convertVariableValue(value.getString(0).charAt(0), value.get(1), - idMapper); + VPaintableMap idMapper) { + return convertVariableValue(((JSONString) value.get(0)).stringValue() + .charAt(0), value.get(1), idMapper); } private static Object convertVariableValue(char variableType, Object value, - PaintableIdMapper idMapper) throws JSONException { + VPaintableMap idMapper) { Object val = null; // TODO type checks etc. switch (variableType) { @@ -90,33 +89,32 @@ public class JsonDecoder implements Serializable { return val; } - private static Object convertMap(JSONObject jsonMap, - PaintableIdMapper idMapper) throws JSONException { + private static Object convertMap(JSONObject jsonMap, VPaintableMap idMapper) { HashMap map = new HashMap(); - Iterator it = jsonMap.keys(); + Iterator it = jsonMap.keySet().iterator(); while (it.hasNext()) { String key = it.next(); map.put(key, - convertVariableValue(jsonMap.getJSONArray(key), idMapper)); + convertVariableValue((JSONArray) jsonMap.get(key), idMapper)); } return map; } - private static String[] convertStringArray(JSONArray jsonArray) - throws JSONException { - List tokens = new ArrayList(); - for (int i = 0; i < jsonArray.length(); ++i) { - tokens.add(jsonArray.getString(i)); + private static String[] convertStringArray(JSONArray jsonArray) { + int size = jsonArray.size(); + List tokens = new ArrayList(size); + for (int i = 0; i < size; ++i) { + tokens.add(String.valueOf(jsonArray.get(i))); } return tokens.toArray(new String[tokens.size()]); } private static Object convertArray(JSONArray jsonArray, - PaintableIdMapper idMapper) throws JSONException { + VPaintableMap idMapper) { List tokens = new ArrayList(); - for (int i = 0; i < jsonArray.length(); ++i) { + for (int i = 0; i < jsonArray.size(); ++i) { // each entry always has two elements: type and value - JSONArray entryArray = jsonArray.getJSONArray(i); + JSONArray entryArray = (JSONArray) jsonArray.get(i); tokens.add(convertVariableValue(entryArray, idMapper)); } return tokens.toArray(new Object[tokens.size()]); diff --git a/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java b/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java index c2656764fe..4b26d5939f 100644 --- a/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java +++ b/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java @@ -22,8 +22,6 @@ import com.vaadin.terminal.gwt.client.VPaintableMap; * Currently, basic data types as well as Map, String[] and Object[] are * supported, where maps and Object[] can contain other supported data types. * - * TODO support bi-directional codec functionality - * * TODO extensible type support * * @since 7.0 @@ -81,7 +79,6 @@ public class JsonEncoder { return combineTypeAndValue(VTYPE_ARRAY, jsonArray); } else if (value instanceof Map) { Map map = (Map) value; - // TODO implement; types for each element JSONObject jsonMap = new JSONObject(); for (String mapKey : map.keySet()) { // TODO handle object graph loops? diff --git a/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java b/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java index 8addc8fbff..0d25b48422 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java @@ -79,9 +79,9 @@ import com.vaadin.ui.Root; * A server side component sends its state to the client in a paint request (see * {@link Paintable} and {@link PaintTarget} on the server side). The client * widget receives these paint requests as calls to - * {@link com.vaadin.terminal.gwt.client.VPaintableWidget#updateFromUIDL()}. The client - * component communicates back to the server by sending a list of variable - * changes (see {@link ApplicationConnection#updateVariable()} and + * {@link com.vaadin.terminal.gwt.client.VPaintableWidget#updateFromUIDL()}. The + * client component communicates back to the server by sending a list of + * variable changes (see {@link ApplicationConnection#updateVariable()} and * {@link VariableOwner#changeVariables(Object, Map)}). * * TODO Document better! @@ -741,22 +741,8 @@ public abstract class AbstractCommunicationManager implements public void writeUidlResponce(boolean repaintAll, final PrintWriter outWriter, Root root, boolean analyzeLayouts) throws PaintException { - outWriter.print("\"changes\":["); - ArrayList paintables = null; - List invalidComponentRelativeSizes = null; - - JsonPaintTarget paintTarget = new JsonPaintTarget(this, outWriter, - !repaintAll); - OpenWindowCache windowCache = currentlyOpenWindowsInClient.get(Integer - .valueOf(root.getRootId())); - if (windowCache == null) { - windowCache = new OpenWindowCache(); - currentlyOpenWindowsInClient.put(Integer.valueOf(root.getRootId()), - windowCache); - } - // Paints components if (repaintAll) { paintables = new ArrayList(); @@ -772,7 +758,7 @@ public abstract class AbstractCommunicationManager implements /* * TODO figure out if we could move this beyond the painting phase, * "respond as fast as possible, then do the cleanup". Beware of - * painting the dirty detatched components. + * painting the dirty detached components. */ for (Iterator it = paintableIdMap.keySet().iterator(); it .hasNext();) { @@ -792,8 +778,8 @@ public abstract class AbstractCommunicationManager implements } paintables = getDirtyVisibleComponents(root); } - if (paintables != null) { + if (paintables != null) { // We need to avoid painting children before parent. // This is ensured by ordering list by depth in component // tree @@ -820,6 +806,23 @@ public abstract class AbstractCommunicationManager implements return 0; } }); + } + + outWriter.print("\"changes\":["); + + List invalidComponentRelativeSizes = null; + + JsonPaintTarget paintTarget = new JsonPaintTarget(this, outWriter, + !repaintAll); + OpenWindowCache windowCache = currentlyOpenWindowsInClient.get(Integer + .valueOf(root.getRootId())); + if (windowCache == null) { + windowCache = new OpenWindowCache(); + currentlyOpenWindowsInClient.put(Integer.valueOf(root.getRootId()), + windowCache); + } + + if (paintables != null) { for (final Iterator i = paintables.iterator(); i .hasNext();) { @@ -1374,7 +1377,7 @@ public abstract class AbstractCommunicationManager implements JSONArray parametersJson = invocationJson.getJSONArray(3); Object[] parameters = new Object[parametersJson.length()]; for (int j = 0; j < parametersJson.length(); ++j) { - parameters[j] = JsonDecoder.convertVariableValue( + parameters[j] = JsonCodec.convertVariableValue( parametersJson.getJSONArray(j), this); } MethodInvocation invocation = new MethodInvocation(paintableId, diff --git a/src/com/vaadin/terminal/gwt/server/JsonCodec.java b/src/com/vaadin/terminal/gwt/server/JsonCodec.java new file mode 100644 index 0000000000..dfe4491400 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/server/JsonCodec.java @@ -0,0 +1,217 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.server; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import com.vaadin.external.json.JSONArray; +import com.vaadin.external.json.JSONException; +import com.vaadin.external.json.JSONObject; +import com.vaadin.terminal.Paintable; +import com.vaadin.terminal.gwt.client.communication.JsonEncoder; + +/** + * Decoder for converting RPC parameters and other values from JSON in transfer + * between the client and the server and vice versa. + * + * @since 7.0 + */ +public class JsonCodec implements Serializable { + + /** + * Convert a JSON array with two elements (type and value) into a + * server-side type, recursively if necessary. + * + * @param value + * JSON array with two elements + * @param idMapper + * mapper between paintable ID and {@link Paintable} objects + * @return converted value (does not contain JSON types) + * @throws JSONException + * if the conversion fails + */ + public static Object convertVariableValue(JSONArray value, + PaintableIdMapper idMapper) throws JSONException { + return convertVariableValue(value.getString(0).charAt(0), value.get(1), + idMapper); + } + + private static Object convertVariableValue(char variableType, Object value, + PaintableIdMapper idMapper) throws JSONException { + Object val = null; + // TODO type checks etc. + switch (variableType) { + case JsonEncoder.VTYPE_ARRAY: + val = convertArray((JSONArray) value, idMapper); + break; + case JsonEncoder.VTYPE_MAP: + val = convertMap((JSONObject) value, idMapper); + break; + case JsonEncoder.VTYPE_STRINGARRAY: + val = convertStringArray((JSONArray) value); + break; + case JsonEncoder.VTYPE_STRING: + val = value; + break; + case JsonEncoder.VTYPE_INTEGER: + // TODO handle properly + val = Integer.valueOf(String.valueOf(value)); + break; + case JsonEncoder.VTYPE_LONG: + // TODO handle properly + val = Long.valueOf(String.valueOf(value)); + break; + case JsonEncoder.VTYPE_FLOAT: + // TODO handle properly + val = Float.valueOf(String.valueOf(value)); + break; + case JsonEncoder.VTYPE_DOUBLE: + // TODO handle properly + val = Double.valueOf(String.valueOf(value)); + break; + case JsonEncoder.VTYPE_BOOLEAN: + // TODO handle properly + val = Boolean.valueOf(String.valueOf(value)); + break; + case JsonEncoder.VTYPE_PAINTABLE: + // TODO handle properly + val = idMapper.getPaintable(String.valueOf(value)); + break; + } + + return val; + } + + private static Object convertMap(JSONObject jsonMap, + PaintableIdMapper idMapper) throws JSONException { + HashMap map = new HashMap(); + Iterator it = jsonMap.keys(); + while (it.hasNext()) { + String key = it.next(); + map.put(key, + convertVariableValue(jsonMap.getJSONArray(key), idMapper)); + } + return map; + } + + private static String[] convertStringArray(JSONArray jsonArray) + throws JSONException { + int length = jsonArray.length(); + List tokens = new ArrayList(length); + for (int i = 0; i < length; ++i) { + tokens.add(jsonArray.getString(i)); + } + return tokens.toArray(new String[tokens.size()]); + } + + private static Object convertArray(JSONArray jsonArray, + PaintableIdMapper idMapper) throws JSONException { + List tokens = new ArrayList(); + for (int i = 0; i < jsonArray.length(); ++i) { + // each entry always has two elements: type and value + JSONArray entryArray = jsonArray.getJSONArray(i); + tokens.add(convertVariableValue(entryArray, idMapper)); + } + return tokens.toArray(new Object[tokens.size()]); + } + + /** + * Encode a value to a JSON representation for transport from the client to + * the server. + * + * @param value + * value to convert + * @param idMapper + * mapper between paintable ID and {@link Paintable} objects + * @return JSON representation of the value + * @throws JSONException + * if encoding a value fails (e.g. NaN or infinite number) + */ + public static JSONArray encode(Object value, PaintableIdMapper idMapper) + throws JSONException { + if (null == value) { + // TODO as undefined type? + return combineTypeAndValue(JsonEncoder.VTYPE_UNDEFINED, + JSONObject.NULL); + } else if (value instanceof String[]) { + String[] array = (String[]) value; + JSONArray jsonArray = new JSONArray(); + for (int i = 0; i < array.length; ++i) { + jsonArray.put(array[i]); + } + return combineTypeAndValue(JsonEncoder.VTYPE_STRINGARRAY, jsonArray); + } else if (value instanceof String) { + return combineTypeAndValue(JsonEncoder.VTYPE_STRING, value); + } else if (value instanceof Boolean) { + return combineTypeAndValue(JsonEncoder.VTYPE_BOOLEAN, value); + } else if (value instanceof Object[]) { + Object[] array = (Object[]) value; + JSONArray jsonArray = new JSONArray(); + for (int i = 0; i < array.length; ++i) { + // TODO handle object graph loops? + jsonArray.put(encode(array[i], idMapper)); + } + return combineTypeAndValue(JsonEncoder.VTYPE_ARRAY, jsonArray); + } else if (value instanceof Map) { + Map map = (Map) value; + JSONObject jsonMap = new JSONObject(); + for (String mapKey : map.keySet()) { + // TODO handle object graph loops? + Object mapValue = map.get(mapKey); + jsonMap.put(mapKey, encode(mapValue, idMapper)); + } + return combineTypeAndValue(JsonEncoder.VTYPE_MAP, jsonMap); + } else if (value instanceof Paintable) { + Paintable paintable = (Paintable) value; + return combineTypeAndValue(JsonEncoder.VTYPE_PAINTABLE, + idMapper.getPaintableId(paintable)); + } else { + return combineTypeAndValue(getTransportType(value), + String.valueOf(value)); + } + } + + private static JSONArray combineTypeAndValue(char type, Object value) { + JSONArray outerArray = new JSONArray(); + outerArray.put(String.valueOf(type)); + outerArray.put(value); + return outerArray; + } + + private static char getTransportType(Object value) { + if (value instanceof String) { + return JsonEncoder.VTYPE_STRING; + } else if (value instanceof Paintable) { + return JsonEncoder.VTYPE_PAINTABLE; + } else if (value instanceof Boolean) { + return JsonEncoder.VTYPE_BOOLEAN; + } else if (value instanceof Integer) { + return JsonEncoder.VTYPE_INTEGER; + } else if (value instanceof Float) { + return JsonEncoder.VTYPE_FLOAT; + } else if (value instanceof Double) { + return JsonEncoder.VTYPE_DOUBLE; + } else if (value instanceof Long) { + return JsonEncoder.VTYPE_LONG; + } else if (value instanceof Enum) { + // transported as string representation + return JsonEncoder.VTYPE_STRING; + } else if (value instanceof String[]) { + return JsonEncoder.VTYPE_STRINGARRAY; + } else if (value instanceof Object[]) { + return JsonEncoder.VTYPE_ARRAY; + } else if (value instanceof Map) { + return JsonEncoder.VTYPE_MAP; + } + // TODO throw exception? + return JsonEncoder.VTYPE_UNDEFINED; + } + +} diff --git a/src/com/vaadin/terminal/gwt/server/PaintableIdMapper.java b/src/com/vaadin/terminal/gwt/server/PaintableIdMapper.java index 09c3afd49f..4e69ab7cd1 100644 --- a/src/com/vaadin/terminal/gwt/server/PaintableIdMapper.java +++ b/src/com/vaadin/terminal/gwt/server/PaintableIdMapper.java @@ -23,4 +23,14 @@ public interface PaintableIdMapper extends Serializable { * @return {@link Paintable} instance or null if none found */ public Paintable getPaintable(String paintableId); + + /** + * Get the paintable identifier corresponding to a {@link Paintable} + * instance. + * + * @param paintable + * {@link Paintable} for which to get the id + * @return paintable id or null if none found + */ + public String getPaintableId(Paintable paintable); } -- 2.39.5