Parcourir la source

Initial support for sending difference in state (#8419)

Forces the shared state to be the type declared by getStateType (#8677)

Creates an empty object for reference when doing a full repaint, causing
only the differences between a freshly created object and the current
state to be sent.
tags/7.0.0.alpha3
Artur Signell il y a 12 ans
Parent
révision
badc5c3ee4

+ 12
- 3
src/com/vaadin/terminal/gwt/client/ApplicationConnection.java Voir le fichier

@@ -43,6 +43,7 @@ import com.vaadin.terminal.gwt.client.communication.JsonDecoder;
import com.vaadin.terminal.gwt.client.communication.JsonEncoder;
import com.vaadin.terminal.gwt.client.communication.MethodInvocation;
import com.vaadin.terminal.gwt.client.communication.RpcManager;
import com.vaadin.terminal.gwt.client.communication.SerializerMap;
import com.vaadin.terminal.gwt.client.communication.SharedState;
import com.vaadin.terminal.gwt.client.communication.StateChangeEvent;
import com.vaadin.terminal.gwt.client.ui.AbstractComponentConnector;
@@ -102,6 +103,9 @@ public class ApplicationConnection {

public static final String PARAM_UNLOADBURST = "onunloadburst";

private static SerializerMap serializerMap = GWT
.create(SerializerMap.class);

/**
* A string that, if found in a non-JSON response to a UIDL request, will
* cause the browser to refresh the page. If followed by a colon, optional
@@ -1414,8 +1418,8 @@ public class ApplicationConnection {
states.getJavaScriptObject(connectorId));

Object state = JsonDecoder.decodeValue(
stateDataAndType, connectorMap,
ApplicationConnection.this);
stateDataAndType, connector.getState(),
connectorMap, ApplicationConnection.this);

connector.setState((SharedState) state);
StateChangeEvent event = GWT
@@ -1569,7 +1573,8 @@ public class ApplicationConnection {
Object[] parameters = new Object[parametersJson.size()];
for (int j = 0; j < parametersJson.size(); ++j) {
parameters[j] = JsonDecoder.decodeValue(
(JSONArray) parametersJson.get(j), getConnectorMap(), this);
(JSONArray) parametersJson.get(j), null, getConnectorMap(),
this);
}
return new MethodInvocation(connectorId, interfaceName, methodName,
parameters);
@@ -2439,4 +2444,8 @@ public class ApplicationConnection {
LayoutManager getLayoutManager() {
return layoutManager;
}

public SerializerMap getSerializerMap() {
return serializerMap;
}
}

+ 3
- 1
src/com/vaadin/terminal/gwt/client/communication/JSONSerializer.java Voir le fichier

@@ -33,12 +33,14 @@ public interface JSONSerializer<T> {
*
* @param jsonValue
* JSON map from property name to property value
* @param target
* The object to write the deserialized values to
* @param idMapper
* mapper from paintable id to paintable, used to decode
* references to paintables
* @return A deserialized object
*/
T deserialize(JSONObject jsonValue, ConnectorMap idMapper,
T deserialize(JSONObject jsonValue, T target, ConnectorMap idMapper,
ApplicationConnection connection);

/**

+ 22
- 16
src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java Voir le fichier

@@ -12,7 +12,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;

import com.google.gwt.core.client.GWT;
import com.google.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONString;
@@ -33,7 +32,6 @@ import com.vaadin.terminal.gwt.client.ServerConnector;
* @since 7.0
*/
public class JsonDecoder {
static SerializerMap serializerMap = GWT.create(SerializerMap.class);

/**
* Decode a JSON array with two elements (type and value) into a client-side
@@ -48,14 +46,15 @@ public class JsonDecoder {
* reference to the current ApplicationConnection
* @return decoded value (does not contain JSON types)
*/
public static Object decodeValue(JSONArray jsonArray,
public static Object decodeValue(JSONArray jsonArray, Object target,
ConnectorMap idMapper, ApplicationConnection connection) {
String type = ((JSONString) jsonArray.get(0)).stringValue();
return decodeValue(type, jsonArray.get(1), idMapper, connection);
return decodeValue(type, jsonArray.get(1), target, idMapper, connection);
}

private static Object decodeValue(String variableType, Object value,
ConnectorMap idMapper, ApplicationConnection connection) {
Object target, ConnectorMap idMapper,
ApplicationConnection connection) {
Object val = null;
// TODO type checks etc.
if (JsonEncoder.VTYPE_NULL.equals(variableType)) {
@@ -92,18 +91,25 @@ public class JsonDecoder {
} else if (JsonEncoder.VTYPE_CONNECTOR.equals(variableType)) {
val = idMapper.getConnector(((JSONString) value).stringValue());
} else {
// object, class name as type
JSONSerializer serializer = serializerMap
.getSerializer(variableType);
// TODO handle case with no serializer found
Object object = serializer.deserialize((JSONObject) value,
return decodeObject(variableType, (JSONObject) value, target,
idMapper, connection);
return object;
}

return val;
}

private static Object decodeObject(String variableType,
JSONObject encodedValue, Object target, ConnectorMap idMapper,
ApplicationConnection connection) {
// object, class name as type
JSONSerializer<Object> serializer = connection.getSerializerMap()
.getSerializer(variableType);
// TODO handle case with no serializer found
Object object = serializer.deserialize(encodedValue, target, idMapper,
connection);
return object;
}

private static Map<String, Object> decodeMap(JSONObject jsonMap,
ConnectorMap idMapper, ApplicationConnection connection) {
HashMap<String, Object> map = new HashMap<String, Object>();
@@ -111,7 +117,7 @@ public class JsonDecoder {
while (it.hasNext()) {
String key = it.next();
map.put(key,
decodeValue((JSONArray) jsonMap.get(key), idMapper,
decodeValue((JSONArray) jsonMap.get(key), null, idMapper,
connection));
}
return map;
@@ -126,8 +132,8 @@ public class JsonDecoder {
String connectorId = it.next();
Connector connector = idMapper.getConnector(connectorId);
map.put(connector,
decodeValue((JSONArray) jsonMap.get(connectorId), idMapper,
connection));
decodeValue((JSONArray) jsonMap.get(connectorId), null,
idMapper, connection));
}
return map;
}
@@ -153,7 +159,7 @@ public class JsonDecoder {
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(decodeValue(entryArray, idMapper, connection));
tokens.add(decodeValue(entryArray, null, idMapper, connection));
}
return tokens;
}
@@ -164,7 +170,7 @@ public class JsonDecoder {
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(decodeValue(entryArray, idMapper, connection));
tokens.add(decodeValue(entryArray, null, idMapper, connection));
}
return tokens;
}

+ 1
- 1
src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java Voir le fichier

@@ -117,7 +117,7 @@ public class JsonEncoder {
// Try to find a generated serializer object, class name is the
// type
transportType = value.getClass().getName();
JSONSerializer serializer = JsonDecoder.serializerMap
JSONSerializer serializer = connection.getSerializerMap()
.getSerializer(transportType);

// TODO handle case with no serializer found

+ 7
- 5
src/com/vaadin/terminal/gwt/client/communication/URLReference_Serializer.java Voir le fichier

@@ -11,13 +11,15 @@ import com.vaadin.terminal.gwt.client.ConnectorMap;

public class URLReference_Serializer implements JSONSerializer<URLReference> {

public URLReference deserialize(JSONObject jsonValue,
public URLReference deserialize(JSONObject jsonValue, URLReference target,
ConnectorMap idMapper, ApplicationConnection connection) {
URLReference reference = GWT.create(URLReference.class);
JSONArray jsonURL = (JSONArray) jsonValue.get("URL");
String URL = (String) JsonDecoder.decodeValue(jsonURL, idMapper,
connection);
reference.setURL(connection.translateVaadinUri(URL));
if (jsonValue.containsKey("URL")) {
JSONArray jsonURL = (JSONArray) jsonValue.get("URL");
String URL = (String) JsonDecoder.decodeValue(jsonURL, null,
idMapper, connection);
reference.setURL(connection.translateVaadinUri(URL));
}
return reference;
}


+ 31
- 5
src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java Voir le fichier

@@ -803,14 +803,28 @@ public abstract class AbstractCommunicationManager implements Serializable {
// client after component creation but before legacy UIDL
// processing.
JSONObject sharedStates = new JSONObject();
for (Connector connector : dirtyVisibleConnectors) {
for (ClientConnector connector : dirtyVisibleConnectors) {
SharedState state = connector.getState();
if (null != state) {
// encode and send shared state
try {
// FIXME Use declared type
Class<? extends SharedState> stateType = connector
.getStateType();
SharedState referenceState = null;
if (repaintAll) {
// Use an empty state object as reference for full
// repaints
try {
referenceState = stateType.newInstance();
} catch (Exception e) {
logger.log(Level.WARNING,
"Error creating reference object for state of type "
+ stateType.getName());
}
}
JSONArray stateJsonArray = JsonCodec.encode(state,
state.getClass(), application);
referenceState, stateType, application);

sharedStates
.put(connector.getConnectorId(), stateJsonArray);
} catch (JSONException e) {
@@ -900,9 +914,21 @@ public abstract class AbstractCommunicationManager implements Serializable {
invocationJson.put(invocation.getMethodName());
JSONArray paramJson = new JSONArray();
for (int i = 0; i < invocation.getParameterTypes().length; ++i) {
Class<?> parameterType = invocation.getParameterTypes()[i];
Object referenceParameter = null;
// TODO Use default values for RPC parameter types
// if (!JsonCodec.isInternalType(parameterType)) {
// try {
// referenceParameter = parameterType.newInstance();
// } catch (Exception e) {
// logger.log(Level.WARNING,
// "Error creating reference object for parameter of type "
// + parameterType.getName());
// }
// }
paramJson.put(JsonCodec.encode(
invocation.getParameters()[i],
invocation.getParameterTypes()[i], application));
invocation.getParameters()[i], referenceParameter,
parameterType, application));
}
invocationJson.put(paramJson);
rpcCalls.put(invocationJson);

+ 9
- 0
src/com/vaadin/terminal/gwt/server/ClientConnector.java Voir le fichier

@@ -6,6 +6,7 @@ package com.vaadin.terminal.gwt.server;
import java.util.List;

import com.vaadin.terminal.gwt.client.Connector;
import com.vaadin.terminal.gwt.client.communication.SharedState;

/**
* Interface implemented by all connectors that are capable of communicating
@@ -35,4 +36,12 @@ public interface ClientConnector extends Connector, RpcTarget {
* @return true if the connector can receive messages, false otherwise
*/
public boolean isConnectorEnabled();

/**
* Returns the type of the shared state for this connector
*
* @return The type of the state. Must never return null.
*/
public Class<? extends SharedState> getStateType();

}

+ 4
- 0
src/com/vaadin/terminal/gwt/server/DragAndDropService.java Voir le fichier

@@ -238,4 +238,8 @@ public class DragAndDropService implements VariableOwner, ClientConnector {
// TODO Use rpc for drag'n'drop
return null;
}

public Class<? extends SharedState> getStateType() {
return SharedState.class;
}
}

+ 69
- 16
src/com/vaadin/terminal/gwt/server/JsonCodec.java Voir le fichier

@@ -419,11 +419,11 @@ public class JsonCodec implements Serializable {
@Deprecated
private static JSONArray encode(Object value, Application application)
throws JSONException {
return encode(value, null, application);
return encode(value, null, null, application);
}

public static JSONArray encode(Object value, Class<?> valueType,
Application application) throws JSONException {
public static JSONArray encode(Object value, Object referenceValue,
Type valueType, Application application) throws JSONException {

if (null == value) {
return encodeNull();
@@ -453,7 +453,8 @@ public class JsonCodec implements Serializable {
"Unable to serialize unsupported type: " + valueType);
}
Collection<?> collection = (Collection<?>) value;
JSONArray jsonArray = encodeCollection(collection, application);
JSONArray jsonArray = encodeCollection(valueType, collection,
application);

return combineTypeAndValue(internalTransportType, jsonArray);
} else if (value instanceof Object[]) {
@@ -486,8 +487,9 @@ public class JsonCodec implements Serializable {
} else {
// Any object that we do not know how to encode we encode by looping
// through fields
return combineTypeAndValue(getCustomTransportType(valueType),
encodeObject(value, application));
return combineTypeAndValue(
getCustomTransportType((Class<?>) valueType),
encodeObject(value, referenceValue, application));
}
}

@@ -495,22 +497,40 @@ public class JsonCodec implements Serializable {
return combineTypeAndValue(JsonEncoder.VTYPE_NULL, JSONObject.NULL);
}

private static Object encodeObject(Object value, Application application)
throws JSONException {
private static Object encodeObject(Object value, Object referenceValue,
Application application) throws JSONException {
JSONObject jsonMap = new JSONObject();

try {
for (PropertyDescriptor pd : Introspector.getBeanInfo(
value.getClass()).getPropertyDescriptors()) {
Class<?> fieldType = pd.getPropertyType();
String fieldName = getTransportFieldName(pd);
if (fieldName == null) {
continue;
}
Method getterMethod = pd.getReadMethod();
// We can't use PropertyDescriptor.getPropertyType() as it does
// not support generics
Type fieldType = getterMethod.getGenericReturnType();
Object fieldValue = getterMethod.invoke(value, (Object[]) null);
jsonMap.put(fieldName,
encode(fieldValue, fieldType, application));
boolean equals = false;
Object referenceFieldValue = null;
if (referenceValue != null) {
referenceFieldValue = getterMethod.invoke(referenceValue,
(Object[]) null);
equals = equals(fieldValue, referenceFieldValue);
}
if (!equals) {
jsonMap.put(
fieldName,
encode(fieldValue, referenceFieldValue, fieldType,
application));
// } else {
// System.out.println("Skipping field " + fieldName
// + " of type " + fieldType.getName()
// + " for object " + value.getClass().getName()
// + " as " + fieldValue + "==" + referenceFieldValue);
}
}
} catch (Exception e) {
// TODO: Should exceptions be handled in a different way?
@@ -519,24 +539,56 @@ public class JsonCodec implements Serializable {
return jsonMap;
}

/**
* Compares the value with the reference. If they match, returns true.
*
* @param fieldValue
* @param referenceValue
* @return
*/
private static boolean equals(Object fieldValue, Object referenceValue) {
if (fieldValue == null) {
return referenceValue == null;
}

if (fieldValue.equals(referenceValue)) {
return true;
}

return false;
}

private static JSONArray encodeArrayContents(Object[] array,
Application application) throws JSONException {
JSONArray jsonArray = new JSONArray();
for (Object o : array) {
jsonArray.put(encode(o, null, application));
jsonArray.put(encode(o, null, null, application));
}
return jsonArray;
}

private static JSONArray encodeCollection(Collection collection,
Application application) throws JSONException {
private static JSONArray encodeCollection(Type targetType,
Collection collection, Application application)
throws JSONException {
JSONArray jsonArray = new JSONArray();
for (Object o : collection) {
jsonArray.put(encode(o, application));
jsonArray.put(encodeChild(targetType, 0, o, application));
}
return jsonArray;
}

private static JSONArray encodeChild(Type targetType, int typeIndex,
Object o, Application application) throws JSONException {
if (targetType instanceof ParameterizedType) {
Type childType = ((ParameterizedType) targetType)
.getActualTypeArguments()[typeIndex];
// Encode using the given type
return encode(o, null, childType, application);
} else {
return encode(o, application);
}
}

private static JSONObject encodeMapContents(Map<Object, Object> map,
Application application) throws JSONException {
JSONObject jsonMap = new JSONObject();
@@ -551,7 +603,8 @@ public class JsonCodec implements Serializable {
"Only maps with String/Connector keys are currently supported (#8602)");
}

jsonMap.put((String) mapKey, encode(mapValue, null, application));
jsonMap.put((String) mapKey,
encode(mapValue, null, null, application));
}
return jsonMap;
}

+ 49
- 12
src/com/vaadin/terminal/gwt/widgetsetutils/SerializerGenerator.java Voir le fichier

@@ -106,7 +106,8 @@ public class SerializerGenerator extends Generator {
composer.addImport(JsonDecoder.class.getName());
// composer.addImport(VaadinSerializer.class.getName());

composer.addImplementedInterface(JSONSerializer.class.getName());
composer.addImplementedInterface(JSONSerializer.class.getName() + "<"
+ beanQualifiedSourceName + ">");

SourceWriter sourceWriter = composer.createSourceWriter(context,
printWriter);
@@ -117,7 +118,7 @@ public class SerializerGenerator extends Generator {
// public JSONValue serialize(Object value, ConnectorMap idMapper,
// ApplicationConnection connection) {
sourceWriter.println("public " + JSONObject.class.getName()
+ " serialize(" + Object.class.getName() + " value, "
+ " serialize(" + beanQualifiedSourceName + " value, "
+ ConnectorMap.class.getName() + " idMapper, "
+ ApplicationConnection.class.getName() + " connection) {");
sourceWriter.indent();
@@ -152,13 +153,20 @@ public class SerializerGenerator extends Generator {
// Deserializer
sourceWriter.println("public " + beanQualifiedSourceName
+ " deserialize(" + JSONObject.class.getName() + " jsonValue, "
+ beanQualifiedSourceName + " target, "
+ ConnectorMap.class.getName() + " idMapper, "
+ ApplicationConnection.class.getName() + " connection) {");
sourceWriter.indent();

// VButtonState state = GWT.create(VButtonState.class);
sourceWriter.println(beanQualifiedSourceName + " state = GWT.create("
+ beanQualifiedSourceName + ".class);");
// if (target == null) {
sourceWriter.println("if (target == null) {");
sourceWriter.indent();
// target = GWT.create(VButtonState.class);
sourceWriter.println("target = GWT.create(" + beanQualifiedSourceName
+ ".class);");
sourceWriter.outdent();
sourceWriter.println("}");

for (JMethod method : getSetters(beanType)) {
String setterName = method.getName();
String fieldName = setterName.substring(3); // setZIndex() -> ZIndex
@@ -167,30 +175,59 @@ public class SerializerGenerator extends Generator {
logger.log(Type.DEBUG, "* Processing field " + fieldName + " in "
+ beanQualifiedSourceName + " (" + beanType.getName() + ")");

// if (jsonValue.containsKey("height")) {
sourceWriter.println("if (jsonValue.containsKey(\"" + fieldName
+ "\")) {");
sourceWriter.indent();
String jsonFieldName = "json_" + fieldName;
// JSONArray json_Height = (JSONArray) jsonValue.get("height");
sourceWriter.println("JSONArray " + jsonFieldName
+ " = (JSONArray) jsonValue.get(\"" + fieldName + "\");");

// state.setHeight((String)
// JsonDecoder.decodeValue(jsonFieldValue,idMapper, connection));

String fieldType;
String getterName = "get" + fieldName;
JPrimitiveType primitiveType = setterParameterType.isPrimitive();
if (primitiveType != null) {
// This is a primitive type -> must used the boxed type
fieldType = primitiveType.getQualifiedBoxedSourceName();
if (primitiveType == JPrimitiveType.BOOLEAN) {
getterName = "is" + fieldName;
}
} else {
fieldType = setterParameterType.getQualifiedSourceName();
}

sourceWriter.println("state." + setterName + "((" + fieldType
// String referenceValue;
sourceWriter.println(fieldType + " referenceValue;");
// if (target == null) {
sourceWriter.println("if (target == null) {");
sourceWriter.indent();
// referenceValue = null;
sourceWriter.println("referenceValue = null;");
// } else {
sourceWriter.println("} else {");
// referenceValue = target.getHeight();
sourceWriter.println("referenceValue = target." + getterName
+ "();");
// }
sourceWriter.outdent();
sourceWriter.println("}");

// target.setHeight((String)
// JsonDecoder.decodeValue(jsonFieldValue,referenceValue, idMapper,
// connection));
sourceWriter.println("target." + setterName + "((" + fieldType
+ ") " + JsonDecoder.class.getName() + ".decodeValue("
+ jsonFieldName + ", idMapper, connection));");
+ jsonFieldName
+ ", referenceValue, idMapper, connection));");

// } ... end of if contains
sourceWriter.println("}");
sourceWriter.outdent();
}

// return state;
sourceWriter.println("return state;");
// return target;
sourceWriter.println("return target;");
sourceWriter.println("}");
sourceWriter.outdent();


+ 16
- 9
src/com/vaadin/ui/AbstractComponent.java Voir le fichier

@@ -18,7 +18,6 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -820,20 +819,28 @@ public abstract class AbstractComponent implements Component, MethodEventSource
* @return new shared state object
*/
protected ComponentState createState() {
try {
return getStateType().newInstance();
} catch (Exception e) {
throw new RuntimeException(
"Error creating state of type " + getStateType().getName()
+ " for " + getClass().getName(), e);
}
}

/* (non-Javadoc)
* @see com.vaadin.terminal.gwt.server.ClientConnector#getStateType()
*/
public Class<? extends ComponentState> getStateType() {
try {
Method m = getClass().getMethod("getState", (Class[]) null);
Class<? extends ComponentState> type = (Class<? extends ComponentState>) m
.getReturnType();
return type.newInstance();
return type;
} catch (Exception e) {
getLogger().log(
Level.INFO,
"Error determining state object class for "
+ getClass().getName());
throw new RuntimeException("Error finding state type for "
+ getClass().getName(), e);
}

// Fall back to ComponentState if detection fails for some reason.
return new ComponentState();
}

/* Documentation copied from interface */

+ 2
- 3
src/com/vaadin/ui/Root.java Voir le fichier

@@ -30,7 +30,6 @@ import com.vaadin.terminal.Resource;
import com.vaadin.terminal.Vaadin6Component;
import com.vaadin.terminal.WrappedRequest;
import com.vaadin.terminal.WrappedRequest.BrowserDetails;
import com.vaadin.terminal.gwt.client.ComponentState;
import com.vaadin.terminal.gwt.client.MouseEventDetails;
import com.vaadin.terminal.gwt.client.ui.notification.VNotification;
import com.vaadin.terminal.gwt.client.ui.root.RootServerRpc;
@@ -475,10 +474,10 @@ public abstract class Root extends AbstractComponentContainer implements
}

@Override
protected ComponentState createState() {
public Class<? extends RootState> getStateType() {
// This is a workaround for a problem with creating the correct state
// object during build
return new RootState();
return RootState.class;
}

/**

Chargement…
Annuler
Enregistrer