Browse Source

#8444 Generate serializers for client to server RPC

tags/7.0.0.alpha2
Artur Signell 12 years ago
parent
commit
4bd6cf0110

+ 45
- 5
src/com/vaadin/terminal/gwt/client/MouseEventDetails.java View File

@@ -66,6 +66,49 @@ public class MouseEventDetails implements Serializable {
return relativeY;
}

public void setButton(int button) {
this.button = button;
}

public void setClientX(int clientX) {
this.clientX = clientX;
}

public void setClientY(int clientY) {
this.clientY = clientY;
}

public void setAltKey(boolean altKey) {
this.altKey = altKey;
}

public void setCtrlKey(boolean ctrlKey) {
this.ctrlKey = ctrlKey;
}

public void setMetaKey(boolean metaKey) {
this.metaKey = metaKey;
}

public void setShiftKey(boolean shiftKey) {
this.shiftKey = shiftKey;
}

public void setType(int type) {
this.type = type;
}

public void setRelativeX(int relativeX) {
this.relativeX = relativeX;
}

public void setRelativeY(int relativeY) {
this.relativeY = relativeY;
}

public MouseEventDetails() {
}

public MouseEventDetails(NativeEvent evt) {
this(evt, null);
}
@@ -85,9 +128,6 @@ public class MouseEventDetails implements Serializable {
}
}

private MouseEventDetails() {
}

