From a7097c68efc075c4f0f77a22b2c40cbf0ff3a589 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Leif=20=C3=85strand?= Date: Mon, 25 Jun 2012 12:37:36 +0300 Subject: [PATCH] Support arrays in RPC and shared state + test (#8655) --- .../gwt/client/communication/JsonDecoder.java | 21 +- .../gwt/client/communication/JsonEncoder.java | 5 + .../vaadin/terminal/gwt/server/JsonCodec.java | 46 ++- .../GeneratedRpcMethodProviderGenerator.java | 21 +- .../widgetsetutils/SerializerGenerator.java | 151 +++++++--- .../SerializerMapGenerator.java | 29 +- .../tests/serialization/SerializerTest.java | 273 ++++++++++++++++++ .../widgetset/client/ComplexTestBean.java | 70 +++++ .../client/SerializerTestConnector.java | 227 +++++++++++++++ .../widgetset/client/SerializerTestRpc.java | 64 ++++ .../widgetset/client/SimpleTestBean.java | 38 +++ .../server/SerializerTestExtension.java | 22 ++ 12 files changed, 891 insertions(+), 76 deletions(-) create mode 100644 tests/testbench/com/vaadin/tests/serialization/SerializerTest.java create mode 100644 tests/testbench/com/vaadin/tests/widgetset/client/ComplexTestBean.java create mode 100644 tests/testbench/com/vaadin/tests/widgetset/client/SerializerTestConnector.java create mode 100644 tests/testbench/com/vaadin/tests/widgetset/client/SerializerTestRpc.java create mode 100644 tests/testbench/com/vaadin/tests/widgetset/client/SimpleTestBean.java create mode 100644 tests/testbench/com/vaadin/tests/widgetset/server/SerializerTestExtension.java diff --git a/src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java b/src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java index 1459d8ee7d..23a2c30cd0 100644 --- a/src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java +++ b/src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java @@ -52,9 +52,7 @@ public class JsonDecoder { } String baseTypeName = type.getBaseTypeName(); - if (baseTypeName.endsWith("[]")) { - return decodeArray(type, (JSONArray) jsonValue, connection); - } else if (Map.class.getName().equals(baseTypeName) + if (Map.class.getName().equals(baseTypeName) || HashMap.class.getName().equals(baseTypeName)) { return decodeMap(type, jsonValue, connection); } else if (List.class.getName().equals(baseTypeName) @@ -78,6 +76,13 @@ public class JsonDecoder { } else if (Boolean.class.getName().equals(baseTypeName)) { // TODO handle properly return Boolean.valueOf(String.valueOf(jsonValue)); + } else if (Byte.class.getName().equals(baseTypeName)) { + // TODO handle properly + return Byte.valueOf(String.valueOf(jsonValue)); + } else if (Character.class.getName().equals(baseTypeName)) { + // TODO handle properly + return Character.valueOf(((JSONString) jsonValue).stringValue() + .charAt(0)); } else if (Connector.class.getName().equals(baseTypeName)) { return ConnectorMap.get(connection).getConnector( ((JSONString) jsonValue).stringValue()); @@ -180,16 +185,6 @@ public class JsonDecoder { return map; } - private static Object[] decodeArray(Type type, JSONArray jsonArray, - ApplicationConnection connection) { - String arrayTypeName = type.getBaseTypeName(); - String chldTypeName = arrayTypeName.substring(0, - arrayTypeName.length() - 2); - List list = decodeList(new Type(chldTypeName, null), jsonArray, - connection); - return list.toArray(new Object[list.size()]); - } - private static List decodeList(Type type, JSONArray jsonArray, ApplicationConnection connection) { List tokens = new ArrayList(); diff --git a/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java b/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java index cb7dbe5e72..925f0b6272 100644 --- a/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java +++ b/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java @@ -13,6 +13,7 @@ import java.util.Set; import com.google.gwt.json.client.JSONArray; import com.google.gwt.json.client.JSONBoolean; import com.google.gwt.json.client.JSONNull; +import com.google.gwt.json.client.JSONNumber; import com.google.gwt.json.client.JSONObject; import com.google.gwt.json.client.JSONString; import com.google.gwt.json.client.JSONValue; @@ -72,6 +73,10 @@ public class JsonEncoder { return new JSONString((String) value); } else if (value instanceof Boolean) { return JSONBoolean.getInstance((Boolean) value); + } else if (value instanceof Byte) { + return new JSONNumber((Byte) value); + } else if (value instanceof Character) { + return new JSONString(String.valueOf(value)); } else if (value instanceof Object[]) { return encodeObjectArray((Object[]) value, restrictToInternalTypes, connection); diff --git a/src/com/vaadin/terminal/gwt/server/JsonCodec.java b/src/com/vaadin/terminal/gwt/server/JsonCodec.java index 71e4727164..4fb7681e4a 100644 --- a/src/com/vaadin/terminal/gwt/server/JsonCodec.java +++ b/src/com/vaadin/terminal/gwt/server/JsonCodec.java @@ -8,6 +8,7 @@ import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.io.Serializable; +import java.lang.reflect.Array; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; @@ -82,6 +83,10 @@ public class JsonCodec implements Serializable { public static boolean isInternalType(Type type) { if (type instanceof Class && ((Class) type).isPrimitive()) { + if (type == byte.class || type == char.class) { + // Almost all primitive types are handled internally + return false; + } // All primitive types are handled internally return true; } else if (type == UidlValue.class) { @@ -123,6 +128,16 @@ public class JsonCodec implements Serializable { // Try to decode object using fields if (value == JSONObject.NULL) { return null; + } else if (targetType == byte.class || targetType == Byte.class) { + return Byte.valueOf(String.valueOf(value)); + } else if (targetType == char.class || targetType == Character.class) { + return Character.valueOf(String.valueOf(value).charAt(0)); + } else if (targetType instanceof Class + && ((Class) targetType).isArray()) { + // Legacy Object[] and String[] handled elsewhere, this takes care + // of generic arrays + return decodeArray((Class) targetType, (JSONArray) value, + application); } else if (targetType == JSONObject.class || targetType == JSONArray.class) { return value; @@ -131,6 +146,18 @@ public class JsonCodec implements Serializable { } } + private static Object decodeArray(Class targetType, JSONArray value, + Application application) throws JSONException { + Class componentType = targetType.getComponentType(); + Object array = Array.newInstance(componentType, value.length()); + for (int i = 0; i < value.length(); i++) { + Object decodedValue = decodeInternalOrCustomType(componentType, + value.get(i), application); + Array.set(array, i, decodedValue); + } + return array; + } + /** * Decodes a value that is of an internal type. *

@@ -203,7 +230,7 @@ public class JsonCodec implements Serializable { return application.getConnector(stringValue); } - // Standard Java types + // Legacy types if (JsonEncoder.VTYPE_STRING.equals(transportType)) { return stringValue; @@ -494,14 +521,17 @@ public class JsonCodec implements Serializable { return value; } else if (value instanceof Number) { return value; + } else if (value instanceof Character) { + // Character is not a Number + return value; } else if (value instanceof Collection) { Collection collection = (Collection) value; JSONArray jsonArray = encodeCollection(valueType, collection, application); return jsonArray; - } else if (value instanceof Object[]) { - Object[] array = (Object[]) value; - JSONArray jsonArray = encodeArrayContents(array, application); + } else if (valueType instanceof Class + && ((Class) valueType).isArray()) { + JSONArray jsonArray = encodeArrayContents(value, application); return jsonArray; } else if (value instanceof Map) { Object jsonMap = encodeMap(valueType, (Map) value, @@ -604,11 +634,13 @@ public class JsonCodec implements Serializable { return e.name(); } - private static JSONArray encodeArrayContents(Object[] array, + private static JSONArray encodeArrayContents(Object array, Application application) throws JSONException { JSONArray jsonArray = new JSONArray(); - for (Object o : array) { - jsonArray.put(encode(o, null, null, application)); + Class componentType = array.getClass().getComponentType(); + for (int i = 0; i < Array.getLength(array); i++) { + jsonArray.put(encode(Array.get(array, i), null, componentType, + application)); } return jsonArray; } diff --git a/src/com/vaadin/terminal/gwt/widgetsetutils/GeneratedRpcMethodProviderGenerator.java b/src/com/vaadin/terminal/gwt/widgetsetutils/GeneratedRpcMethodProviderGenerator.java index ba3dcd85b9..b1d69b178b 100644 --- a/src/com/vaadin/terminal/gwt/widgetsetutils/GeneratedRpcMethodProviderGenerator.java +++ b/src/com/vaadin/terminal/gwt/widgetsetutils/GeneratedRpcMethodProviderGenerator.java @@ -146,8 +146,8 @@ public class GeneratedRpcMethodProviderGenerator extends Generator { if (i != 0) { sourceWriter.print(", "); } - sourceWriter.print("(" - + parameterType.getQualifiedSourceName() + String parameterTypeName = getBoxedTypeName(parameterType); + sourceWriter.print("(" + parameterTypeName + ") parameters[" + i + "]"); } sourceWriter.println(");"); @@ -179,13 +179,7 @@ public class GeneratedRpcMethodProviderGenerator extends Generator { } public static void writeTypeCreator(SourceWriter sourceWriter, JType type) { - String typeName; - if (type.isPrimitive() != null) { - // Used boxed types for primitives - typeName = type.isPrimitive().getQualifiedBoxedSourceName(); - } else { - typeName = type.getErasedType().getQualifiedBinaryName(); - } + String typeName = getBoxedTypeName(type); sourceWriter.print("new Type(\"" + typeName + "\", "); JParameterizedType parameterized = type.isParameterized(); if (parameterized != null) { @@ -202,6 +196,15 @@ public class GeneratedRpcMethodProviderGenerator extends Generator { sourceWriter.print(")"); } + public static String getBoxedTypeName(JType type) { + if (type.isPrimitive() != null) { + // Used boxed types for primitives + return type.isPrimitive().getQualifiedBoxedSourceName(); + } else { + return type.getErasedType().getQualifiedSourceName(); + } + } + private String getInvokeMethodName(JClassType type) { return "invoke" + type.getQualifiedSourceName().replaceAll("\\.", "_"); } diff --git a/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerGenerator.java b/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerGenerator.java index c32b54ff1c..1951f8ba40 100644 --- a/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerGenerator.java +++ b/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerGenerator.java @@ -6,7 +6,6 @@ package com.vaadin.terminal.gwt.widgetsetutils; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Date; import java.util.HashSet; import java.util.List; @@ -16,13 +15,15 @@ import com.google.gwt.core.ext.GeneratorContext; 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.JArrayType; import com.google.gwt.core.ext.typeinfo.JClassType; import com.google.gwt.core.ext.typeinfo.JEnumConstant; import com.google.gwt.core.ext.typeinfo.JEnumType; import com.google.gwt.core.ext.typeinfo.JMethod; import com.google.gwt.core.ext.typeinfo.JPrimitiveType; import com.google.gwt.core.ext.typeinfo.JType; -import com.google.gwt.core.ext.typeinfo.TypeOracle; +import com.google.gwt.core.ext.typeinfo.TypeOracleException; +import com.google.gwt.json.client.JSONArray; import com.google.gwt.json.client.JSONObject; import com.google.gwt.json.client.JSONString; import com.google.gwt.json.client.JSONValue; @@ -46,26 +47,32 @@ import com.vaadin.terminal.gwt.client.communication.SerializerMap; public class SerializerGenerator extends Generator { private static final String SUBTYPE_SEPARATOR = "___"; - private static String beanSerializerPackageName = SerializerMap.class + private static String serializerPackageName = SerializerMap.class .getPackage().getName(); @Override public String generate(TreeLogger logger, GeneratorContext context, - String beanTypeName) throws UnableToCompleteException { - JClassType beanType = context.getTypeOracle().findType(beanTypeName); - String beanSerializerClassName = getSerializerSimpleClassName(beanType); + String typeName) throws UnableToCompleteException { + JClassType type; + try { + type = (JClassType) context.getTypeOracle().parse(typeName); + } catch (TypeOracleException e1) { + logger.log(Type.ERROR, "Could not find type " + typeName, e1); + throw new UnableToCompleteException(); + } + String serializerClassName = getSerializerSimpleClassName(type); try { // Generate class source code - generateClass(logger, context, beanType, beanSerializerPackageName, - beanSerializerClassName); + generateClass(logger, context, type, serializerPackageName, + serializerClassName); } catch (Exception e) { logger.log(TreeLogger.ERROR, "SerializerGenerator failed for " - + beanType.getQualifiedSourceName(), e); + + type.getQualifiedSourceName(), e); throw new UnableToCompleteException(); } // return the fully qualifed name of the class generated - return getFullyQualifiedSerializerClassName(beanType); + return getFullyQualifiedSerializerClassName(type); } /** @@ -75,7 +82,7 @@ public class SerializerGenerator extends Generator { * Logger object * @param context * Generator context - * @param beanType + * @param type * @param beanTypeName * bean type for which the serializer is to be generated * @param beanSerializerTypeName @@ -83,7 +90,7 @@ public class SerializerGenerator extends Generator { * @throws UnableToCompleteException */ private void generateClass(TreeLogger logger, GeneratorContext context, - JClassType beanType, String serializerPackageName, + JClassType type, String serializerPackageName, String serializerClassName) throws UnableToCompleteException { // get print writer that receives the source code PrintWriter printWriter = null; @@ -94,13 +101,12 @@ public class SerializerGenerator extends Generator { if (printWriter == null) { return; } - boolean isEnum = (beanType.isEnum() != null); + boolean isEnum = (type.isEnum() != null); + boolean isArray = (type.isArray() != null); - Date date = new Date(); - TypeOracle typeOracle = context.getTypeOracle(); - String beanQualifiedSourceName = beanType.getQualifiedSourceName(); + String qualifiedSourceName = type.getQualifiedSourceName(); logger.log(Type.DEBUG, "Processing serializable type " - + beanQualifiedSourceName + "..."); + + qualifiedSourceName + "..."); // init composer, set class properties, create source writer ClassSourceFileComposerFactory composer = null; @@ -115,12 +121,12 @@ public class SerializerGenerator extends Generator { composer.addImport(JsonDecoder.class.getName()); // composer.addImport(VaadinSerializer.class.getName()); - if (isEnum) { + if (isEnum || isArray) { composer.addImplementedInterface(JSONSerializer.class.getName() - + "<" + beanQualifiedSourceName + ">"); + + "<" + qualifiedSourceName + ">"); } else { composer.addImplementedInterface(DiffJSONSerializer.class.getName() - + "<" + beanQualifiedSourceName + ">"); + + "<" + qualifiedSourceName + ">"); } SourceWriter sourceWriter = composer.createSourceWriter(context, @@ -132,17 +138,19 @@ public class SerializerGenerator extends Generator { // public JSONValue serialize(Object value, // ApplicationConnection connection) { sourceWriter.println("public " + JSONValue.class.getName() - + " serialize(" + beanQualifiedSourceName + " value, " + + " serialize(" + qualifiedSourceName + " value, " + ApplicationConnection.class.getName() + " connection) {"); sourceWriter.indent(); // MouseEventDetails castedValue = (MouseEventDetails) value; - sourceWriter.println(beanQualifiedSourceName + " castedValue = (" - + beanQualifiedSourceName + ") value;"); + sourceWriter.println(qualifiedSourceName + " castedValue = (" + + qualifiedSourceName + ") value;"); if (isEnum) { - writeEnumSerializer(logger, sourceWriter, beanType); + writeEnumSerializer(logger, sourceWriter, type); + } else if (isArray) { + writeArraySerializer(logger, sourceWriter, type.isArray()); } else { - writeBeanSerializer(logger, sourceWriter, beanType); + writeBeanSerializer(logger, sourceWriter, type); } // } sourceWriter.outdent(); @@ -152,14 +160,14 @@ public class SerializerGenerator extends Generator { // Updater // public void update(T target, Type type, JSONValue jsonValue, // ApplicationConnection connection); - if (!isEnum) { - sourceWriter.println("public void update(" - + beanQualifiedSourceName + " target, Type type, " - + JSONValue.class.getName() + " jsonValue, " - + ApplicationConnection.class.getName() + " connection) {"); + if (!isEnum && !isArray) { + sourceWriter.println("public void update(" + qualifiedSourceName + + " target, Type type, " + JSONValue.class.getName() + + " jsonValue, " + ApplicationConnection.class.getName() + + " connection) {"); sourceWriter.indent(); - writeBeanDeserializer(logger, sourceWriter, beanType); + writeBeanDeserializer(logger, sourceWriter, type); sourceWriter.outdent(); sourceWriter.println("}"); @@ -168,18 +176,19 @@ public class SerializerGenerator extends Generator { // Deserializer // T deserialize(Type type, JSONValue jsonValue, ApplicationConnection // connection); - sourceWriter.println("public " + beanQualifiedSourceName + sourceWriter.println("public " + qualifiedSourceName + " deserialize(Type type, " + JSONValue.class.getName() + " jsonValue, " + ApplicationConnection.class.getName() + " connection) {"); sourceWriter.indent(); if (isEnum) { - writeEnumDeserializer(logger, sourceWriter, beanType.isEnum()); + writeEnumDeserializer(logger, sourceWriter, type.isEnum()); + } else if (isArray) { + writeArrayDeserializer(logger, sourceWriter, type.isArray()); } else { - sourceWriter.println(beanQualifiedSourceName - + " target = GWT.create(" + beanQualifiedSourceName - + ".class);"); + sourceWriter.println(qualifiedSourceName + " target = GWT.create(" + + qualifiedSourceName + ".class);"); sourceWriter .println("update(target, type, jsonValue, connection);"); // return target; @@ -195,7 +204,7 @@ public class SerializerGenerator extends Generator { // commit generated class context.commit(logger, printWriter); logger.log(TreeLogger.INFO, "Generated Serializer class " - + getFullyQualifiedSerializerClassName(beanType)); + + getFullyQualifiedSerializerClassName(type)); } private void writeEnumDeserializer(TreeLogger logger, @@ -214,6 +223,43 @@ public class SerializerGenerator extends Generator { sourceWriter.println("return null;"); } + private void writeArrayDeserializer(TreeLogger logger, + SourceWriter sourceWriter, JArrayType type) { + JType leafType = type.getLeafType(); + int rank = type.getRank(); + + sourceWriter.println(JSONArray.class.getName() + + " jsonArray = jsonValue.isArray();"); + + // Type value = new Type[jsonArray.size()][][]; + sourceWriter.print(type.getQualifiedSourceName() + " value = new " + + leafType.getQualifiedSourceName() + "[jsonArray.size()]"); + for (int i = 1; i < rank; i++) { + sourceWriter.print("[]"); + } + sourceWriter.println(";"); + + sourceWriter.println("for(int i = 0 ; i < value.length; i++) {"); + sourceWriter.indent(); + + JType componentType = type.getComponentType(); + + sourceWriter.print("value[i] = (" + + GeneratedRpcMethodProviderGenerator + .getBoxedTypeName(componentType) + ") " + + JsonDecoder.class.getName() + ".decodeValue("); + GeneratedRpcMethodProviderGenerator.writeTypeCreator(sourceWriter, + componentType); + sourceWriter.print(", jsonArray.get(i), null, connection)"); + + sourceWriter.println(";"); + + sourceWriter.outdent(); + sourceWriter.println("}"); + + sourceWriter.println("return value;"); + } + private void writeBeanDeserializer(TreeLogger logger, SourceWriter sourceWriter, JClassType beanType) { String beanQualifiedSourceName = beanType.getQualifiedSourceName(); @@ -281,6 +327,23 @@ public class SerializerGenerator extends Generator { + "(castedValue.name());"); } + private void writeArraySerializer(TreeLogger logger, + SourceWriter sourceWriter, JArrayType array) { + sourceWriter.println(JSONArray.class.getName() + " values = new " + + JSONArray.class.getName() + "();"); + JType componentType = array.getComponentType(); + // JPrimitiveType primitive = componentType.isPrimitive(); + sourceWriter.println("for (int i = 0; i < castedValue.length; i++) {"); + sourceWriter.indent(); + sourceWriter.print("values.set(i, "); + sourceWriter.print(JsonEncoder.class.getName() + + ".encode(castedValue[i], false, connection)"); + sourceWriter.println(");"); + sourceWriter.outdent(); + sourceWriter.println("}"); + sourceWriter.println("return values;"); + } + private void writeBeanSerializer(TreeLogger logger, SourceWriter sourceWriter, JClassType beanType) throws UnableToCompleteException { @@ -373,10 +436,15 @@ public class SerializerGenerator extends Generator { return getSimpleClassName(beanType) + "_Serializer"; } - private static String getSimpleClassName(JClassType type) { - if (type.isMemberType()) { + private static String getSimpleClassName(JType type) { + JArrayType arrayType = type.isArray(); + if (arrayType != null) { + return "Array" + getSimpleClassName(arrayType.getComponentType()); + } + JClassType classType = type.isClass(); + if (classType != null && classType.isMemberType()) { // Assumed to be static sub class - String baseName = getSimpleClassName(type.getEnclosingType()); + String baseName = getSimpleClassName(classType.getEnclosingType()); String name = baseName + SUBTYPE_SEPARATOR + type.getSimpleSourceName(); return name; @@ -385,7 +453,6 @@ public class SerializerGenerator extends Generator { } public static String getFullyQualifiedSerializerClassName(JClassType type) { - return beanSerializerPackageName + "." - + getSerializerSimpleClassName(type); + return serializerPackageName + "." + getSerializerSimpleClassName(type); } } diff --git a/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerMapGenerator.java b/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerMapGenerator.java index 2688775435..5e151323a0 100644 --- a/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerMapGenerator.java +++ b/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerMapGenerator.java @@ -17,6 +17,7 @@ import com.google.gwt.core.ext.GeneratorContext; 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.JArrayType; import com.google.gwt.core.ext.typeinfo.JClassType; import com.google.gwt.core.ext.typeinfo.JMethod; import com.google.gwt.core.ext.typeinfo.JParameterizedType; @@ -96,6 +97,10 @@ public class SerializerMapGenerator extends Generator { JClassType javaSerializable = typeOracle.findType(Serializable.class .getName()); for (JClassType type : typesNeedingSerializers) { + if (type.isArray() != null) { + // Don't check for arrays + continue; + } boolean serializable = type.isAssignableTo(javaSerializable); if (!serializable) { logger.log( @@ -166,8 +171,15 @@ public class SerializerMapGenerator extends Generator { // TODO cache serializer instances in a map for (JClassType type : typesNeedingSerializers) { - sourceWriter.println("if (type.equals(\"" - + type.getQualifiedBinaryName() + "\")) {"); + sourceWriter.print("if (type.equals(\"" + + type.getQualifiedSourceName() + "\")"); + if (type instanceof JArrayType) { + // Also add binary name to support encoding based on + // object.getClass().getName() + sourceWriter.print("||type.equals(\"" + type.getJNISignature() + + "\")"); + } + sourceWriter.println(") {"); sourceWriter.indent(); String serializerName = SerializerGenerator .getFullyQualifiedSerializerClassName(type); @@ -277,6 +289,14 @@ public class SerializerMapGenerator extends Generator { serializableTypes.add(typeClass); findSubTypesNeedingSerializers(typeClass, serializableTypes); } + + // Generate (n-1)-dimensional array serializer for n-dimensional array + JArrayType arrayType = type.isArray(); + if (arrayType != null) { + serializableTypes.add(arrayType); + addTypeIfNeeded(serializableTypes, arrayType.getComponentType()); + } + } Set> frameworkHandledTypes = new HashSet>(); @@ -293,15 +313,14 @@ public class SerializerMapGenerator extends Generator { frameworkHandledTypes.add(Map.class); frameworkHandledTypes.add(List.class); frameworkHandledTypes.add(Set.class); + frameworkHandledTypes.add(Byte.class); + frameworkHandledTypes.add(Character.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.isPrimitive() != null) { return true; } diff --git a/tests/testbench/com/vaadin/tests/serialization/SerializerTest.java b/tests/testbench/com/vaadin/tests/serialization/SerializerTest.java new file mode 100644 index 0000000000..0f7f5cca32 --- /dev/null +++ b/tests/testbench/com/vaadin/tests/serialization/SerializerTest.java @@ -0,0 +1,273 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.tests.serialization; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import com.vaadin.annotations.Widgetset; +import com.vaadin.terminal.WrappedRequest; +import com.vaadin.terminal.gwt.client.Connector; +import com.vaadin.tests.components.AbstractTestRoot; +import com.vaadin.tests.util.Log; +import com.vaadin.tests.widgetset.client.ComplexTestBean; +import com.vaadin.tests.widgetset.client.SerializerTestRpc; +import com.vaadin.tests.widgetset.client.SimpleTestBean; +import com.vaadin.tests.widgetset.server.SerializerTestExtension; + +@Widgetset("com.vaadin.tests.widgetset.TestingWidgetSet") +public class SerializerTest extends AbstractTestRoot { + + private Log log = new Log(40); + + @Override + protected void setup(WrappedRequest request) { + final SerializerTestExtension testExtension = new SerializerTestExtension(); + addExtension(testExtension); + addComponent(log); + + SerializerTestRpc rpc = testExtension + .getRpcProxy(SerializerTestRpc.class); + rpc.sendBoolean(true, Boolean.FALSE, new boolean[] { true, true, false, + true, false, false }); + rpc.sendByte((byte) 5, Byte.valueOf((byte) -12), new byte[] { 3, 1, 2 }); + rpc.sendChar('∫', Character.valueOf('å'), "aBcD".toCharArray()); + rpc.sendInt(Integer.MAX_VALUE, Integer.valueOf(0), new int[] { 5, 7 }); + rpc.sendLong(577431841358l, Long.valueOf(0), new long[] { + -57841235865l, 57 }); + rpc.sendFloat(3.14159f, Float.valueOf(Math.nextUp(1)), new float[] { + 57, 0, -12 }); + rpc.sendDouble(Math.PI, Double.valueOf(-Math.E), new double[] { + Double.MAX_VALUE, Double.MIN_VALUE }); + rpc.sendString("This is a tesing string ‡"); + rpc.sendConnector(this); + rpc.sendBean( + new ComplexTestBean(new SimpleTestBean(0), + new SimpleTestBean(1), Arrays.asList( + new SimpleTestBean(3), new SimpleTestBean(4)), + 5), new SimpleTestBean(6), + new SimpleTestBean[] { new SimpleTestBean(7) }); + rpc.sendNull("Not null", null); + rpc.sendNestedArray(new int[][] { { 5 }, { 7 } }, + new SimpleTestBean[][] { { new SimpleTestBean(4), + new SimpleTestBean(2) } }); + rpc.sendList(Arrays.asList(5, 8, -234), Arrays. asList(this, + testExtension), Arrays.asList(new SimpleTestBean(234), + new SimpleTestBean(-568))); + // Disabled because of #8861 + // rpc.sendArrayList( + // Arrays.asList(new int[] { 1, 2 }, new int[] { 3, 4 }), + // Arrays.asList(new Integer[] { 5, 6 }, new Integer[] { 7, 8 }), + // Collections + // .singletonList(new SimpleTestBean[] { new SimpleTestBean( + // 7) })); + // Disabled because of #8861 + // rpc.sendListArray( + // new List[] { Arrays.asList(1, 2), Arrays.asList(3, 4) }, + // new List[] { Collections.singletonList(new SimpleTestBean(-1)) }); + rpc.sendSet(new HashSet(Arrays.asList(4, 7, 12)), Collections + .singleton((Connector) this), new HashSet( + Arrays.asList(new SimpleTestBean(1), new SimpleTestBean(2)))); + + rpc.sendMap(new HashMap() { + { + put("1", new SimpleTestBean(1)); + put("2", new SimpleTestBean(2)); + } + }, new HashMap() { + { + put(testExtension, true); + put(getRoot(), false); + } + }, new HashMap() { + { + put(5, testExtension); + put(10, getRoot()); + } + }, new HashMap() { + { + put(new SimpleTestBean(5), new SimpleTestBean(-5)); + put(new SimpleTestBean(-4), new SimpleTestBean(4)); + } + }); + rpc.sendWrappedGenerics(new HashMap, Map>>() { + { + put(Collections.singleton(new SimpleTestBean(42)), + new HashMap>() { + { + put(1, Arrays.asList(new SimpleTestBean(1), + new SimpleTestBean(3))); + } + }); + } + }); + + testExtension.registerRpc(new SerializerTestRpc() { + public void sendBoolean(boolean value, Boolean boxedValue, + boolean[] array) { + log.log("sendBoolean: " + value + ", " + boxedValue + ", " + + Arrays.toString(array)); + } + + public void sendByte(byte value, Byte boxedValue, byte[] array) { + log.log("sendByte: " + value + ", " + boxedValue + ", " + + Arrays.toString(array)); + } + + public void sendChar(char value, Character boxedValue, char[] array) { + log.log("sendChar: " + value + ", " + boxedValue + ", " + + Arrays.toString(array)); + } + + public void sendInt(int value, Integer boxedValue, int[] array) { + log.log("sendInt: " + value + ", " + boxedValue + ", " + + Arrays.toString(array)); + } + + public void sendLong(long value, Long boxedValue, long[] array) { + log.log("sendLong: " + value + ", " + boxedValue + ", " + + Arrays.toString(array)); + } + + public void sendFloat(float value, Float boxedValue, float[] array) { + log.log("sendFloat: " + value + ", " + boxedValue + ", " + + Arrays.toString(array)); + } + + public void sendDouble(double value, Double boxedValue, + double[] array) { + log.log("sendDouble: " + value + ", " + boxedValue + ", " + + Arrays.toString(array)); + } + + public void sendString(String value) { + log.log("sendString: " + value); + } + + public void sendConnector(Connector connector) { + log.log("sendConnector: " + connector.getClass().getName()); + } + + public void sendBean(ComplexTestBean complexBean, + SimpleTestBean simpleBean, SimpleTestBean[] array) { + log.log("sendBean: " + complexBean + ", " + simpleBean + ", " + + Arrays.toString(array)); + } + + public void sendNull(String value1, String value2) { + log.log("sendNull: " + value1 + ", " + value2); + } + + public void sendNestedArray(int[][] nestedIntArray, + SimpleTestBean[][] nestedBeanArray) { + log.log("sendNestedArray: " + + Arrays.deepToString(nestedIntArray) + ", " + + Arrays.deepToString(nestedBeanArray)); + } + + public void sendList(List intList, + List connectorList, List beanList) { + log.log("sendList: " + intList + ", " + + connectorCollectionToString(connectorList) + ", " + + beanList); + } + + private String connectorCollectionToString( + Collection collection) { + StringBuilder sb = new StringBuilder(); + + for (Connector connector : collection) { + if (sb.length() != 0) { + sb.append(", "); + } + sb.append(connector.getClass()); + } + + String string = sb.toString(); + return string; + } + + public void sendArrayList(List primitiveArrayList, + List objectArrayList, + List beanArrayList) { + log.log("sendArrayList: " + primitiveArrayList + ", " + + objectArrayList + ", " + beanArrayList); + } + + public void sendListArray(List[] objectListArray, + List[] beanListArray) { + log.log("sendArrayList: " + Arrays.toString(objectListArray) + + ", " + Arrays.toString(beanListArray)); + } + + public void sendSet(Set intSet, + Set connectorSet, Set beanSet) { + log.log("sendSet: " + intSet + ", " + + connectorCollectionToString(connectorSet) + ", " + + beanSet); + } + + public void sendMap(Map stringMap, + Map connectorMap, + Map intMap, + Map beanMap) { + StringBuilder sb = new StringBuilder(); + for (Entry entry : connectorMap.entrySet()) { + if (sb.length() == 0) { + sb.append('['); + } else { + sb.append(", "); + } + sb.append(entry.getKey().getClass().getName()); + sb.append('='); + sb.append(entry.getValue()); + } + sb.append(']'); + String connectorMapString = sb.toString(); + + sb = new StringBuilder(); + for (Entry entry : intMap.entrySet()) { + if (sb.length() == 0) { + sb.append('['); + } else { + sb.append(", "); + } + sb.append(entry.getKey()); + sb.append('='); + sb.append(entry.getValue().getClass().getName()); + } + sb.append(']'); + String intMapString = sb.toString(); + + log.log("sendMap: " + stringMap + ", " + connectorMapString + + ", " + intMapString + ", " + beanMap); + } + + public void sendWrappedGenerics( + Map, Map>> generics) { + log.log("sendWrappedGenerics: " + generics.toString()); + } + + }); + } + + @Override + protected String getTestDescription() { + return "Test for lots of different cases of encoding and decoding variuos data types"; + } + + @Override + protected Integer getTicketNumber() { + return Integer.valueOf(8655); + } + +} diff --git a/tests/testbench/com/vaadin/tests/widgetset/client/ComplexTestBean.java b/tests/testbench/com/vaadin/tests/widgetset/client/ComplexTestBean.java new file mode 100644 index 0000000000..e465ee2624 --- /dev/null +++ b/tests/testbench/com/vaadin/tests/widgetset/client/ComplexTestBean.java @@ -0,0 +1,70 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.tests.widgetset.client; + +import java.util.List; + +import com.vaadin.terminal.gwt.client.communication.SharedState; + +@SuppressWarnings("javadoc") +public class ComplexTestBean extends SharedState { + private SimpleTestBean innerBean1; + private SimpleTestBean innerBean2; + private List innerBeanCollection; + private int privimite; + + public ComplexTestBean() { + // Default + } + + public ComplexTestBean(SimpleTestBean innerBean1, + SimpleTestBean innerBean2, + List innerBeanCollection, int privimite) { + this.innerBean1 = innerBean1; + this.innerBean2 = innerBean2; + this.innerBeanCollection = innerBeanCollection; + this.privimite = privimite; + } + + public SimpleTestBean getInnerBean1() { + return innerBean1; + } + + public void setInnerBean1(SimpleTestBean innerBean) { + innerBean1 = innerBean; + } + + public SimpleTestBean getInnerBean2() { + return innerBean2; + } + + public void setInnerBean2(SimpleTestBean innerBean2) { + this.innerBean2 = innerBean2; + } + + public List getInnerBeanCollection() { + return innerBeanCollection; + } + + public void setInnerBeanCollection(List innerBeanCollection) { + this.innerBeanCollection = innerBeanCollection; + } + + public int getPrivimite() { + return privimite; + } + + public void setPrivimite(int privimite) { + this.privimite = privimite; + } + + @Override + public String toString() { + return "ComplexTestBean [innerBean1=" + innerBean1 + ", innerBean2=" + + innerBean2 + ", innerBeanCollection=" + innerBeanCollection + + ", privimite=" + privimite + "]"; + } + +} diff --git a/tests/testbench/com/vaadin/tests/widgetset/client/SerializerTestConnector.java b/tests/testbench/com/vaadin/tests/widgetset/client/SerializerTestConnector.java new file mode 100644 index 0000000000..72bfbe285a --- /dev/null +++ b/tests/testbench/com/vaadin/tests/widgetset/client/SerializerTestConnector.java @@ -0,0 +1,227 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.tests.widgetset.client; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import com.vaadin.terminal.gwt.client.Connector; +import com.vaadin.terminal.gwt.client.communication.RpcProxy; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; +import com.vaadin.terminal.gwt.client.extensions.AbstractExtensionConnector; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.tests.widgetset.server.SerializerTestExtension; + +@Connect(SerializerTestExtension.class) +public class SerializerTestConnector extends AbstractExtensionConnector { + + private SerializerTestRpc rpc = RpcProxy.create(SerializerTestRpc.class, + this); + + public SerializerTestConnector() { + registerRpc(SerializerTestRpc.class, new SerializerTestRpc() { + public void sendWrappedGenerics( + Map, Map>> generics) { + Map, Map>> updated = new HashMap, Map>>(); + + SimpleTestBean firstValue = generics.values().iterator().next() + .get(Integer.valueOf(1)).get(0); + Set key = new HashSet(Arrays + .asList(firstValue)); + + Map> value = new HashMap>(); + Set firstKeyValue = generics.keySet() + .iterator().next(); + value.put(Integer.valueOf(1), new ArrayList( + firstKeyValue)); + + updated.put(key, value); + + rpc.sendWrappedGenerics(updated); + } + + public void sendString(String value) { + char[] chars = value.toCharArray(); + Arrays.sort(chars); + rpc.sendString(new String(chars)); + } + + public void sendSet(Set intSet, + Set connectorSet, Set beanSet) { + + beanSet.iterator().next().setValue(intSet.size()); + Set updatedIntSet = new HashSet(); + + for (Integer integer : intSet) { + updatedIntSet.add(Integer.valueOf(-integer.intValue())); + } + rpc.sendSet(updatedIntSet, + Collections.singleton(getRootConnector()), beanSet); + } + + public void sendNestedArray(int[][] nestedIntArray, + SimpleTestBean[][] nestedBeanArray) { + rpc.sendNestedArray(new int[][] { { nestedIntArray[1][0], + nestedIntArray[0][0] } }, new SimpleTestBean[][] { + { nestedBeanArray[0][1] }, { nestedBeanArray[0][0] } }); + } + + public void sendMap(Map stringMap, + Map connectorMap, + Map intMap, + Map beanMap) { + Map updatedBeanMap = new HashMap(); + for (Entry entry : beanMap + .entrySet()) { + updatedBeanMap.put(entry.getValue(), entry.getKey()); + } + + rpc.sendMap(Collections.singletonMap("a", stringMap.get("b")), + Collections.singletonMap(getThisConnector(), + connectorMap.get(getRootConnector())), + Collections.singletonMap( + Integer.valueOf(stringMap.size()), + getThisConnector()), updatedBeanMap); + } + + public void sendLong(long value, Long boxedValue, long[] array) { + rpc.sendLong(array[0], Long.valueOf(value), new long[] { + array[1], boxedValue.longValue() }); + } + + public void sendList(List intList, + List connectorList, List beanList) { + Collections.sort(intList); + Collections.reverse(beanList); + rpc.sendList(intList, + Arrays.asList(getThisConnector(), getRootConnector()), + beanList); + } + + public void sendInt(int value, Integer boxedValue, int[] array) { + rpc.sendInt(array.length, Integer.valueOf(array[0]), new int[] { + value, boxedValue.intValue() }); + } + + public void sendFloat(float value, Float boxedValue, float[] array) { + Arrays.sort(array); + rpc.sendFloat(boxedValue.floatValue(), Float.valueOf(value), + array); + } + + public void sendDouble(double value, Double boxedValue, + double[] array) { + rpc.sendDouble(value + boxedValue.doubleValue(), + Double.valueOf(value - boxedValue.doubleValue()), + new double[] { array.length, array[0], array[1] }); + } + + public void sendConnector(Connector connector) { + rpc.sendConnector(getThisConnector()); + } + + public void sendChar(char value, Character boxedValue, char[] array) { + rpc.sendChar(Character.toUpperCase(boxedValue.charValue()), + Character.valueOf(value), new String(array) + .toLowerCase().toCharArray()); + } + + public void sendByte(byte value, Byte boxedValue, byte[] array) { + // There will most certainly be a bug that is not discovered + // because this particular method doesn't do anything with it's + // values... + rpc.sendByte(value, boxedValue, array); + } + + public void sendBoolean(boolean value, Boolean boxedValue, + boolean[] array) { + boolean[] inverseArray = new boolean[array.length]; + for (int i = 0; i < array.length; i++) { + inverseArray[i] = !array[i]; + } + rpc.sendBoolean(boxedValue == Boolean.TRUE, + Boolean.valueOf(!value), inverseArray); + } + + public void sendBean(ComplexTestBean complexBean, + SimpleTestBean simpleBean, SimpleTestBean[] array) { + SimpleTestBean updatedSimpleBean = new SimpleTestBean(); + updatedSimpleBean.setValue(complexBean.getInnerBean1() + .getValue()); + + ComplexTestBean updatedComplexBean = new ComplexTestBean(); + updatedComplexBean.setInnerBean1(complexBean.getInnerBean2()); + updatedComplexBean.setInnerBean2(complexBean + .getInnerBeanCollection().get(0)); + updatedComplexBean.setInnerBeanCollection(Arrays.asList( + simpleBean, updatedSimpleBean)); + updatedComplexBean.setPrivimite(complexBean.getPrivimite() + 1); + + ArrayList arrayList = new ArrayList( + Arrays.asList(array)); + Collections.reverse(arrayList); + + rpc.sendBean(updatedComplexBean, updatedSimpleBean, + arrayList.toArray(new SimpleTestBean[array.length])); + } + + public void sendArrayList(List primitiveArrayList, + List objectArrayList, + List beanArrayList) { + Collections.reverse(beanArrayList); + List updatedObjectArrayList = new ArrayList(); + for (int[] array : primitiveArrayList) { + updatedObjectArrayList.add(new Integer[] { + Integer.valueOf(array.length), + Integer.valueOf(array[0]) }); + } + + rpc.sendArrayList(Arrays.asList( + new int[] { primitiveArrayList.size() }, + new int[] { objectArrayList.get(0).length }), + updatedObjectArrayList, beanArrayList); + } + + public void sendNull(String value1, String value2) { + rpc.sendNull(value2, value1); + } + + public void sendListArray(List[] objectListArray, + List[] beanListArray) { + rpc.sendListArray(new List[] { objectListArray[1], + objectListArray[0] }, new List[] { Collections + .singletonList(beanListArray[0].get(0)) }); + } + }); + } + + private Connector getRootConnector() { + return getConnection().getRootConnector(); + } + + private Connector getThisConnector() { + // Cast to Connector for use in e.g. Collections.singleton() to get a + // Set + return this; + } + + @Override + public ComplexTestBean getState() { + return (ComplexTestBean) super.getState(); + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + // TODO do something clever + } + +} diff --git a/tests/testbench/com/vaadin/tests/widgetset/client/SerializerTestRpc.java b/tests/testbench/com/vaadin/tests/widgetset/client/SerializerTestRpc.java new file mode 100644 index 0000000000..5b73e1d34d --- /dev/null +++ b/tests/testbench/com/vaadin/tests/widgetset/client/SerializerTestRpc.java @@ -0,0 +1,64 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.tests.widgetset.client; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.vaadin.terminal.gwt.client.Connector; +import com.vaadin.terminal.gwt.client.communication.ClientRpc; +import com.vaadin.terminal.gwt.client.communication.ServerRpc; + +@SuppressWarnings("javadoc") +public interface SerializerTestRpc extends ServerRpc, ClientRpc { + public void sendBoolean(boolean value, Boolean boxedValue, boolean[] array); + + public void sendByte(byte value, Byte boxedValue, byte[] array); + + public void sendChar(char value, Character boxedValue, char[] array); + + public void sendInt(int value, Integer boxedValue, int[] array); + + public void sendLong(long value, Long boxedValue, long[] array); + + public void sendFloat(float value, Float boxedValue, float[] array); + + public void sendDouble(double value, Double boxedValue, double[] array); + + public void sendString(String value); + + public void sendConnector(Connector connector); + + public void sendBean(ComplexTestBean complexBean, + SimpleTestBean simpleBean, SimpleTestBean[] array); + + public void sendNull(String value1, String value2); + + public void sendNestedArray(int[][] nestedIntArray, + SimpleTestBean[][] nestedBeanArray); + + public void sendList(List intList, List connectorList, + List beanList); + + public void sendArrayList(List primitiveArrayList, + List objectArrayList, + List beanArrayList); + + public void sendListArray(List[] objectListArray, + List[] beanListArray); + + public void sendSet(Set intSet, Set connectorSet, + Set beanSet); + + public void sendMap(Map stringMap, + Map connectorMap, + Map intMap, + Map beanMap); + + public void sendWrappedGenerics( + Map, Map>> generics); + +} diff --git a/tests/testbench/com/vaadin/tests/widgetset/client/SimpleTestBean.java b/tests/testbench/com/vaadin/tests/widgetset/client/SimpleTestBean.java new file mode 100644 index 0000000000..43ad51e758 --- /dev/null +++ b/tests/testbench/com/vaadin/tests/widgetset/client/SimpleTestBean.java @@ -0,0 +1,38 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.tests.widgetset.client; + +import java.io.Serializable; + +public class SimpleTestBean implements Serializable { + private int value; + + public SimpleTestBean() { + this(0); + } + + public SimpleTestBean(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public void setValue(int value) { + this.value = value; + } + + @Override + public String toString() { + return "SimpleTestBean(" + value + ")"; + } + + @Override + public int hashCode() { + // Implement hash code to get consistent HashSet.toString + return value; + } +} \ No newline at end of file diff --git a/tests/testbench/com/vaadin/tests/widgetset/server/SerializerTestExtension.java b/tests/testbench/com/vaadin/tests/widgetset/server/SerializerTestExtension.java new file mode 100644 index 0000000000..99c05e8f76 --- /dev/null +++ b/tests/testbench/com/vaadin/tests/widgetset/server/SerializerTestExtension.java @@ -0,0 +1,22 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.tests.widgetset.server; + +import com.vaadin.terminal.AbstractExtension; +import com.vaadin.tests.widgetset.client.ComplexTestBean; +import com.vaadin.tests.widgetset.client.SerializerTestRpc; + +public class SerializerTestExtension extends AbstractExtension { + + @Override + public ComplexTestBean getState() { + return (ComplexTestBean) super.getState(); + } + + public void registerRpc(SerializerTestRpc rpc) { + super.registerRpc(rpc); + } + +} -- 2.39.5