]> source.dussan.org Git - vaadin-framework.git/commitdiff
#8444 Generate serializers for client to server RPC
authorArtur Signell <artur@vaadin.com>
Thu, 1 Mar 2012 16:12:34 +0000 (18:12 +0200)
committerArtur Signell <artur@vaadin.com>
Thu, 1 Mar 2012 16:14:33 +0000 (18:14 +0200)
12 files changed:
src/com/vaadin/terminal/gwt/client/MouseEventDetails.java
src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java
src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java
src/com/vaadin/terminal/gwt/client/communication/VaadinSerializer.java
src/com/vaadin/terminal/gwt/client/ui/ButtonConnector.java
src/com/vaadin/terminal/gwt/client/ui/VButton.java
src/com/vaadin/terminal/gwt/client/ui/VNativeButton.java
src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java
src/com/vaadin/terminal/gwt/server/JsonCodec.java
src/com/vaadin/terminal/gwt/widgetsetutils/SerializerGenerator.java
src/com/vaadin/terminal/gwt/widgetsetutils/SerializerMapGenerator.java
src/com/vaadin/ui/Button.java

index 260dfa6fff24ddb14e8ff21661f89e1a6bf890ba..57b83701fd2c5fe630da7294bcad66f128e9efa7 100644 (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() {
index fdaaf43abd45477e50dee63df3c7f8926bee3930..6682faa69d05cba6583959979572338208ee80f8 100644 (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
index 21cb94753a236b5aee1852c213f86a9b1c589ea0..3013cc9060287916bc0f53a09e14992ca015bac6 100644 (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));
+            }
         }
     }
 
index c8cf869e0115fc738d9300e0befacce8308e6db9..f201e507e30eebe0ba34df87d85ab5768dd7f5f0 100644 (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 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);
+
 }
index 34b4591eaeaa5bc65dea47cf9d15606a1107401c..c9e69235b1039ed1fb11e623e2d7848a1b29ac62 100644 (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
index 5309d11a04e5a54d066c0e744e459bb6abe63cab..f5093ee47b797e4fcd79a4c78f6598d72849a86a 100644 (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;
     }
index c7036d52d0ccf2f3ac9eca694b872a6ee3213081..a6a50dc8c1ce087b10edc484b9fac9d44806d46b 100644 (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;
     }
index 9143d61aca7db15ceed625f9932d6a028b36ba6b..749c9ece6fd16a7fb81d0dcc05ccd635caf45268 100644 (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,
index 09afea3f741321c69cb5bbe3148d4a8e053e52f2..350a70d9d46e1af52fe12451fc97c9122b5dccab 100644 (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();
index 0beed2abf039ce59e7ab2264f186415f0e88d7bd..2df141f0ed6104137421c9ec1487e046be039146 100644 (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;
index 778ced783ba896f4c3d5c6b304856a5e06a3b0d9..a766d75da741cae8f9e9caf78c9e05319f2a24ef 100644 (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);
         }
     }
 
index 5574da6e6cfddd3e087ee7d5af3414992a22b658..2fb174654f36e754e919aee9d82c0679c0e252c6 100644 (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() {