@Override
public String toString() {
return serialize();
@@ -128,8 +168,8 @@ public class MouseEventDetails implements Serializable {
return "";
}

public Class<MouseEventDetails> getType() {
return MouseEventDetails.class;
public int getType() {
return type;
}

public boolean isDoubleClick() {

+ 1
- 2
src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java View File

@@ -29,8 +29,7 @@ import com.vaadin.terminal.gwt.client.ConnectorMap;
* @since 7.0
*/
public class JsonDecoder {
private static SerializerMap serializerMap = GWT
.create(SerializerMap.class);
static SerializerMap serializerMap = GWT.create(SerializerMap.class);

/**
* Convert a JSON array with two elements (type and value) into a

+ 14
- 2
src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java View File

@@ -93,8 +93,20 @@ public class JsonEncoder {
return combineTypeAndValue(VTYPE_PAINTABLE, new JSONString(
connectorMap.getConnectorId(paintable)));
} else {
return combineTypeAndValue(getTransportType(value), new JSONString(
String.valueOf(value)));
if (getTransportType(value) != VTYPE_UNDEFINED) {
return combineTypeAndValue(getTransportType(value),
new JSONString(String.valueOf(value)));
} else {
// Try to find a generated serializer object, class name is the
// type
String type = value.getClass().getName();
VaadinSerializer serializer = JsonDecoder.serializerMap
.getSerializer(type);

// TODO handle case with no serializer found
return combineTypeAndValue(type,
serializer.serialize(value, connectorMap));
}
}
}


+ 23
- 3
src/com/vaadin/terminal/gwt/client/communication/VaadinSerializer.java View File

@@ -5,7 +5,9 @@
package com.vaadin.terminal.gwt.client.communication;

import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONValue;
import com.vaadin.terminal.gwt.client.ConnectorMap;
import com.vaadin.terminal.gwt.server.JsonCodec;

/**
* Serializer that can deserialize custom objects received from the server.
@@ -18,16 +20,34 @@ import com.vaadin.terminal.gwt.client.ConnectorMap;
public interface VaadinSerializer {

/**
* Creates and deserializes an object received from the server.
* Creates and deserializes an object received from the server. Must be
* compatible with {@link #serialize(Object, ConnectorMap)} and also with
* the server side
* {@link JsonCodec#encode(Object, com.vaadin.terminal.gwt.server.PaintableIdMapper)}
* .
*
* @param jsonValue
* JSON map from property name to property value
* @param idMapper
* mapper from paintable id to paintable, used to decode
* references to paintables
* @return deserialized object
* @return A deserialized object
*/
// TODO Object -> something
Object deserialize(JSONObject jsonValue, ConnectorMap idMapper);

/**
* Serialize the given object into JSON. Must be compatible with
* {@link #deserialize(JSONObject, ConnectorMap)} and also with the server
* side
* {@link JsonCodec#decode(com.vaadin.external.json.JSONArray, com.vaadin.terminal.gwt.server.PaintableIdMapper)}
*
* @param value
* The object to serialize
* @param idMapper
* mapper from paintable id to paintable, used to decode
* references to paintables
* @return A JSON serialized version of the object
*/
JSONObject serialize(Object value, ConnectorMap idMapper);

}

+ 1
- 1
src/com/vaadin/terminal/gwt/client/ui/ButtonConnector.java View File

@@ -28,7 +28,7 @@ public class ButtonConnector extends AbstractComponentConnector {
* @param mouseEventDetails
* serialized mouse event details
*/
public void click(String mouseEventDetails);
public void click(MouseEventDetails mouseEventDetails);

/**
* Indicate to the server that the client has disabled the button as a

+ 1
- 1
src/com/vaadin/terminal/gwt/client/ui/VButton.java View File

@@ -307,7 +307,7 @@ public class VButton extends FocusWidget implements ClickHandler, FocusHandler,
// Add mouse details
MouseEventDetails details = new MouseEventDetails(
event.getNativeEvent(), getElement());
buttonRpcProxy.click(details.serialize());
buttonRpcProxy.click(details);

clickPending = false;
}

+ 1
- 1
src/com/vaadin/terminal/gwt/client/ui/VNativeButton.java View File

@@ -125,7 +125,7 @@ public class VNativeButton extends Button implements ClickHandler,
// Add mouse details
MouseEventDetails details = new MouseEventDetails(
event.getNativeEvent(), getElement());
buttonRpcProxy.click(details.serialize());
buttonRpcProxy.click(details);

clickPending = false;
}

+ 1
- 1
src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java View File

@@ -1485,7 +1485,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] = JsonCodec.convertVariableValue(
parameters[j] = JsonCodec.decode(
parametersJson.getJSONArray(j), this);
}
MethodInvocation invocation = new MethodInvocation(connectorId,

+ 48
- 5
src/com/vaadin/terminal/gwt/server/JsonCodec.java View File

@@ -4,9 +4,11 @@

package com.vaadin.terminal.gwt.server;

import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
@@ -61,8 +63,8 @@ public class JsonCodec implements Serializable {
* @throws JSONException
* if the conversion fails
*/
public static Object convertVariableValue(JSONArray value,
PaintableIdMapper idMapper) throws JSONException {
public static Object decode(JSONArray value, PaintableIdMapper idMapper)
throws JSONException {
return convertVariableValue(value.getString(0), value.get(1), idMapper);
}

@@ -96,6 +98,10 @@ public class JsonCodec implements Serializable {
} else if (JsonEncoder.VTYPE_PAINTABLE.equals(variableType)) {
// TODO handle properly
val = idMapper.getPaintable(String.valueOf(value));
} else {
// Try to decode object using fields
return decodeObject(variableType, (JSONObject) value, idMapper);

}

return val;
@@ -107,8 +113,7 @@ public class JsonCodec implements Serializable {
Iterator<String> it = jsonMap.keys();
while (it.hasNext()) {
String key = it.next();
map.put(key,
convertVariableValue(jsonMap.getJSONArray(key), idMapper));
map.put(key, decode(jsonMap.getJSONArray(key), idMapper));
}
return map;
}
@@ -129,7 +134,7 @@ public class JsonCodec implements Serializable {
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));
tokens.add(decode(entryArray, idMapper));
}
return tokens.toArray(new Object[tokens.size()]);
}
@@ -210,6 +215,44 @@ public class JsonCodec implements Serializable {
return jsonMap;
}

private static Object decodeObject(String type,
JSONObject serializedObject, PaintableIdMapper idMapper)
throws JSONException {

Class<?> cls;
try {
cls = Class.forName(type);

Object decodedObject = cls.newInstance();
for (PropertyDescriptor pd : Introspector.getBeanInfo(cls)
.getPropertyDescriptors()) {
if (pd.getReadMethod() == null || pd.getWriteMethod() == null) {
continue;
}

String fieldName = pd.getName();
JSONArray encodedObject = serializedObject
.getJSONArray(fieldName);
pd.getWriteMethod().invoke(decodedObject,
decode(encodedObject, idMapper));
}

return decodedObject;
} catch (ClassNotFoundException e) {
throw new JSONException(e);
} catch (IllegalArgumentException e) {
throw new JSONException(e);
} catch (IllegalAccessException e) {
throw new JSONException(e);
} catch (InvocationTargetException e) {
throw new JSONException(e);
} catch (InstantiationException e) {
throw new JSONException(e);
} catch (IntrospectionException e) {
throw new JSONException(e);
}
}

private static JSONArray encodeArrayContents(Object[] array,
PaintableIdMapper idMapper) throws JSONException {
JSONArray jsonArray = new JSONArray();

+ 95
- 44
src/com/vaadin/terminal/gwt/widgetsetutils/SerializerGenerator.java View File

@@ -22,10 +22,12 @@ import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONValue;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
import com.vaadin.terminal.gwt.client.ConnectorMap;
import com.vaadin.terminal.gwt.client.communication.JsonDecoder;
import com.vaadin.terminal.gwt.client.communication.JsonEncoder;
import com.vaadin.terminal.gwt.client.communication.SerializerMap;
import com.vaadin.terminal.gwt.client.communication.VaadinSerializer;

@@ -110,6 +112,42 @@ public class SerializerGenerator extends Generator {
printWriter);
sourceWriter.indent();

// Serializer

// public JSONValue serialize(Object value, ConnectorMap idMapper) {
sourceWriter.println("public " + JSONValue.class.getName()
+ " serialize(" + Object.class.getName() + " value, "
+ ConnectorMap.class.getName() + " idMapper) {");
sourceWriter.indent();
// MouseEventDetails castedValue = (MouseEventDetails) value;
sourceWriter.println(beanQualifiedSourceName + " castedValue = ("
+ beanQualifiedSourceName + ") value;");
// JSONObject json = new JSONObject();
sourceWriter.println(JSONObject.class.getName() + " json = new "
+ JSONObject.class.getName() + "();");

for (JMethod setterMethod : getSetters(beanType)) {
String setterName = setterMethod.getName();
String capitalizedFieldName = setterName.substring(3);
String fieldName = decapitalize(capitalizedFieldName);
String getterName = findGetter(beanType, setterMethod);

if (getterName == null) {
logger.log(TreeLogger.WARN, "No getter found for " + fieldName
+ ". Serialization will likely fail");
}
// json.put("button",
// JsonEncoder.encode(castedValue.getButton(), idMapper));
sourceWriter.println("json.put(\"" + fieldName + "\", "
+ JsonEncoder.class.getName() + ".encode(castedValue."
+ getterName + "(), idMapper));");
}
// return json;
sourceWriter.println("return json;");
// }
sourceWriter.println("}");

// Deserializer
sourceWriter.println("public " + beanQualifiedSourceName
+ " deserialize(" + JSONObject.class.getName() + " jsonValue, "
+ ConnectorMap.class.getName() + " idMapper) {");
@@ -118,44 +156,35 @@ public class SerializerGenerator extends Generator {
// VButtonState state = GWT.create(VButtonState.class);
sourceWriter.println(beanQualifiedSourceName + " state = GWT.create("
+ beanQualifiedSourceName + ".class);");
JClassType objectType = typeOracle.findType("java.lang.Object");
while (!objectType.equals(beanType)) {
for (JMethod method : getSetters(beanType)) {
String setterName = method.getName();
String capitalizedFieldName = setterName.substring(3);
String fieldName = decapitalize(capitalizedFieldName);
JType setterParameterType = method.getParameterTypes()[0];

logger.log(
Type.INFO,
"* Processing field " + fieldName + " in "
+ beanQualifiedSourceName + " ("
+ beanType.getName() + ")");

String jsonFieldName = "json" + capitalizedFieldName;
// JSONArray jsonHeight = (JSONArray) jsonValue.get("height");
sourceWriter.println("JSONArray " + jsonFieldName
+ " = (JSONArray) jsonValue.get(\"" + fieldName
+ "\");");

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

String fieldType;
JPrimitiveType primitiveType = setterParameterType
.isPrimitive();
if (primitiveType != null) {
// This is a primitive type -> must used the boxed type
fieldType = primitiveType.getQualifiedBoxedSourceName();
} else {
fieldType = setterParameterType.getQualifiedSourceName();
}
for (JMethod method : getSetters(beanType)) {
String setterName = method.getName();
String capitalizedFieldName = setterName.substring(3);
String fieldName = decapitalize(capitalizedFieldName);
JType setterParameterType = method.getParameterTypes()[0];

logger.log(Type.INFO, "* Processing field " + fieldName + " in "
+ beanQualifiedSourceName + " (" + beanType.getName() + ")");

String jsonFieldName = "json" + capitalizedFieldName;
// JSONArray jsonHeight = (JSONArray) jsonValue.get("height");
sourceWriter.println("JSONArray " + jsonFieldName
+ " = (JSONArray) jsonValue.get(\"" + fieldName + "\");");

sourceWriter.println("state." + setterName + "((" + fieldType
+ ") JsonDecoder.convertValue(" + jsonFieldName
+ ", idMapper));");
// state.setHeight((String)
// JsonDecoder.convertValue(jsonFieldValue,idMapper));

String fieldType;
JPrimitiveType primitiveType = setterParameterType.isPrimitive();
if (primitiveType != null) {
// This is a primitive type -> must used the boxed type
fieldType = primitiveType.getQualifiedBoxedSourceName();
} else {
fieldType = setterParameterType.getQualifiedSourceName();
}
beanType = beanType.getSuperclass();

sourceWriter.println("state." + setterName + "((" + fieldType
+ ") JsonDecoder.convertValue(" + jsonFieldName
+ ", idMapper));");
}

// return state;
@@ -175,19 +204,41 @@ public class SerializerGenerator extends Generator {

}

private String findGetter(JClassType beanType, JMethod setterMethod) {
JType setterParameterType = setterMethod.getParameterTypes()[0];
String capitalizedFieldName = setterMethod.getName().substring(3);
if (setterParameterType.getQualifiedSourceName().equals(
boolean.class.getName())) {
return "is" + capitalizedFieldName;
} else {
return "get" + capitalizedFieldName;
}
}

/**
* Returns a list of all setters found in the beanType or its parent class
*
* @param beanType
* The type to check
* @return A list of setter methods from the class and its parents
*/
protected static List<JMethod> getSetters(JClassType beanType) {

List<JMethod> setterMethods = new ArrayList<JMethod>();

for (JMethod method : beanType.getMethods()) {
// Process all setters that have corresponding fields
if (!method.isPublic() || method.isStatic()
|| !method.getName().startsWith("set")
|| method.getParameterTypes().length != 1) {
// Not setter, skip to next method
continue;
while (!beanType.getQualifiedSourceName()
.equals(Object.class.getName())) {
for (JMethod method : beanType.getMethods()) {
// Process all setters that have corresponding fields
if (!method.isPublic() || method.isStatic()
|| !method.getName().startsWith("set")
|| method.getParameterTypes().length != 1) {
// Not setter, skip to next method
continue;
}
setterMethods.add(method);
}
setterMethods.add(method);
beanType = beanType.getSuperclass();
}

return setterMethods;

+ 23
- 1
src/com/vaadin/terminal/gwt/widgetsetutils/SerializerMapGenerator.java View File

@@ -22,6 +22,7 @@ import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
import com.vaadin.terminal.gwt.client.communication.SerializerMap;
import com.vaadin.terminal.gwt.client.communication.ServerRpc;
import com.vaadin.terminal.gwt.client.communication.SharedState;
import com.vaadin.terminal.gwt.client.communication.VaadinSerializer;

@@ -144,7 +145,15 @@ public class SerializerMapGenerator extends Generator {
types.add(type);
}

// Add all types used from/in the determined types
// Serializer classes might also be needed for RPC methods
JClassType serverRpcType = typeOracle.findType(ServerRpc.class
.getName());
JClassType[] serverRpcSubtypes = serverRpcType.getSubtypes();
for (JClassType type : serverRpcSubtypes) {
addMethodParameterTypes(type, types);
}

// Add all types used from/in the types
for (Object t : types.toArray()) {
findSubTypesNeedingSerializers((JClassType) t, types);
}
@@ -153,6 +162,18 @@ public class SerializerMapGenerator extends Generator {
return types;
}

private void addMethodParameterTypes(JClassType classContainingMethods,
HashSet<JClassType> types) {
for (JMethod method : classContainingMethods.getMethods()) {
if (method.getName().equals("initRpc")) {
continue;
}
for (JType type : method.getParameterTypes()) {
types.add(type.isClass());
}
}
}

public void findSubTypesNeedingSerializers(JClassType type,
Set<JClassType> serializableTypes) {
// Find all setters and look at their parameter type to determine if a
@@ -169,6 +190,7 @@ public class SerializerMapGenerator extends Generator {
}

serializableTypes.add(setterType.isClass());
findSubTypesNeedingSerializers(type, serializableTypes);
}
}


+ 2
- 2
src/com/vaadin/ui/Button.java View File

@@ -48,8 +48,8 @@ public class Button extends AbstractComponent implements
public Button() {
// TODO take the implementation out of an anonymous class?
registerRpcImplementation(new ButtonServerRpc() {
public void click(String mouseEventDetails) {
fireClick(MouseEventDetails.deSerialize(mouseEventDetails));
public void click(MouseEventDetails mouseEventDetails) {
fireClick(mouseEventDetails);
}

public void disableOnClick() {

Loading…
Cancel
Save