From 4bd6cf0110a171c58815b0845be396ed6e53b1f4 Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Thu, 1 Mar 2012 18:12:34 +0200 Subject: [PATCH] #8444 Generate serializers for client to server RPC --- .../gwt/client/MouseEventDetails.java | 50 ++++++- .../gwt/client/communication/JsonDecoder.java | 3 +- .../gwt/client/communication/JsonEncoder.java | 16 +- .../communication/VaadinSerializer.java | 26 +++- .../gwt/client/ui/ButtonConnector.java | 2 +- .../terminal/gwt/client/ui/VButton.java | 2 +- .../terminal/gwt/client/ui/VNativeButton.java | 2 +- .../server/AbstractCommunicationManager.java | 2 +- .../vaadin/terminal/gwt/server/JsonCodec.java | 53 ++++++- .../widgetsetutils/SerializerGenerator.java | 139 ++++++++++++------ .../SerializerMapGenerator.java | 24 ++- src/com/vaadin/ui/Button.java | 4 +- 12 files changed, 255 insertions(+), 68 deletions(-) diff --git a/src/com/vaadin/terminal/gwt/client/MouseEventDetails.java b/src/com/vaadin/terminal/gwt/client/MouseEventDetails.java index 260dfa6fff..57b83701fd 100644 --- a/src/com/vaadin/terminal/gwt/client/MouseEventDetails.java +++ b/src/com/vaadin/terminal/gwt/client/MouseEventDetails.java @@ -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 getType() { - return MouseEventDetails.class; + public int getType() { + return type; } public boolean isDoubleClick() { diff --git a/src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java b/src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java index fdaaf43abd..6682faa69d 100644 --- a/src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java +++ b/src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java @@ -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 diff --git a/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java b/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java index 21cb94753a..3013cc9060 100644 --- a/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java +++ b/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java @@ -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)); + } } } diff --git a/src/com/vaadin/terminal/gwt/client/communication/VaadinSerializer.java b/src/com/vaadin/terminal/gwt/client/communication/VaadinSerializer.java index c8cf869e01..f201e507e3 100644 --- a/src/com/vaadin/terminal/gwt/client/communication/VaadinSerializer.java +++ b/src/com/vaadin/terminal/gwt/client/communication/VaadinSerializer.java @@ -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); + } diff --git a/src/com/vaadin/terminal/gwt/client/ui/ButtonConnector.java b/src/com/vaadin/terminal/gwt/client/ui/ButtonConnector.java index 34b4591eae..c9e69235b1 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/ButtonConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/ButtonConnector.java @@ -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 diff --git a/src/com/vaadin/terminal/gwt/client/ui/VButton.java b/src/com/vaadin/terminal/gwt/client/ui/VButton.java index 5309d11a04..f5093ee47b 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VButton.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VButton.java @@ -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; } diff --git a/src/com/vaadin/terminal/gwt/client/ui/VNativeButton.java b/src/com/vaadin/terminal/gwt/client/ui/VNativeButton.java index c7036d52d0..a6a50dc8c1 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VNativeButton.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VNativeButton.java @@ -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; } diff --git a/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java b/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java index 9143d61aca..749c9ece6f 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java @@ -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, diff --git a/src/com/vaadin/terminal/gwt/server/JsonCodec.java b/src/com/vaadin/terminal/gwt/server/JsonCodec.java index 09afea3f74..350a70d9d4 100644 --- a/src/com/vaadin/terminal/gwt/server/JsonCodec.java +++ b/src/com/vaadin/terminal/gwt/server/JsonCodec.java @@ -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 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(); diff --git a/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerGenerator.java b/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerGenerator.java index 0beed2abf0..2df141f0ed 100644 --- a/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerGenerator.java +++ b/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerGenerator.java @@ -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 getSetters(JClassType beanType) { List setterMethods = new ArrayList(); - 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; diff --git a/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerMapGenerator.java b/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerMapGenerator.java index 778ced783b..a766d75da7 100644 --- a/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerMapGenerator.java +++ b/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerMapGenerator.java @@ -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 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 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); } } diff --git a/src/com/vaadin/ui/Button.java b/src/com/vaadin/ui/Button.java index 5574da6e6c..2fb174654f 100644 --- a/src/com/vaadin/ui/Button.java +++ b/src/com/vaadin/ui/Button.java @@ -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() { -- 2.39.5