From e9882b1b664446c855b7b6c04468b4bfbbf314da Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Tue, 28 Feb 2012 18:28:59 +0200 Subject: [PATCH] #8442 Serialize also nested beans, #8441 Allow using static inner classes for communication --- .../vaadin/terminal/gwt/server/JsonCodec.java | 11 +- .../widgetsetutils/SerializerGenerator.java | 109 +++++++++++------ .../SerializerMapGenerator.java | 115 +++++++++++++++--- 3 files changed, 178 insertions(+), 57 deletions(-) diff --git a/src/com/vaadin/terminal/gwt/server/JsonCodec.java b/src/com/vaadin/terminal/gwt/server/JsonCodec.java index d8964f85a6..09afea3f74 100644 --- a/src/com/vaadin/terminal/gwt/server/JsonCodec.java +++ b/src/com/vaadin/terminal/gwt/server/JsonCodec.java @@ -19,7 +19,6 @@ 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.vaadin.terminal.gwt.client.communication.SharedState; /** * Decoder for converting RPC parameters and other values from JSON in transfer @@ -153,9 +152,6 @@ public class JsonCodec implements Serializable { // TODO as undefined type? return combineTypeAndValue(JsonEncoder.VTYPE_UNDEFINED, JSONObject.NULL); - } else if (value instanceof SharedState) { - return combineTypeAndValue(value.getClass().getName(), - encodeObject(value, idMapper)); } else if (value instanceof String[]) { String[] array = (String[]) value; JSONArray jsonArray = new JSONArray(); @@ -181,9 +177,14 @@ public class JsonCodec implements Serializable { Paintable paintable = (Paintable) value; return combineTypeAndValue(JsonEncoder.VTYPE_PAINTABLE, idMapper.getPaintableId(paintable)); - } else { + } else if (getTransportType(value) != JsonEncoder.VTYPE_UNDEFINED) { return combineTypeAndValue(getTransportType(value), String.valueOf(value)); + } else { + // Any object that we do not know how to encode we encode by looping + // through fields + return combineTypeAndValue(value.getClass().getCanonicalName(), + encodeObject(value, idMapper)); } } diff --git a/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerGenerator.java b/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerGenerator.java index d7af52b10d..0beed2abf0 100644 --- a/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerGenerator.java +++ b/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerGenerator.java @@ -5,7 +5,9 @@ package com.vaadin.terminal.gwt.widgetsetutils; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.Date; +import java.util.List; import com.google.gwt.core.client.GWT; import com.google.gwt.core.ext.Generator; @@ -24,6 +26,7 @@ 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.SerializerMap; import com.vaadin.terminal.gwt.client.communication.VaadinSerializer; /** @@ -36,29 +39,27 @@ import com.vaadin.terminal.gwt.client.communication.VaadinSerializer; */ public class SerializerGenerator extends Generator { - private String packageName; - private String beanSerializerClassName; + private static final String SUBTYPE_SEPARATOR = "___"; + private static String beanSerializerPackageName = SerializerMap.class + .getPackage().getName(); @Override public String generate(TreeLogger logger, GeneratorContext context, String beanTypeName) throws UnableToCompleteException { - String beanSerializerTypeName = beanTypeName + "_Serializer"; + JClassType beanType = context.getTypeOracle().findType(beanTypeName); + String beanSerializerClassName = getSerializerSimpleClassName(beanType); try { - TypeOracle typeOracle = context.getTypeOracle(); - - // get classType and save instance variables - JClassType classType = typeOracle.getType(beanTypeName); - packageName = classType.getPackage().getName(); - beanSerializerClassName = classType.getSimpleSourceName() - + "_Serializer"; // Generate class source code - generateClass(logger, context, beanTypeName, beanSerializerTypeName); + generateClass(logger, context, beanType, beanSerializerPackageName, + beanSerializerClassName); } catch (Exception e) { logger.log(TreeLogger.ERROR, "SerializerGenerator failed for " - + beanTypeName, e); + + beanType.getQualifiedSourceName(), e); } // return the fully qualifed name of the class generated - return packageName + "." + beanSerializerClassName; + logger.log(TreeLogger.INFO, "Generated Serializer class " + + getFullyQualifiedSerializerClassName(beanType)); + return getFullyQualifiedSerializerClassName(beanType); } /** @@ -68,17 +69,19 @@ public class SerializerGenerator extends Generator { * Logger object * @param context * Generator context + * @param beanType * @param beanTypeName * bean type for which the serializer is to be generated * @param beanSerializerTypeName * name of the serializer class to generate */ private void generateClass(TreeLogger logger, GeneratorContext context, - String beanTypeName, String beanSerializerTypeName) { + JClassType beanType, String serializerPackageName, + String serializerClassName) { // get print writer that receives the source code PrintWriter printWriter = null; - printWriter = context.tryCreate(logger, packageName, - beanSerializerClassName); + printWriter = context.tryCreate(logger, serializerPackageName, + serializerClassName); // print writer if null, source code has ALREADY been generated if (printWriter == null) { @@ -86,13 +89,14 @@ public class SerializerGenerator extends Generator { } Date date = new Date(); TypeOracle typeOracle = context.getTypeOracle(); - logger.log(Type.DEBUG, "Processing serializable type " + beanTypeName - + "..."); + String beanQualifiedSourceName = beanType.getQualifiedSourceName(); + logger.log(Type.DEBUG, "Processing serializable type " + + beanQualifiedSourceName + "..."); // init composer, set class properties, create source writer ClassSourceFileComposerFactory composer = null; - composer = new ClassSourceFileComposerFactory(packageName, - beanSerializerClassName); + composer = new ClassSourceFileComposerFactory(serializerPackageName, + serializerClassName); composer.addImport(GWT.class.getName()); composer.addImport(JSONArray.class.getName()); // composer.addImport(JSONObject.class.getName()); @@ -106,34 +110,27 @@ public class SerializerGenerator extends Generator { printWriter); sourceWriter.indent(); - sourceWriter.println("public " + beanTypeName + " deserialize(" - + JSONObject.class.getName() + " jsonValue, " + sourceWriter.println("public " + beanQualifiedSourceName + + " deserialize(" + JSONObject.class.getName() + " jsonValue, " + ConnectorMap.class.getName() + " idMapper) {"); sourceWriter.indent(); // VButtonState state = GWT.create(VButtonState.class); - sourceWriter.println(beanTypeName + " state = GWT.create(" - + beanTypeName + ".class);"); - JClassType beanType = typeOracle.findType(beanTypeName); + sourceWriter.println(beanQualifiedSourceName + " state = GWT.create(" + + beanQualifiedSourceName + ".class);"); JClassType objectType = typeOracle.findType("java.lang.Object"); while (!objectType.equals(beanType)) { - 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; - - } + 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.DEBUG, "* Processing field " + fieldName - + " in " + beanTypeName + " (" + beanType.getName() - + ")"); + logger.log( + Type.INFO, + "* Processing field " + fieldName + " in " + + beanQualifiedSourceName + " (" + + beanType.getName() + ")"); String jsonFieldName = "json" + capitalizedFieldName; // JSONArray jsonHeight = (JSONArray) jsonValue.get("height"); @@ -178,7 +175,45 @@ public class SerializerGenerator extends Generator { } + 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; + } + setterMethods.add(method); + } + + return setterMethods; + } + private String decapitalize(String name) { return name.substring(0, 1).toLowerCase() + name.substring(1); } + + private static String getSerializerSimpleClassName(JClassType beanType) { + return getSimpleClassName(beanType) + "_Serializer"; + } + + private static String getSimpleClassName(JClassType type) { + if (type.isMemberType()) { + // Assumed to be static sub class + String baseName = getSimpleClassName(type.getEnclosingType()); + String name = baseName + SUBTYPE_SEPARATOR + + type.getSimpleSourceName(); + return name; + } + return type.getSimpleSourceName(); + } + + public static String getFullyQualifiedSerializerClassName(JClassType type) { + return beanSerializerPackageName + "." + + getSerializerSimpleClassName(type); + } } diff --git a/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerMapGenerator.java b/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerMapGenerator.java index 3536cbd117..2d5e2b704f 100644 --- a/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerMapGenerator.java +++ b/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerMapGenerator.java @@ -6,6 +6,9 @@ package com.vaadin.terminal.gwt.widgetsetutils; import java.io.PrintWriter; import java.util.Date; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; import com.google.gwt.core.ext.Generator; import com.google.gwt.core.ext.GeneratorContext; @@ -13,6 +16,8 @@ import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.TreeLogger.Type; import com.google.gwt.core.ext.UnableToCompleteException; import com.google.gwt.core.ext.typeinfo.JClassType; +import com.google.gwt.core.ext.typeinfo.JMethod; +import com.google.gwt.core.ext.typeinfo.JType; import com.google.gwt.core.ext.typeinfo.TypeOracle; import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; import com.google.gwt.user.rebind.SourceWriter; @@ -38,20 +43,18 @@ public class SerializerMapGenerator extends Generator { try { TypeOracle typeOracle = context.getTypeOracle(); + Set typesNeedingSerializers = findTypesNeedingSerializers( + typeOracle, logger); // get classType and save instance variables JClassType classType = typeOracle.getType(typeName); packageName = classType.getPackage().getName(); className = classType.getSimpleSourceName() + "Impl"; // Generate class source code for SerializerMapImpl - generateClass(logger, context); + generateSerializerMap(typesNeedingSerializers, logger, context); - // Generate serializer classes for each subclass of SharedState - JClassType serializerType = typeOracle.findType(SharedState.class - .getName()); - JClassType[] serializerSubtypes = serializerType.getSubtypes(); SerializerGenerator sg = new SerializerGenerator(); - for (JClassType type : serializerSubtypes) { + for (JClassType type : typesNeedingSerializers) { sg.generate(logger, context, type.getQualifiedSourceName()); } } catch (Exception e) { @@ -65,12 +68,15 @@ public class SerializerMapGenerator extends Generator { /** * Generate source code for WidgetMapImpl * + * @param typesNeedingSerializers + * * @param logger * Logger object * @param context * Generator context */ - private void generateClass(TreeLogger logger, GeneratorContext context) { + private void generateSerializerMap(Set typesNeedingSerializers, + TreeLogger logger, GeneratorContext context) { // get print writer that receives the source code PrintWriter printWriter = null; printWriter = context.tryCreate(logger, packageName, className); @@ -79,12 +85,8 @@ public class SerializerMapGenerator extends Generator { if (printWriter == null) { return; } - logger.log(Type.INFO, "Detecting serializable data types..."); Date date = new Date(); TypeOracle typeOracle = context.getTypeOracle(); - JClassType serializerType = typeOracle.findType(SharedState.class - .getName()); - JClassType[] serializerSubtypes = serializerType.getSubtypes(); // init composer, set class properties, create source writer ClassSourceFileComposerFactory composer = null; @@ -100,15 +102,18 @@ public class SerializerMapGenerator extends Generator { sourceWriter.indent(); // TODO cache serializer instances in a map - for (JClassType type : serializerSubtypes) { + for (JClassType type : typesNeedingSerializers) { sourceWriter.println("if (type.equals(\"" + type.getQualifiedSourceName() + "\")) {"); sourceWriter.indent(); - sourceWriter.println("return GWT.create(" - + type.getQualifiedSourceName() + "_Serializer.class);"); + String serializerName = SerializerGenerator + .getFullyQualifiedSerializerClassName(type); + sourceWriter.println("return GWT.create(" + serializerName + + ".class);"); sourceWriter.outdent(); sourceWriter.println("}"); - logger.log(Type.INFO, "Configured serializer for " + type.getName()); + logger.log(Type.INFO, "Configured serializer (" + serializerName + + ") for " + type.getName()); } sourceWriter .println("throw new RuntimeException(\"No serializer found for class \"+type);"); @@ -125,4 +130,84 @@ public class SerializerMapGenerator extends Generator { + "seconds)"); } + + public Set findTypesNeedingSerializers(TypeOracle typeOracle, + TreeLogger logger) { + logger.log(Type.INFO, "Detecting serializable data types..."); + + HashSet types = new HashSet(); + + // Generate serializer classes for each subclass of SharedState + JClassType serializerType = typeOracle.findType(SharedState.class + .getName()); + JClassType[] serializerSubtypes = serializerType.getSubtypes(); + for (JClassType type : serializerSubtypes) { + types.add(type); + } + + // Add all types used from/in the determined types + for (Object t : types.toArray()) { + findSubTypesNeedingSerializers((JClassType) t, types); + } + logger.log(Type.INFO, "Serializable data types: " + types.toString()); + + return types; + } + + public void findSubTypesNeedingSerializers(JClassType type, + Set serializableTypes) { + // Find all setters and look at their parameter type to determine if a + // new serializer is needed + for (JMethod setterMethod : SerializerGenerator.getSetters(type)) { + // The one and only parameter for the setter + JType setterType = setterMethod.getParameterTypes()[0]; + + if (serializableTypes.contains(setterType)) { + continue; + } + if (serializationHandledByFramework(setterType)) { + continue; + } + + serializableTypes.add(setterType.isClass()); + } + } + + Set> frameworkHandledTypes = new HashSet>(); + { + frameworkHandledTypes.add(String.class); + frameworkHandledTypes.add(Boolean.class); + frameworkHandledTypes.add(Integer.class); + frameworkHandledTypes.add(Float.class); + frameworkHandledTypes.add(Double.class); + frameworkHandledTypes.add(Long.class); + frameworkHandledTypes.add(Enum.class); + frameworkHandledTypes.add(String[].class); + frameworkHandledTypes.add(Object[].class); + frameworkHandledTypes.add(Map.class); + + } + + private boolean serializationHandledByFramework(JType setterType) { + // Some types are handled by the framework at the moment. See #8449 + // This method should be removed at some point. + if (setterType.isArray() != null) { + return true; + } + if (setterType.isEnum() != null) { + return true; + } + if (setterType.isPrimitive() != null) { + return true; + } + + String qualifiedName = setterType.getQualifiedSourceName(); + for (Class cls : frameworkHandledTypes) { + if (qualifiedName.equals(cls.getName())) { + return true; + } + } + + return false; + } } -- 2.39.5