Also includes minor refactoring in preparation for shared state support.tags/7.0.0.alpha2
@@ -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<String, Object> map = new HashMap<String, Object>(); | |||
Iterator<String> it = jsonMap.keys(); | |||
Iterator<String> 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<String> tokens = new ArrayList<String>(); | |||
for (int i = 0; i < jsonArray.length(); ++i) { | |||
tokens.add(jsonArray.getString(i)); | |||
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, | |||
PaintableIdMapper idMapper) throws JSONException { | |||
VPaintableMap idMapper) { | |||
List<Object> tokens = new ArrayList<Object>(); | |||
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()]); |
@@ -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? |
@@ -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, |
@@ -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; | |||
} | |||
} |
@@ -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); | |||
} |