]> source.dussan.org Git - vaadin-framework.git/commitdiff
Basic JSON encoding and decoding utilities from server to client (#8304)
authorHenri Sara <hesara@vaadin.com>
Mon, 30 Jan 2012 13:47:49 +0000 (15:47 +0200)
committerHenri Sara <hesara@vaadin.com>
Mon, 30 Jan 2012 13:47:49 +0000 (15:47 +0200)
Also includes minor refactoring in preparation for shared state support.

src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java
src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java
src/com/vaadin/terminal/gwt/server/JsonCodec.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/server/JsonDecoder.java [deleted file]
src/com/vaadin/terminal/gwt/server/PaintableIdMapper.java

diff --git a/src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java b/src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java
new file mode 100644 (file)
index 0000000..e7fea08
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.terminal.gwt.client.communication;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+
+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;
+
+/**
+ * Client side decoder for converting shared state and other values from JSON
+ * received from the server.
+ * 
+ * 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 {
+    /**
+     * Convert a JSON array with two elements (type and value) into a
+     * client-side type, recursively if necessary.
+     * 
+     * @param value
+     *            JSON array with two elements
+     * @param idMapper
+     *            mapper between paintable ID and {@link VPaintable} objects
+     * @return converted value (does not contain JSON types)
+     */
+    public static Object convertVariableValue(JSONArray value,
+            VPaintableMap idMapper) {
+        return convertVariableValue(((JSONString) value.get(0)).stringValue()
+                .charAt(0), value.get(1), idMapper);
+    }
+
+    private static Object convertVariableValue(char variableType, Object value,
+            VPaintableMap idMapper) {
+        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, VPaintableMap idMapper) {
+        HashMap<String, Object> map = new HashMap<String, Object>();
+        Iterator<String> it = jsonMap.keySet().iterator();
+        while (it.hasNext()) {
+            String key = it.next();
+            map.put(key,
+                    convertVariableValue((JSONArray) jsonMap.get(key), idMapper));
+        }
+        return map;
+    }
+
+    private static String[] convertStringArray(JSONArray jsonArray) {
+        int size = jsonArray.size();
+        List<String> tokens = new ArrayList<String>(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,
+            VPaintableMap idMapper) {
+        List<Object> tokens = new ArrayList<Object>();
+        for (int i = 0; i < jsonArray.size(); ++i) {
+            // each entry always has two elements: type and value
+            JSONArray entryArray = (JSONArray) jsonArray.get(i);
+            tokens.add(convertVariableValue(entryArray, idMapper));
+        }
+        return tokens.toArray(new Object[tokens.size()]);
+    }
+
+}
index c2656764fea4a2ba43c0be34da921033509b569f..4b26d5939f89cc6609e9850ab3c8691749016e78 100644 (file)
@@ -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<String, Object> map = (Map<String, Object>) value;
-            // TODO implement; types for each element
             JSONObject jsonMap = new JSONObject();
             for (String mapKey : map.keySet()) {
                 // TODO handle object graph loops?
index 8addc8fbffa9048ca77cfb0f94f66b7afaa7dc79..0d25b484225ad34913e481a3b3793b3ad54a0668 100644 (file)
@@ -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<Paintable> paintables = null;
 
-        List<InvalidLayout> 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<Paintable>();
@@ -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<Paintable> 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<InvalidLayout> 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<Paintable> 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 (file)
index 0000000..dfe4491
--- /dev/null
@@ -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<String, Object> map = new HashMap<String, Object>();
+        Iterator<String> 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<String> tokens = new ArrayList<String>(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<Object> tokens = new ArrayList<Object>();
+        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<String, Object> map = (Map<String, Object>) 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/JsonDecoder.java b/src/com/vaadin/terminal/gwt/server/JsonDecoder.java
deleted file mode 100644 (file)
index 35cbe52..0000000
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
-@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 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.
- * 
- * TODO support bi-directional codec functionality
- * 
- * @since 7.0
- */
-public class JsonDecoder 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 from paintable ID to {@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<String, Object> map = new HashMap<String, Object>();
-        Iterator<String> 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 {
-        List<String> tokens = new ArrayList<String>();
-        for (int i = 0; i < jsonArray.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<Object> tokens = new ArrayList<Object>();
-        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()]);
-    }
-
-}
index 09c3afd49f3e2bdc4a7253cd49f5c3c308d0ead3..4e69ab7cd1dfd91e70177cae81d9f819402382c4 100644 (file)
@@ -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);
 }