From: Leif Åstrand Date: Wed, 22 Aug 2012 14:07:22 +0000 (+0300) Subject: Use ConnectorBundle for JSON encoding and decoding (#9371) X-Git-Tag: 7.0.0.beta1~221^2~13 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=02878bd07a38dc69fe415c7f60238817d2a7c434;p=vaadin-framework.git Use ConnectorBundle for JSON encoding and decoding (#9371) --- diff --git a/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/ConnectorBundleLoaderFactory.java b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/ConnectorBundleLoaderFactory.java index 9f830d4a8d..9a5b83f460 100644 --- a/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/ConnectorBundleLoaderFactory.java +++ b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/ConnectorBundleLoaderFactory.java @@ -9,6 +9,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -43,6 +44,8 @@ import com.vaadin.terminal.gwt.client.metadata.TypeDataStore; import com.vaadin.terminal.gwt.widgetsetutils.metadata.ClientRpcVisitor; import com.vaadin.terminal.gwt.widgetsetutils.metadata.ConnectorBundle; import com.vaadin.terminal.gwt.widgetsetutils.metadata.ConnectorInitVisitor; +import com.vaadin.terminal.gwt.widgetsetutils.metadata.GeneratedSerializer; +import com.vaadin.terminal.gwt.widgetsetutils.metadata.Property; import com.vaadin.terminal.gwt.widgetsetutils.metadata.ServerRpcVisitor; import com.vaadin.terminal.gwt.widgetsetutils.metadata.StateInitVisitor; import com.vaadin.terminal.gwt.widgetsetutils.metadata.TypeVisitor; @@ -134,7 +137,7 @@ public class ConnectorBundleLoaderFactory extends Generator { w.println("public void load() {"); w.indent(); - printBundleData(w, bundle); + printBundleData(logger, w, bundle); // Close load method w.outdent(); @@ -165,7 +168,8 @@ public class ConnectorBundleLoaderFactory extends Generator { w.commit(logger); } - private void printBundleData(SourceWriter w, ConnectorBundle bundle) { + private void printBundleData(TreeLogger logger, SourceWriter w, + ConnectorBundle bundle) throws UnableToCompleteException { writeIdentifiers(w, bundle); writeGwtConstructors(w, bundle); writeReturnTypes(w, bundle); @@ -173,6 +177,133 @@ public class ConnectorBundleLoaderFactory extends Generator { writeParamTypes(w, bundle); writeProxys(w, bundle); wirteDelayedInfo(w, bundle); + writeProperites(logger, w, bundle); + writePropertyTypes(w, bundle); + writeSetters(logger, w, bundle); + writeGetters(logger, w, bundle); + writeSerializers(logger, w, bundle); + } + + private void writeSerializers(TreeLogger logger, SourceWriter w, + ConnectorBundle bundle) throws UnableToCompleteException { + Map serializers = bundle.getSerializers(); + for (Entry entry : serializers.entrySet()) { + JType type = entry.getKey(); + GeneratedSerializer serializer = entry.getValue(); + + w.print("store.setSerializerFactory("); + writeClassLiteral(w, type); + w.print(", "); + w.println("new Invoker() {"); + w.indent(); + + w.println("public Object invoke(Object target, Object[] params) {"); + w.indent(); + + serializer.writeSerializerInstantiator(logger, w); + + w.outdent(); + w.println("}"); + + w.outdent(); + w.print("}"); + w.println(");"); + } + } + + private void writeGetters(TreeLogger logger, SourceWriter w, + ConnectorBundle bundle) { + Set properties = bundle.getNeedsSetter(); + for (Property property : properties) { + w.print("store.setGetter("); + writeClassLiteral(w, property.getBeanType()); + w.print(", \""); + w.print(escape(property.getName())); + w.print("\", new Invoker() {"); + w.indent(); + + w.println("public Object invoke(Object bean, Object[] params) {"); + w.indent(); + + property.writeGetterBody(logger, w, "bean"); + w.println(); + + w.outdent(); + w.println("}"); + + w.outdent(); + w.println("});"); + } + } + + private void writeSetters(TreeLogger logger, SourceWriter w, + ConnectorBundle bundle) { + Set properties = bundle.getNeedsSetter(); + for (Property property : properties) { + w.print("store.setSetter("); + writeClassLiteral(w, property.getBeanType()); + w.print(", \""); + w.print(escape(property.getName())); + w.println("\", new Invoker() {"); + w.indent(); + + w.println("public Object invoke(Object bean, Object[] params) {"); + w.indent(); + + property.writeSetterBody(logger, w, "bean", "params[0]"); + + w.println("return null;"); + + w.outdent(); + w.println("}"); + + w.outdent(); + w.println("});"); + } + } + + private void writePropertyTypes(SourceWriter w, ConnectorBundle bundle) { + Set properties = bundle.getNeedsType(); + for (Property property : properties) { + w.print("store.setPropertyType("); + writeClassLiteral(w, property.getBeanType()); + w.print(", \""); + w.print(escape(property.getName())); + w.print("\", "); + writeTypeCreator(w, property.getPropertyType()); + w.println(");"); + } + } + + private void writeProperites(TreeLogger logger, SourceWriter w, + ConnectorBundle bundle) throws UnableToCompleteException { + Set needsPropertyListing = bundle.getNeedsPropertyListing(); + for (JClassType type : needsPropertyListing) { + w.print("store.setProperties("); + writeClassLiteral(w, type); + w.print(", new String[] {"); + + Set usedPropertyNames = new HashSet(); + Collection properties = bundle.getProperties(type); + for (Property property : properties) { + String name = property.getName(); + if (!usedPropertyNames.add(name)) { + logger.log( + Type.ERROR, + type.getQualifiedSourceName() + + " has multiple properties with the name " + + name + + ". This can happen if there are multiple setters with identical names exect casing."); + throw new UnableToCompleteException(); + } + + w.print("\""); + w.print(name); + w.print("\", "); + } + + w.println("});"); + } } private void wirteDelayedInfo(SourceWriter w, ConnectorBundle bundle) { @@ -187,14 +318,14 @@ public class ConnectorBundleLoaderFactory extends Generator { Delayed annotation = method.getAnnotation(Delayed.class); if (annotation != null) { w.print("store.setDelayed("); - printClassLiteral(w, type); + writeClassLiteral(w, type); w.print(", \""); w.print(escape(method.getName())); w.println("\");"); if (annotation.lastonly()) { w.print("store.setLastonly("); - printClassLiteral(w, type); + writeClassLiteral(w, type); w.print(", \""); w.print(escape(method.getName())); w.println("\");"); @@ -208,7 +339,7 @@ public class ConnectorBundleLoaderFactory extends Generator { Set needsProxySupport = bundle.getNeedsProxySupport(); for (JClassType type : needsProxySupport) { w.print("store.setProxyHandler("); - printClassLiteral(w, type); + writeClassLiteral(w, type); w.print(", new "); w.print(ProxyHandler.class.getCanonicalName()); w.println("() {"); @@ -253,7 +384,7 @@ public class ConnectorBundleLoaderFactory extends Generator { w.print("handler.invoke(this, "); w.print(TypeData.class.getCanonicalName()); w.print(".getType("); - printClassLiteral(w, type); + writeClassLiteral(w, type); w.print(").getMethod(\""); w.print(escape(method.getName())); w.print("\"), new Object [] {"); @@ -288,7 +419,7 @@ public class ConnectorBundleLoaderFactory extends Generator { Set methods = entry.getValue(); for (JMethod method : methods) { w.print("store.setParamTypes("); - printClassLiteral(w, type); + writeClassLiteral(w, type); w.print(", \""); w.print(escape(method.getName())); w.print("\", new Type[] {"); @@ -312,7 +443,7 @@ public class ConnectorBundleLoaderFactory extends Generator { Set methods = entry.getValue(); for (JMethod method : methods) { w.print("store.setInvoker("); - printClassLiteral(w, type); + writeClassLiteral(w, type); w.print(", \""); w.print(escape(method.getName())); w.println("\", new Invoker() {"); @@ -368,7 +499,7 @@ public class ConnectorBundleLoaderFactory extends Generator { // setReturnType(Class type, String methodName, Type // returnType) w.print("store.setReturnType("); - printClassLiteral(w, type); + writeClassLiteral(w, type); w.print(", \""); w.print(escape(method.getName())); w.print("\", "); @@ -382,7 +513,7 @@ public class ConnectorBundleLoaderFactory extends Generator { Set constructors = bundle.getGwtConstructors(); for (JClassType type : constructors) { w.print("store.setConstructor("); - printClassLiteral(w, type); + writeClassLiteral(w, type); w.println(", new Invoker() {"); w.indent(); @@ -392,7 +523,7 @@ public class ConnectorBundleLoaderFactory extends Generator { w.print("return "); w.print(GWT.class.getName()); w.print(".create("); - printClassLiteral(w, type); + writeClassLiteral(w, type); w.println(");"); w.outdent(); @@ -403,7 +534,7 @@ public class ConnectorBundleLoaderFactory extends Generator { } } - private void printClassLiteral(SourceWriter w, JClassType type) { + public static void writeClassLiteral(SourceWriter w, JType type) { w.print(type.getQualifiedSourceName()); w.print(".class"); } @@ -417,7 +548,7 @@ public class ConnectorBundleLoaderFactory extends Generator { w.print("store.setClass(\""); w.print(escape(id)); w.print("\", "); - printClassLiteral(w, type); + writeClassLiteral(w, type); w.println(");"); } } @@ -450,7 +581,7 @@ public class ConnectorBundleLoaderFactory extends Generator { Collection visitors = getVisitors(typeOracle); ConnectorBundle eagerBundle = new ConnectorBundle( - ConnectorBundleLoader.EAGER_BUNDLE_NAME, visitors); + ConnectorBundleLoader.EAGER_BUNDLE_NAME, visitors, typeOracle); TreeLogger eagerLogger = logger.branch(Type.TRACE, "Populating eager bundle"); @@ -515,9 +646,9 @@ public class ConnectorBundleLoaderFactory extends Generator { public static void writeTypeCreator(SourceWriter sourceWriter, JType type) { String typeName = ConnectorBundleLoaderFactory.getBoxedTypeName(type); - sourceWriter.print("new Type(\"" + typeName + "\", "); JParameterizedType parameterized = type.isParameterized(); if (parameterized != null) { + sourceWriter.print("new Type(\"" + typeName + "\", "); sourceWriter.print("new Type[] {"); JClassType[] typeArgs = parameterized.getTypeArgs(); for (JClassType jClassType : typeArgs) { @@ -526,7 +657,7 @@ public class ConnectorBundleLoaderFactory extends Generator { } sourceWriter.print("}"); } else { - sourceWriter.print("null"); + sourceWriter.print("new Type(" + typeName + ".class"); } sourceWriter.print(")"); } diff --git a/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerGenerator.java b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerGenerator.java deleted file mode 100644 index 83e1c17881..0000000000 --- a/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerGenerator.java +++ /dev/null @@ -1,484 +0,0 @@ -/* - * Copyright 2011 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.terminal.gwt.widgetsetutils; - -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.core.ext.Generator; -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.JPackage; -import com.google.gwt.core.ext.typeinfo.JPrimitiveType; -import com.google.gwt.core.ext.typeinfo.JType; -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; -import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; -import com.google.gwt.user.rebind.SourceWriter; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.communication.DiffJSONSerializer; -import com.vaadin.terminal.gwt.client.communication.JSONSerializer; -import com.vaadin.terminal.gwt.client.communication.JsonDecoder; -import com.vaadin.terminal.gwt.client.communication.JsonEncoder; -import com.vaadin.terminal.gwt.client.communication.SerializerMap; - -/** - * GWT generator for creating serializer classes for custom classes sent from - * server to client. - * - * Only fields with a correspondingly named setter are deserialized. - * - * @since 7.0 - */ -public class SerializerGenerator extends Generator { - - private static final String SUBTYPE_SEPARATOR = "___"; - - @Override - public String generate(TreeLogger logger, GeneratorContext context, - 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, type, - getSerializerPackageName(type), serializerClassName); - } catch (Exception e) { - logger.log(TreeLogger.ERROR, "SerializerGenerator failed for " - + type.getQualifiedSourceName(), e); - throw new UnableToCompleteException(); - } - - // return the fully qualifed name of the class generated - return getFullyQualifiedSerializerClassName(type); - } - - /** - * Generate source code for a VaadinSerializer implementation. - * - * @param logger - * Logger object - * @param context - * Generator context - * @param type - * @param beanTypeName - * bean type for which the serializer is to be generated - * @param beanSerializerTypeName - * name of the serializer class to generate - * @throws UnableToCompleteException - */ - private void generateClass(TreeLogger logger, GeneratorContext context, - JClassType type, String serializerPackageName, - String serializerClassName) throws UnableToCompleteException { - // get print writer that receives the source code - PrintWriter printWriter = null; - printWriter = context.tryCreate(logger, serializerPackageName, - serializerClassName); - - // print writer if null, source code has ALREADY been generated - if (printWriter == null) { - return; - } - boolean isEnum = (type.isEnum() != null); - boolean isArray = (type.isArray() != null); - - String qualifiedSourceName = type.getQualifiedSourceName(); - logger.log(Type.DEBUG, "Processing serializable type " - + qualifiedSourceName + "..."); - - // init composer, set class properties, create source writer - ClassSourceFileComposerFactory composer = null; - composer = new ClassSourceFileComposerFactory(serializerPackageName, - serializerClassName); - composer.addImport(GWT.class.getName()); - composer.addImport(JSONValue.class.getName()); - composer.addImport(com.vaadin.terminal.gwt.client.metadata.Type.class - .getName()); - // composer.addImport(JSONObject.class.getName()); - // composer.addImport(VPaintableMap.class.getName()); - composer.addImport(JsonDecoder.class.getName()); - // composer.addImport(VaadinSerializer.class.getName()); - - if (isEnum || isArray) { - composer.addImplementedInterface(JSONSerializer.class.getName() - + "<" + qualifiedSourceName + ">"); - } else { - composer.addImplementedInterface(DiffJSONSerializer.class.getName() - + "<" + qualifiedSourceName + ">"); - } - - SourceWriter sourceWriter = composer.createSourceWriter(context, - printWriter); - sourceWriter.indent(); - - // Serializer - - // public JSONValue serialize(Object value, - // ApplicationConnection connection) { - sourceWriter.println("public " + JSONValue.class.getName() - + " serialize(" + qualifiedSourceName + " value, " - + ApplicationConnection.class.getName() + " connection) {"); - sourceWriter.indent(); - // MouseEventDetails castedValue = (MouseEventDetails) value; - sourceWriter.println(qualifiedSourceName + " castedValue = (" - + qualifiedSourceName + ") value;"); - - if (isEnum) { - writeEnumSerializer(logger, sourceWriter, type); - } else if (isArray) { - writeArraySerializer(logger, sourceWriter, type.isArray()); - } else { - writeBeanSerializer(logger, sourceWriter, type); - } - // } - sourceWriter.outdent(); - sourceWriter.println("}"); - sourceWriter.println(); - - // Updater - // public void update(T target, Type type, JSONValue jsonValue, - // ApplicationConnection 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, type); - - sourceWriter.outdent(); - sourceWriter.println("}"); - } - - // Deserializer - // T deserialize(Type type, JSONValue jsonValue, ApplicationConnection - // connection); - sourceWriter.println("public " + qualifiedSourceName - + " deserialize(Type type, " + JSONValue.class.getName() - + " jsonValue, " + ApplicationConnection.class.getName() - + " connection) {"); - sourceWriter.indent(); - - if (isEnum) { - writeEnumDeserializer(logger, sourceWriter, type.isEnum()); - } else if (isArray) { - writeArrayDeserializer(logger, sourceWriter, type.isArray()); - } else { - sourceWriter.println(qualifiedSourceName + " target = GWT.create(" - + qualifiedSourceName + ".class);"); - sourceWriter - .println("update(target, type, jsonValue, connection);"); - // return target; - sourceWriter.println("return target;"); - } - sourceWriter.outdent(); - sourceWriter.println("}"); - - // End of class - sourceWriter.outdent(); - sourceWriter.println("}"); - - // commit generated class - context.commit(logger, printWriter); - logger.log(TreeLogger.INFO, "Generated Serializer class " - + getFullyQualifiedSerializerClassName(type)); - } - - private void writeEnumDeserializer(TreeLogger logger, - SourceWriter sourceWriter, JEnumType enumType) { - sourceWriter.println("String enumIdentifier = ((" - + JSONString.class.getName() + ")jsonValue).stringValue();"); - for (JEnumConstant e : enumType.getEnumConstants()) { - sourceWriter.println("if (\"" + e.getName() - + "\".equals(enumIdentifier)) {"); - sourceWriter.indent(); - sourceWriter.println("return " + enumType.getQualifiedSourceName() - + "." + e.getName() + ";"); - sourceWriter.outdent(); - sourceWriter.println("}"); - } - 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] = (" - + ConnectorBundleLoaderFactory - .getBoxedTypeName(componentType) + ") " - + JsonDecoder.class.getName() + ".decodeValue("); - ConnectorBundleLoaderFactory.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(); - - // JSONOBject json = (JSONObject)jsonValue; - sourceWriter.println(JSONObject.class.getName() + " json = (" - + JSONObject.class.getName() + ")jsonValue;"); - - for (JMethod method : getSetters(beanType)) { - String setterName = method.getName(); - String baseName = setterName.substring(3); - String fieldName = getTransportFieldName(baseName); // setZIndex() - // -> zIndex - JType setterParameterType = method.getParameterTypes()[0]; - - logger.log(Type.DEBUG, "* Processing field " + fieldName + " in " - + beanQualifiedSourceName + " (" + beanType.getName() + ")"); - - // if (json.containsKey("height")) { - sourceWriter.println("if (json.containsKey(\"" + fieldName - + "\")) {"); - sourceWriter.indent(); - String jsonFieldName = "json_" + fieldName; - // JSONValue json_Height = json.get("height"); - sourceWriter.println("JSONValue " + jsonFieldName - + " = json.get(\"" + fieldName + "\");"); - - String fieldType; - String getterName = "get" + baseName; - JPrimitiveType primitiveType = setterParameterType.isPrimitive(); - if (primitiveType != null) { - // This is a primitive type -> must used the boxed type - fieldType = primitiveType.getQualifiedBoxedSourceName(); - if (primitiveType == JPrimitiveType.BOOLEAN) { - getterName = "is" + baseName; - } - } else { - fieldType = setterParameterType.getQualifiedSourceName(); - } - - // String referenceValue = target.getHeight(); - sourceWriter.println(fieldType + " referenceValue = target." - + getterName + "();"); - - // target.setHeight((String) - // JsonDecoder.decodeValue(jsonFieldValue,referenceValue, idMapper, - // connection)); - sourceWriter.print("target." + setterName + "((" + fieldType + ") " - + JsonDecoder.class.getName() + ".decodeValue("); - ConnectorBundleLoaderFactory.writeTypeCreator(sourceWriter, - setterParameterType); - sourceWriter.println(", " + jsonFieldName - + ", referenceValue, connection));"); - - // } ... end of if contains - sourceWriter.outdent(); - sourceWriter.println("}"); - } - } - - private void writeEnumSerializer(TreeLogger logger, - SourceWriter sourceWriter, JClassType beanType) { - // return new JSONString(castedValue.name()); - sourceWriter.println("return new " + JSONString.class.getName() - + "(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 { - - // JSONObject json = new JSONObject(); - sourceWriter.println(JSONObject.class.getName() + " json = new " - + JSONObject.class.getName() + "();"); - - HashSet usedFieldNames = new HashSet(); - - for (JMethod setterMethod : getSetters(beanType)) { - String setterName = setterMethod.getName(); - String fieldName = getTransportFieldName(setterName.substring(3)); // setZIndex() - // -> zIndex - if (!usedFieldNames.add(fieldName)) { - logger.log( - TreeLogger.ERROR, - "Can't encode " - + beanType.getQualifiedSourceName() - + " as it has multiple fields with the name " - + fieldName.toLowerCase() - + ". This can happen if only casing distinguishes one property name from another."); - throw new UnableToCompleteException(); - } - String getterName = findGetter(beanType, setterMethod); - - if (getterName == null) { - logger.log(TreeLogger.ERROR, "No getter found for " + fieldName - + ". Serialization will likely fail"); - } - // json.put("button", - // JsonEncoder.encode(castedValue.getButton(), false, idMapper, - // connection)); - sourceWriter.println("json.put(\"" + fieldName + "\", " - + JsonEncoder.class.getName() + ".encode(castedValue." - + getterName + "(), false, connection));"); - } - // return json; - sourceWriter.println("return json;"); - - } - - private static String getTransportFieldName(String baseName) { - return Character.toLowerCase(baseName.charAt(0)) - + baseName.substring(1); - } - - private String findGetter(JClassType beanType, JMethod setterMethod) { - JType setterParameterType = setterMethod.getParameterTypes()[0]; - String fieldName = setterMethod.getName().substring(3); - if (setterParameterType.getQualifiedSourceName().equals( - boolean.class.getName())) { - return "is" + fieldName; - } else { - return "get" + fieldName; - } - } - - /** - * 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(); - - while (beanType != null - && !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); - } - beanType = beanType.getSuperclass(); - } - - return setterMethods; - } - - private static String getSerializerSimpleClassName(JClassType beanType) { - return getSimpleClassName(beanType) + "_Serializer"; - } - - 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(classType.getEnclosingType()); - String name = baseName + SUBTYPE_SEPARATOR - + type.getSimpleSourceName(); - return name; - } - return type.getSimpleSourceName(); - } - - public static String getFullyQualifiedSerializerClassName(JClassType type) { - return getSerializerPackageName(type) + "." - + getSerializerSimpleClassName(type); - } - - private static String getSerializerPackageName(JClassType type) { - JPackage typePackage = type.getPackage(); - if (typePackage == null) { - return SerializerMap.class.getPackage().getName(); - } else { - String packageName = typePackage.getName(); - // Dev mode classloader gets unhappy for some java packages - if (packageName.startsWith("java.")) { - packageName = "com.vaadin." + packageName; - } - return packageName; - } - } -} diff --git a/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerMapGenerator.java b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerMapGenerator.java deleted file mode 100644 index ac22aa2e8a..0000000000 --- a/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerMapGenerator.java +++ /dev/null @@ -1,377 +0,0 @@ -/* - * Copyright 2011 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.terminal.gwt.widgetsetutils; - -import java.io.PrintWriter; -import java.io.Serializable; -import java.util.Date; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import com.google.gwt.core.ext.Generator; -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; -import com.google.gwt.core.ext.typeinfo.JType; -import com.google.gwt.core.ext.typeinfo.NotFoundException; -import com.google.gwt.core.ext.typeinfo.TypeOracle; -import com.google.gwt.json.client.JSONValue; -import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; -import com.google.gwt.user.rebind.SourceWriter; -import com.vaadin.shared.communication.ClientRpc; -import com.vaadin.shared.communication.ServerRpc; -import com.vaadin.shared.communication.SharedState; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.communication.JSONSerializer; -import com.vaadin.terminal.gwt.client.communication.SerializerMap; - -/** - * GWT generator that creates a {@link SerializerMap} implementation (mapper - * from type string to serializer instance) and serializer classes for all - * subclasses of {@link SharedState}. - * - * @since 7.0 - */ -public class SerializerMapGenerator extends Generator { - - private static final String FAIL_IF_NOT_SERIALIZABLE = "vFailIfNotSerializable"; - private String packageName; - private String className; - - @Override - public String generate(TreeLogger logger, GeneratorContext context, - String typeName) throws UnableToCompleteException { - - try { - TypeOracle typeOracle = context.getTypeOracle(); - Set typesNeedingSerializers = findTypesNeedingSerializers( - typeOracle, logger); - checkForUnserializableTypes(typesNeedingSerializers, typeOracle, - logger); - Set typesWithExistingSerializers = findTypesWithExistingSerializers( - typeOracle, logger); - Set serializerMappings = new HashSet(); - serializerMappings.addAll(typesNeedingSerializers); - serializerMappings.addAll(typesWithExistingSerializers); - // 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 - generateSerializerMap(serializerMappings, logger, context); - - SerializerGenerator sg = new SerializerGenerator(); - for (JClassType type : typesNeedingSerializers) { - sg.generate(logger, context, type.getQualifiedSourceName()); - } - } catch (Exception e) { - logger.log(TreeLogger.ERROR, - "SerializerMapGenerator creation failed", e); - throw new UnableToCompleteException(); - } - // return the fully qualifed name of the class generated - return packageName + "." + className; - } - - /** - * Emits a warning for all classes that are used in communication but do not - * implement java.io.Serializable. Implementing java.io.Serializable is not - * needed for communication but for the server side Application to be - * serializable i.e. work in GAE for instance. - * - * @param typesNeedingSerializers - * @param typeOracle - * @param logger - * @throws UnableToCompleteException - */ - private void checkForUnserializableTypes( - Set typesNeedingSerializers, TypeOracle typeOracle, - TreeLogger logger) throws UnableToCompleteException { - 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) { - boolean abortCompile = "true".equals(System - .getProperty(FAIL_IF_NOT_SERIALIZABLE)); - logger.log( - abortCompile ? Type.ERROR : Type.WARN, - type - + " is used in RPC or shared state but does not implement " - + Serializable.class.getName() - + ". Communication will work but the Application on server side cannot be serialized if it refers to objects of this type. " - + "If the system property " - + FAIL_IF_NOT_SERIALIZABLE - + " is set to \"true\", this causes the compilation to fail instead of just emitting a warning."); - if (abortCompile) { - throw new UnableToCompleteException(); - } - } - } - } - - private Set findTypesWithExistingSerializers( - TypeOracle typeOracle, TreeLogger logger) - throws UnableToCompleteException { - JClassType serializerInterface = typeOracle - .findType(JSONSerializer.class.getName()); - JType[] deserializeParamTypes = new JType[] { - typeOracle - .findType(com.vaadin.terminal.gwt.client.metadata.Type.class - .getName()), - typeOracle.findType(JSONValue.class.getName()), - typeOracle.findType(ApplicationConnection.class.getName()) }; - String deserializeMethodName = "deserialize"; - try { - serializerInterface.getMethod(deserializeMethodName, - deserializeParamTypes); - } catch (NotFoundException e) { - logger.log(Type.ERROR, "Could not find " + deserializeMethodName - + " in " + serializerInterface); - throw new UnableToCompleteException(); - } - - Set types = new HashSet(); - for (JClassType serializer : serializerInterface.getSubtypes()) { - JMethod deserializeMethod = serializer.findMethod( - deserializeMethodName, deserializeParamTypes); - if (deserializeMethod == null) { - logger.log(Type.DEBUG, "Could not find " - + deserializeMethodName + " in " + serializer); - continue; - } - JType returnType = deserializeMethod.getReturnType(); - logger.log(Type.DEBUG, "Found " + deserializeMethodName - + " with return type " + returnType + " in " + serializer); - - types.add(returnType.isClass()); - } - return types; - } - - /** - * Generate source code for SerializerMapImpl - * - * @param typesNeedingSerializers - * - * @param logger - * Logger object - * @param context - * Generator 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); - // print writer if null, source code has ALREADY been generated - if (printWriter == null) { - return; - } - Date date = new Date(); - TypeOracle typeOracle = context.getTypeOracle(); - - // init composer, set class properties, create source writer - ClassSourceFileComposerFactory composer = null; - composer = new ClassSourceFileComposerFactory(packageName, className); - composer.addImport("com.google.gwt.core.client.GWT"); - composer.addImplementedInterface(SerializerMap.class.getName()); - SourceWriter sourceWriter = composer.createSourceWriter(context, - printWriter); - sourceWriter.indent(); - - sourceWriter.println("public " + JSONSerializer.class.getName() - + " getSerializer(String type) {"); - sourceWriter.indent(); - - // TODO cache serializer instances in a map - for (JClassType type : typesNeedingSerializers) { - 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); - sourceWriter.println("return GWT.create(" + serializerName - + ".class);"); - sourceWriter.outdent(); - sourceWriter.println("}"); - logger.log(Type.INFO, "Configured serializer (" + serializerName - + ") for " + type.getName()); - } - sourceWriter - .println("throw new RuntimeException(\"No serializer found for class \"+type);"); - sourceWriter.outdent(); - sourceWriter.println("}"); - - // close generated class - sourceWriter.outdent(); - sourceWriter.println("}"); - // commit generated class - context.commit(logger, printWriter); - logger.log(Type.INFO, - "Done. (" + (new Date().getTime() - date.getTime()) / 1000 - + "seconds)"); - - } - - public Set findTypesNeedingSerializers(TypeOracle typeOracle, - TreeLogger logger) { - logger.log(Type.DEBUG, "Detecting serializable data types..."); - - HashSet types = new HashSet(); - - // Generate serializer classes for each subclass of SharedState - JClassType serializerType = typeOracle.findType(SharedState.class - .getName()); - types.add(serializerType); - JClassType[] serializerSubtypes = serializerType.getSubtypes(); - for (JClassType type : serializerSubtypes) { - types.add(type); - } - - // Serializer classes might also be needed for RPC methods - for (Class cls : new Class[] { ServerRpc.class, ClientRpc.class }) { - JClassType rpcType = typeOracle.findType(cls.getName()); - JClassType[] serverRpcSubtypes = rpcType.getSubtypes(); - for (JClassType type : serverRpcSubtypes) { - addMethodParameterTypes(type, types, logger); - } - } - - // Add all types used from/in the types - for (Object t : types.toArray()) { - findSubTypesNeedingSerializers((JClassType) t, types); - } - logger.log(Type.DEBUG, "Serializable data types: " + types.toString()); - - return types; - } - - private void addMethodParameterTypes(JClassType classContainingMethods, - Set types, TreeLogger logger) { - for (JMethod method : classContainingMethods.getMethods()) { - if (method.getName().equals("initRpc")) { - continue; - } - for (JType type : method.getParameterTypes()) { - addTypeIfNeeded(types, type); - } - } - } - - 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]; - addTypeIfNeeded(serializableTypes, setterType); - } - } - - private void addTypeIfNeeded(Set serializableTypes, JType type) { - if (serializableTypes.contains(type)) { - return; - } - JParameterizedType parametrized = type.isParameterized(); - if (parametrized != null) { - for (JClassType parameterType : parametrized.getTypeArgs()) { - addTypeIfNeeded(serializableTypes, parameterType); - } - } - - if (serializationHandledByFramework(type)) { - return; - } - - if (serializableTypes.contains(type)) { - return; - } - - JClassType typeClass = type.isClass(); - if (typeClass != null) { - // setterTypeClass is null at least for List. It is - // possible that we need to handle the cases somehow, for - // instance for List. - 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>(); - { - 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); - 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.isPrimitive() != null) { - return true; - } - - String qualifiedName = setterType.getQualifiedSourceName(); - for (Class cls : frameworkHandledTypes) { - if (qualifiedName.equals(cls.getName())) { - return true; - } - } - - return false; - } -} diff --git a/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/ArraySerializer.java b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/ArraySerializer.java new file mode 100644 index 0000000000..1c729e9841 --- /dev/null +++ b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/ArraySerializer.java @@ -0,0 +1,90 @@ +/* + * Copyright 2011 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.terminal.gwt.widgetsetutils.metadata; + +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.typeinfo.JArrayType; +import com.google.gwt.core.ext.typeinfo.JType; +import com.google.gwt.json.client.JSONArray; +import com.google.gwt.user.rebind.SourceWriter; +import com.vaadin.terminal.gwt.client.communication.JsonDecoder; +import com.vaadin.terminal.gwt.client.communication.JsonEncoder; +import com.vaadin.terminal.gwt.widgetsetutils.ConnectorBundleLoaderFactory; + +public class ArraySerializer extends JsonSerializer { + + private final JArrayType arrayType; + + public ArraySerializer(JArrayType arrayType) { + super(arrayType); + this.arrayType = arrayType; + } + + @Override + protected void printDeserializerBody(TreeLogger logger, SourceWriter w, + String type, String jsonValue, String connection) { + JType leafType = arrayType.getLeafType(); + int rank = arrayType.getRank(); + + w.println(JSONArray.class.getName() + " jsonArray = " + jsonValue + + ".isArray();"); + + // Type value = new Type[jsonArray.size()][][]; + w.print(arrayType.getQualifiedSourceName() + " value = new " + + leafType.getQualifiedSourceName() + "[jsonArray.size()]"); + for (int i = 1; i < rank; i++) { + w.print("[]"); + } + w.println(";"); + + w.println("for(int i = 0 ; i < value.length; i++) {"); + w.indent(); + + JType componentType = arrayType.getComponentType(); + + w.print("value[i] = (" + + ConnectorBundleLoaderFactory.getBoxedTypeName(componentType) + + ") " + JsonDecoder.class.getName() + ".decodeValue("); + ConnectorBundleLoaderFactory.writeTypeCreator(w, componentType); + w.print(", jsonArray.get(i), null, " + connection + ")"); + + w.println(";"); + + w.outdent(); + w.println("}"); + + w.println("return value;"); + } + + @Override + protected void printSerializerBody(TreeLogger logger, SourceWriter w, + String value, String applicationConnection) { + w.println(JSONArray.class.getName() + " values = new " + + JSONArray.class.getName() + "();"); + // JPrimitiveType primitive = componentType.isPrimitive(); + w.println("for (int i = 0; i < " + value + ".length; i++) {"); + w.indent(); + w.print("values.set(i, "); + w.print(JsonEncoder.class.getName() + ".encode(" + value + + "[i], false, " + applicationConnection + ")"); + w.println(");"); + w.outdent(); + w.println("}"); + w.println("return values;"); + } + +} diff --git a/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/ClientRpcVisitor.java b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/ClientRpcVisitor.java index 2f628b76cb..d5a78b86b9 100644 --- a/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/ClientRpcVisitor.java +++ b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/ClientRpcVisitor.java @@ -21,6 +21,7 @@ import java.util.Set; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.typeinfo.JClassType; import com.google.gwt.core.ext.typeinfo.JMethod; +import com.google.gwt.core.ext.typeinfo.JType; public class ClientRpcVisitor extends TypeVisitor { @Override @@ -33,6 +34,11 @@ public class ClientRpcVisitor extends TypeVisitor { for (JMethod method : methods) { bundle.setNeedsInvoker(type, method); bundle.setNeedsParamTypes(type, method); + + JType[] parameterTypes = method.getParameterTypes(); + for (JType paramType : parameterTypes) { + bundle.setNeedsSerialize(paramType); + } } } } diff --git a/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/ConnectorBundle.java b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/ConnectorBundle.java index e93c72aa2f..ad6b1eb102 100644 --- a/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/ConnectorBundle.java +++ b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/ConnectorBundle.java @@ -4,62 +4,114 @@ package com.vaadin.terminal.gwt.widgetsetutils.metadata; +import java.io.Serializable; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Set; 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.JEnumType; import com.google.gwt.core.ext.typeinfo.JMethod; +import com.google.gwt.core.ext.typeinfo.JParameterizedType; +import com.google.gwt.core.ext.typeinfo.JType; import com.google.gwt.core.ext.typeinfo.NotFoundException; +import com.google.gwt.core.ext.typeinfo.TypeOracle; +import com.google.gwt.json.client.JSONValue; import com.vaadin.shared.communication.ClientRpc; import com.vaadin.shared.communication.ServerRpc; import com.vaadin.shared.ui.Connect; +import com.vaadin.terminal.gwt.client.ApplicationConnection; import com.vaadin.terminal.gwt.client.ComponentConnector; import com.vaadin.terminal.gwt.client.ServerConnector; +import com.vaadin.terminal.gwt.client.communication.JSONSerializer; public class ConnectorBundle { + private static final String FAIL_IF_NOT_SERIALIZABLE = "vFailIfNotSerializable"; + private final String name; private final ConnectorBundle previousBundle; + private final Collection visitors; + private final Map customSerializers; + private final Set hasSerializeSupport = new HashSet(); + private final Set needsSerializeSupport = new HashSet(); + private final Map serializers = new HashMap(); + + private final Set needsPropertyList = new HashSet(); private final Set needsGwtConstructor = new HashSet(); - private final Map> identifiers = new HashMap>(); private final Set visitedTypes = new HashSet(); - private final Set visitQueue = new HashSet(); - private final Map> needsReturnType = new HashMap>(); - - private final Collection visitors; - private final Set needsProxySupport = new HashSet(); + + private final Map> identifiers = new HashMap>(); + private final Map> needsReturnType = new HashMap>(); private final Map> needsInvoker = new HashMap>(); private final Map> needsParamTypes = new HashMap>(); private final Map> needsDelayedInfo = new HashMap>(); + private final Set needsSetter = new HashSet(); + private final Set needsType = new HashSet(); + private final Set needsGetter = new HashSet(); + private ConnectorBundle(String name, ConnectorBundle previousBundle, - Collection visitors) { + Collection visitors, + Map customSerializers) { this.name = name; this.previousBundle = previousBundle; this.visitors = visitors; + this.customSerializers = customSerializers; } public ConnectorBundle(String name, ConnectorBundle previousBundle) { - this(name, previousBundle, previousBundle.visitors); - } + this(name, previousBundle, previousBundle.visitors, + previousBundle.customSerializers); + } + + public ConnectorBundle(String name, Collection visitors, + TypeOracle oracle) throws NotFoundException { + this(name, null, visitors, findCustomSerializers(oracle)); + } + + private static Map findCustomSerializers( + TypeOracle oracle) throws NotFoundException { + Map serializers = new HashMap(); + + JClassType serializerInterface = oracle.findType(JSONSerializer.class + .getName()); + JType[] deserializeParamTypes = new JType[] { + oracle.findType(com.vaadin.terminal.gwt.client.metadata.Type.class + .getName()), + oracle.findType(JSONValue.class.getName()), + oracle.findType(ApplicationConnection.class.getName()) }; + String deserializeMethodName = "deserialize"; + // Just test that the method exists + serializerInterface.getMethod(deserializeMethodName, + deserializeParamTypes); + + for (JClassType serializer : serializerInterface.getSubtypes()) { + JMethod deserializeMethod = serializer.findMethod( + deserializeMethodName, deserializeParamTypes); + if (deserializeMethod == null) { + continue; + } + JType returnType = deserializeMethod.getReturnType(); - public ConnectorBundle(String name, Collection visitors) { - this(name, null, visitors); + serializers.put(returnType, serializer); + } + return serializers; } public void setNeedsGwtConstructor(JClassType type) { if (!needsGwtConstructor(type)) { - ensureVisited(type); needsGwtConstructor.add(type); } } @@ -75,7 +127,6 @@ public class ConnectorBundle { public void setIdentifier(JClassType type, String identifier) { if (!hasIdentifier(type, identifier)) { - ensureVisited(type); addMapping(identifiers, type, identifier); } } @@ -114,8 +165,13 @@ public class ConnectorBundle { public void processType(TreeLogger logger, JClassType type) throws UnableToCompleteException { - ensureVisited(type); - purgeQueue(logger); + if (!isTypeVisited(type)) { + for (TypeVisitor typeVisitor : visitors) { + invokeVisitor(logger, type, typeVisitor); + } + visitedTypes.add(type); + purgeSerializeSupportQueue(logger); + } } private boolean isTypeVisited(JClassType type) { @@ -126,31 +182,200 @@ public class ConnectorBundle { } } - private void ensureVisited(JClassType type) { - if (!isTypeVisited(type)) { - visitQueue.add(type); + private void purgeSerializeSupportQueue(TreeLogger logger) + throws UnableToCompleteException { + while (!needsSerializeSupport.isEmpty()) { + Iterator iterator = needsSerializeSupport.iterator(); + JType type = iterator.next(); + iterator.remove(); + + if (hasSserializeSupport(type)) { + continue; + } + + addSerializeSupport(logger, type); } } - private void purgeQueue(TreeLogger logger) throws UnableToCompleteException { - while (!visitQueue.isEmpty()) { - Iterator iterator = visitQueue.iterator(); - JClassType type = iterator.next(); - iterator.remove(); + private void addSerializeSupport(TreeLogger logger, JType type) + throws UnableToCompleteException { + hasSerializeSupport.add(type); - if (isTypeVisited(type)) { - continue; + JParameterizedType parametrized = type.isParameterized(); + if (parametrized != null) { + for (JClassType parameterType : parametrized.getTypeArgs()) { + setNeedsSerialize(parameterType); } + } - // Mark as visited before visiting to avoid adding to queue again - visitedTypes.add(type); + if (serializationHandledByFramework(type)) { + return; + } - for (TypeVisitor typeVisitor : visitors) { - invokeVisitor(logger, type, typeVisitor); + JClassType customSerializer = customSerializers.get(type); + JClassType typeAsClass = type.isClass(); + JEnumType enumType = type.isEnum(); + JArrayType arrayType = type.isArray(); + + if (customSerializer != null) { + logger.log(Type.INFO, "Will serialize " + type + " using " + + customSerializer.getName()); + setSerializer(type, new CustomSerializer(customSerializer)); + } else if (arrayType != null) { + logger.log(Type.INFO, "Will serialize " + type + " as an array"); + setSerializer(type, new ArraySerializer(arrayType)); + setNeedsSerialize(arrayType.getComponentType()); + } else if (enumType != null) { + logger.log(Type.INFO, "Will serialize " + type + " as an enum"); + setSerializer(type, new EnumSerializer(enumType)); + } else if (typeAsClass != null) { + // Bean + checkSerializable(logger, typeAsClass); + + logger.log(Type.INFO, "Will serialize " + type + " as a bean"); + + setNeedsPropertyList(typeAsClass); + + for (Property property : getProperties(typeAsClass)) { + setNeedsGwtConstructor(property.getBeanType()); + setNeedsSetter(property); + + // Getters needed for reading previous value that should be + // passed to sub encoder + setNeedsGetter(property); + setNeedsType(property); + + JType propertyType = property.getPropertyType(); + setNeedsSerialize(propertyType); } } } + private void checkSerializable(TreeLogger logger, JClassType type) + throws UnableToCompleteException { + JClassType javaSerializable = type.getOracle().findType( + Serializable.class.getName()); + boolean serializable = type.isAssignableTo(javaSerializable); + if (!serializable) { + boolean abortCompile = "true".equals(System + .getProperty(FAIL_IF_NOT_SERIALIZABLE)); + logger.log( + abortCompile ? Type.ERROR : Type.WARN, + type + + " is used in RPC or shared state but does not implement " + + Serializable.class.getName() + + ". Communication will work but the Application on server side cannot be serialized if it refers to objects of this type. " + + "If the system property " + + FAIL_IF_NOT_SERIALIZABLE + + " is set to \"true\", this causes the compilation to fail instead of just emitting a warning."); + if (abortCompile) { + throw new UnableToCompleteException(); + } + } + } + + private void setSerializer(JType type, GeneratedSerializer serializer) { + if (!hasSerializer(type)) { + serializers.put(type, serializer); + } + } + + private boolean hasSerializer(JType type) { + if (serializers.containsKey(type)) { + return true; + } else { + return previousBundle != null && previousBundle.hasSerializer(type); + } + } + + public Map getSerializers() { + return Collections.unmodifiableMap(serializers); + } + + private void setNeedsGetter(Property property) { + if (!isNeedsGetter(property)) { + needsGetter.add(property); + } + } + + private boolean isNeedsGetter(Property property) { + if (needsGetter.contains(property)) { + return true; + } else { + return previousBundle != null + && previousBundle.isNeedsGetter(property); + } + } + + public Set getNeedsGetter() { + return Collections.unmodifiableSet(needsGetter); + } + + private void setNeedsType(Property property) { + if (!isNeedsType(property)) { + needsType.add(property); + } + } + + public Set getNeedsType() { + return Collections.unmodifiableSet(needsType); + } + + private boolean isNeedsType(Property property) { + if (needsType.contains(property)) { + return true; + } else { + return previousBundle != null + && previousBundle.isNeedsType(property); + } + } + + public void setNeedsSetter(Property property) { + if (!isNeedsSetter(property)) { + needsSetter.add(property); + } + } + + private boolean isNeedsSetter(Property property) { + if (needsSetter.contains(property)) { + return true; + } else { + return previousBundle != null + && previousBundle.isNeedsSetter(property); + } + } + + public Set getNeedsSetter() { + return Collections.unmodifiableSet(needsSetter); + } + + private void setNeedsPropertyList(JClassType type) { + if (!isNeedsPropertyList(type)) { + needsPropertyList.add(type); + } + } + + private boolean isNeedsPropertyList(JClassType type) { + if (needsPropertyList.contains(type)) { + return true; + } else { + return previousBundle != null + && previousBundle.isNeedsPropertyList(type); + } + } + + public Set getNeedsPropertyListing() { + return Collections.unmodifiableSet(needsPropertyList); + } + + public Collection getProperties(JClassType type) { + HashSet properties = new HashSet(); + + properties.addAll(MethodProperty.findProperties(type)); + + return properties; + } + private void invokeVisitor(TreeLogger logger, JClassType type, TypeVisitor typeVisitor) throws UnableToCompleteException { TreeLogger subLogger = logger.branch(Type.TRACE, @@ -158,9 +383,11 @@ public class ConnectorBundle { + typeVisitor.getClass().getSimpleName()); if (isConnectedConnector(type)) { typeVisitor.visitConnector(subLogger, type, this); - } else if (isClientRpc(type)) { + } + if (isClientRpc(type)) { typeVisitor.visitClientRpc(subLogger, type, this); - } else if (isServerRpc(type)) { + } + if (isServerRpc(type)) { typeVisitor.visitServerRpc(subLogger, type, this); } } @@ -172,7 +399,6 @@ public class ConnectorBundle { public void setNeedsReturnType(JClassType type, JMethod method) { if (!isNeedsReturnType(type, method)) { - ensureVisited(type); addMapping(needsReturnType, type, method); } } @@ -221,7 +447,6 @@ public class ConnectorBundle { public void setNeedsInvoker(JClassType type, JMethod method) { if (!isNeedsInvoker(type, method)) { - ensureVisited(type); addMapping(needsInvoker, type, method); } } @@ -254,7 +479,6 @@ public class ConnectorBundle { public void setNeedsParamTypes(JClassType type, JMethod method) { if (!isNeedsParamTypes(type, method)) { - ensureVisited(type); addMapping(needsParamTypes, type, method); } } @@ -274,7 +498,6 @@ public class ConnectorBundle { public void setNeedsProxySupport(JClassType type) { if (!isNeedsProxySupport(type)) { - ensureVisited(type); needsProxySupport.add(type); } } @@ -294,7 +517,6 @@ public class ConnectorBundle { public void setNeedsDelayedInfo(JClassType type, JMethod method) { if (!isNeedsDelayedInfo(type, method)) { - ensureVisited(type); addMapping(needsDelayedInfo, type, method); } } @@ -311,4 +533,55 @@ public class ConnectorBundle { public Map> getNeedsDelayedInfo() { return Collections.unmodifiableMap(needsDelayedInfo); } + + public void setNeedsSerialize(JType type) { + if (!hasSserializeSupport(type)) { + needsSerializeSupport.add(type); + } + } + + private static 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); + 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.isPrimitive() != null) { + return true; + } + + String qualifiedName = setterType.getQualifiedSourceName(); + for (Class cls : frameworkHandledTypes) { + if (qualifiedName.equals(cls.getName())) { + return true; + } + } + + return false; + } + + private boolean hasSserializeSupport(JType type) { + if (hasSerializeSupport.contains(type)) { + return true; + } else { + return previousBundle != null + && previousBundle.hasSserializeSupport(type); + } + } } \ No newline at end of file diff --git a/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/CustomSerializer.java b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/CustomSerializer.java new file mode 100644 index 0000000000..11252c4acb --- /dev/null +++ b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/CustomSerializer.java @@ -0,0 +1,43 @@ +/* + * Copyright 2011 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.terminal.gwt.widgetsetutils.metadata; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.UnableToCompleteException; +import com.google.gwt.core.ext.typeinfo.JClassType; +import com.google.gwt.user.rebind.SourceWriter; +import com.vaadin.terminal.gwt.widgetsetutils.ConnectorBundleLoaderFactory; + +public class CustomSerializer implements GeneratedSerializer { + + private final JClassType serializerType; + + public CustomSerializer(JClassType serializerType) { + this.serializerType = serializerType; + } + + @Override + public void writeSerializerInstantiator(TreeLogger logger, SourceWriter w) + throws UnableToCompleteException { + w.print("return "); + w.print(GWT.class.getCanonicalName()); + w.print(".create("); + ConnectorBundleLoaderFactory.writeClassLiteral(w, serializerType); + w.println(");"); + } +} diff --git a/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/EnumSerializer.java b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/EnumSerializer.java new file mode 100644 index 0000000000..324b555300 --- /dev/null +++ b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/EnumSerializer.java @@ -0,0 +1,58 @@ +/* + * Copyright 2011 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.terminal.gwt.widgetsetutils.metadata; + +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.typeinfo.JEnumConstant; +import com.google.gwt.core.ext.typeinfo.JEnumType; +import com.google.gwt.json.client.JSONString; +import com.google.gwt.user.rebind.SourceWriter; + +public class EnumSerializer extends JsonSerializer { + + private final JEnumType enumType; + + public EnumSerializer(JEnumType type) { + super(type); + enumType = type; + } + + @Override + protected void printDeserializerBody(TreeLogger logger, SourceWriter w, + String type, String jsonValue, String connection) { + w.println("String enumIdentifier = ((" + JSONString.class.getName() + + ")" + jsonValue + ").stringValue();"); + for (JEnumConstant e : enumType.getEnumConstants()) { + w.println("if (\"" + e.getName() + "\".equals(enumIdentifier)) {"); + w.indent(); + w.println("return " + enumType.getQualifiedSourceName() + "." + + e.getName() + ";"); + w.outdent(); + w.println("}"); + } + w.println("return null;"); + } + + @Override + protected void printSerializerBody(TreeLogger logger, SourceWriter w, + String value, String applicationConnection) { + // return new JSONString(castedValue.name()); + w.println("return new " + JSONString.class.getName() + "(" + value + + ".name());"); + } + +} diff --git a/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/GeneratedSerializer.java b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/GeneratedSerializer.java new file mode 100644 index 0000000000..0cfde7748c --- /dev/null +++ b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/GeneratedSerializer.java @@ -0,0 +1,26 @@ +/* + * Copyright 2011 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.terminal.gwt.widgetsetutils.metadata; + +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.UnableToCompleteException; +import com.google.gwt.user.rebind.SourceWriter; + +public interface GeneratedSerializer { + public void writeSerializerInstantiator(TreeLogger logger, SourceWriter w) + throws UnableToCompleteException; +} diff --git a/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/JsonSerializer.java b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/JsonSerializer.java new file mode 100644 index 0000000000..accee84fe7 --- /dev/null +++ b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/JsonSerializer.java @@ -0,0 +1,88 @@ +/* + * Copyright 2011 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.terminal.gwt.widgetsetutils.metadata; + +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.UnableToCompleteException; +import com.google.gwt.core.ext.typeinfo.JType; +import com.google.gwt.json.client.JSONValue; +import com.google.gwt.user.rebind.SourceWriter; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.communication.JSONSerializer; + +public abstract class JsonSerializer implements GeneratedSerializer { + + private final JType type; + + public JsonSerializer(JType type) { + this.type = type; + } + + @Override + public void writeSerializerInstantiator(TreeLogger logger, SourceWriter w) + throws UnableToCompleteException { + + w.print("return new "); + w.print(JSONSerializer.class.getCanonicalName()); + w.print("<"); + w.print(type.getQualifiedSourceName()); + w.println(">() {"); + w.indent(); + + writeSerializerBody(logger, w); + + w.outdent(); + w.println("};"); + } + + protected void writeSerializerBody(TreeLogger logger, SourceWriter w) { + String qualifiedSourceName = type.getQualifiedSourceName(); + w.println("public " + JSONValue.class.getName() + " serialize(" + + qualifiedSourceName + " value, " + + ApplicationConnection.class.getName() + " connection) {"); + w.indent(); + // MouseEventDetails castedValue = (MouseEventDetails) value; + w.println(qualifiedSourceName + " castedValue = (" + + qualifiedSourceName + ") value;"); + + printSerializerBody(logger, w, "castedValue", "connection"); + + // End of serializer method + w.outdent(); + w.println("}"); + + // Deserializer + // T deserialize(Type type, JSONValue jsonValue, ApplicationConnection + // connection); + w.println("public " + qualifiedSourceName + " deserialize(Type type, " + + JSONValue.class.getName() + " jsonValue, " + + ApplicationConnection.class.getName() + " connection) {"); + w.indent(); + + printDeserializerBody(logger, w, "type", "jsonValue", "connection"); + + w.outdent(); + w.println("}"); + } + + protected abstract void printDeserializerBody(TreeLogger logger, + SourceWriter w, String type, String jsonValue, String connection); + + protected abstract void printSerializerBody(TreeLogger logger, + SourceWriter w, String value, String applicationConnection); + +} diff --git a/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/MethodProperty.java b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/MethodProperty.java new file mode 100644 index 0000000000..f422205175 --- /dev/null +++ b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/MethodProperty.java @@ -0,0 +1,124 @@ +/* + * Copyright 2011 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.terminal.gwt.widgetsetutils.metadata; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import com.google.gwt.core.ext.TreeLogger; +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.user.rebind.SourceWriter; + +public class MethodProperty extends Property { + + private final JMethod setter; + + private MethodProperty(JClassType beanType, JMethod setter) { + super(getTransportFieldName(setter), beanType, setter + .getParameterTypes()[0]); + this.setter = setter; + } + + public static Collection findProperties(JClassType type) { + Collection properties = new ArrayList(); + + List setters = getSetters(type); + for (JMethod setter : setters) { + properties.add(new MethodProperty(type, setter)); + } + + return properties; + } + + /** + * 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 + */ + private static List getSetters(JClassType beanType) { + List setterMethods = new ArrayList(); + + while (beanType != null + && !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); + } + beanType = beanType.getSuperclass(); + } + + return setterMethods; + } + + @Override + public void writeSetterBody(TreeLogger logger, SourceWriter w, + String beanVariable, String valueVariable) { + w.print("(("); + w.print(getBeanType().getQualifiedSourceName()); + w.print(") "); + w.print(beanVariable); + w.print(")."); + w.print(setter.getName()); + w.print("(("); + w.print(getUnboxedPropertyTypeName()); + w.print(") "); + w.print(valueVariable); + w.println(");"); + } + + @Override + public void writeGetterBody(TreeLogger logger, SourceWriter w, + String beanVariable) { + w.print("return (("); + w.print(getBeanType().getQualifiedSourceName()); + w.print(") "); + w.print(beanVariable); + w.print(")."); + w.print(findGetter(getBeanType(), setter)); + w.print("();"); + } + + private String findGetter(JClassType beanType, JMethod setterMethod) { + JType setterParameterType = setterMethod.getParameterTypes()[0]; + String fieldName = setterMethod.getName().substring(3); + if (setterParameterType.getQualifiedSourceName().equals( + boolean.class.getName())) { + return "is" + fieldName; + } else { + return "get" + fieldName; + } + } + + private static String getTransportFieldName(JMethod setter) { + String baseName = setter.getName().substring(3); + return Character.toLowerCase(baseName.charAt(0)) + + baseName.substring(1); + } + +} diff --git a/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/Property.java b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/Property.java new file mode 100644 index 0000000000..1714489db5 --- /dev/null +++ b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/Property.java @@ -0,0 +1,84 @@ +/* + * Copyright 2011 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.terminal.gwt.widgetsetutils.metadata; + +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.typeinfo.JClassType; +import com.google.gwt.core.ext.typeinfo.JPrimitiveType; +import com.google.gwt.core.ext.typeinfo.JType; +import com.google.gwt.user.rebind.SourceWriter; + +public abstract class Property { + private final String name; + private final JClassType beanType; + private final JType propertyType; + + protected Property(String name, JClassType beanType, JType propertyType) { + this.name = name; + this.beanType = beanType; + this.propertyType = propertyType; + } + + public String getName() { + return name; + } + + public JType getPropertyType() { + return propertyType; + } + + public String getUnboxedPropertyTypeName() { + JType propertyType = getPropertyType(); + JPrimitiveType primitive = propertyType.isPrimitive(); + if (primitive != null) { + return primitive.getQualifiedBoxedSourceName(); + } else { + return propertyType.getQualifiedSourceName(); + } + } + + public JClassType getBeanType() { + return beanType; + } + + public abstract void writeSetterBody(TreeLogger logger, SourceWriter w, + String beanVariable, String valueVariable); + + public abstract void writeGetterBody(TreeLogger logger, SourceWriter w, + String beanVariable); + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (obj instanceof Property) { + Property other = (Property) obj; + return other.getClass() == getClass() + && other.getBeanType().equals(getBeanType()) + && other.getName().equals(getName()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return getClass().hashCode() * 31 ^ 2 + getBeanType().hashCode() * 31 + + getName().hashCode(); + } + +} diff --git a/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/ServerRpcVisitor.java b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/ServerRpcVisitor.java index 5505c70dc3..4c0fc94500 100644 --- a/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/ServerRpcVisitor.java +++ b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/ServerRpcVisitor.java @@ -21,6 +21,7 @@ import java.util.Set; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.typeinfo.JClassType; import com.google.gwt.core.ext.typeinfo.JMethod; +import com.google.gwt.core.ext.typeinfo.JType; public class ServerRpcVisitor extends TypeVisitor { @Override @@ -35,6 +36,11 @@ public class ServerRpcVisitor extends TypeVisitor { JMethod[] methods = subType.getMethods(); for (JMethod method : methods) { bundle.setNeedsDelayedInfo(type, method); + + JType[] parameterTypes = method.getParameterTypes(); + for (JType paramType : parameterTypes) { + bundle.setNeedsSerialize(paramType); + } } } } diff --git a/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/StateInitVisitor.java b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/StateInitVisitor.java index d22f03635b..49cf687412 100644 --- a/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/StateInitVisitor.java +++ b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/StateInitVisitor.java @@ -16,6 +16,8 @@ public class StateInitVisitor extends TypeVisitor { JMethod getState = findInheritedMethod(type, "getState"); bundle.setNeedsReturnType(type, getState); + bundle.setNeedsSerialize(getState.getReturnType()); + JType stateType = getState.getReturnType(); bundle.setNeedsGwtConstructor(stateType.isClass()); } diff --git a/client/src/com/vaadin/Vaadin.gwt.xml b/client/src/com/vaadin/Vaadin.gwt.xml index fe9643232e..44357b24a0 100644 --- a/client/src/com/vaadin/Vaadin.gwt.xml +++ b/client/src/com/vaadin/Vaadin.gwt.xml @@ -23,14 +23,6 @@ - - - - - diff --git a/client/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java b/client/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java index 9b58eb1295..7e1c505fe9 100644 --- a/client/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java +++ b/client/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java @@ -65,7 +65,6 @@ import com.vaadin.terminal.gwt.client.communication.HasJavaScriptConnectorHelper import com.vaadin.terminal.gwt.client.communication.JsonDecoder; import com.vaadin.terminal.gwt.client.communication.JsonEncoder; import com.vaadin.terminal.gwt.client.communication.RpcManager; -import com.vaadin.terminal.gwt.client.communication.SerializerMap; import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; import com.vaadin.terminal.gwt.client.extensions.AbstractExtensionConnector; import com.vaadin.terminal.gwt.client.metadata.ConnectorBundleLoader; @@ -106,8 +105,6 @@ public class ApplicationConnection { public static final char VAR_ESCAPE_CHARACTER = '\u001b'; - private static SerializerMap serializerMap; - /** * A string that, if found in a non-JSON response to a UIDL request, will * cause the browser to refresh the page. If followed by a colon, optional @@ -215,7 +212,6 @@ public class ApplicationConnection { rpcManager = GWT.create(RpcManager.class); layoutManager = GWT.create(LayoutManager.class); layoutManager.setConnection(this); - serializerMap = GWT.create(SerializerMap.class); } public void init(WidgetSet widgetSet, ApplicationConfiguration cnf) { @@ -2577,8 +2573,4 @@ public class ApplicationConnection { LayoutManager getLayoutManager() { return layoutManager; } - - public SerializerMap getSerializerMap() { - return serializerMap; - } } diff --git a/client/src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java b/client/src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java index ef5090ec18..a98d08c368 100644 --- a/client/src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java +++ b/client/src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java @@ -31,6 +31,8 @@ import com.google.gwt.json.client.JSONValue; import com.vaadin.shared.Connector; import com.vaadin.terminal.gwt.client.ApplicationConnection; import com.vaadin.terminal.gwt.client.ConnectorMap; +import com.vaadin.terminal.gwt.client.metadata.NoDataException; +import com.vaadin.terminal.gwt.client.metadata.Property; import com.vaadin.terminal.gwt.client.metadata.Type; /** @@ -106,18 +108,42 @@ public class JsonDecoder { private static Object decodeObject(Type type, JSONValue jsonValue, Object target, ApplicationConnection connection) { - JSONSerializer serializer = connection.getSerializerMap() - .getSerializer(type.getBaseTypeName()); - // TODO handle case with no serializer found - // Currently getSerializer throws exception if not found - - if (target != null && serializer instanceof DiffJSONSerializer) { - DiffJSONSerializer diffSerializer = (DiffJSONSerializer) serializer; - diffSerializer.update(target, type, jsonValue, connection); - return target; + JSONSerializer serializer = (JSONSerializer) type + .findSerializer(); + if (serializer != null) { + if (target != null && serializer instanceof DiffJSONSerializer) { + DiffJSONSerializer diffSerializer = (DiffJSONSerializer) serializer; + diffSerializer.update(target, type, jsonValue, connection); + return target; + } else { + Object object = serializer.deserialize(type, jsonValue, + connection); + return object; + } } else { - Object object = serializer.deserialize(type, jsonValue, connection); - return object; + try { + Collection properties = type.getProperties(); + if (target == null) { + target = type.createInstance(); + } + JSONObject jsonObject = jsonValue.isObject(); + + for (Property property : properties) { + JSONValue encodedPropertyValue = jsonObject.get(property + .getName()); + if (encodedPropertyValue == null) { + continue; + } + Object propertyReference = property.getValue(target); + Object decodedValue = decodeValue(property.getType(), + encodedPropertyValue, propertyReference, connection); + property.setValue(target, decodedValue); + } + return target; + } catch (NoDataException e) { + throw new RuntimeException("Can not deserialize " + + type.getSignature(), e); + } } } diff --git a/client/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java b/client/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java index 3730cad4c3..9b28da8b34 100644 --- a/client/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java +++ b/client/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java @@ -33,6 +33,9 @@ import com.vaadin.shared.Connector; import com.vaadin.shared.JsonConstants; import com.vaadin.shared.communication.UidlValue; import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.metadata.NoDataException; +import com.vaadin.terminal.gwt.client.metadata.Property; +import com.vaadin.terminal.gwt.client.metadata.Type; /** * Encoder for converting RPC parameters and other values to JSON for transfer @@ -99,12 +102,33 @@ public class JsonEncoder { } else { // Try to find a generated serializer object, class name is the // type - transportType = value.getClass().getName(); - JSONSerializer serializer = connection.getSerializerMap() - .getSerializer(transportType); + Type type = new Type(value.getClass()); + + JSONSerializer serializer = (JSONSerializer) type + .findSerializer(); + if (serializer != null) { + return serializer.serialize(value, connection); + } else { + try { + Collection properties = type.getProperties(); + + JSONObject jsonObject = new JSONObject(); + for (Property property : properties) { + Object propertyValue = property.getValue(value); + JSONValue encodedPropertyValue = encode( + propertyValue, restrictToInternalTypes, + connection); + jsonObject.put(property.getName(), + encodedPropertyValue); + } + return jsonObject; + + } catch (NoDataException e) { + throw new RuntimeException("Can not encode " + + type.getSignature(), e); + } + } - // TODO handle case with no serializer found - return serializer.serialize(value, connection); } } } diff --git a/client/src/com/vaadin/terminal/gwt/client/communication/SerializerMap.java b/client/src/com/vaadin/terminal/gwt/client/communication/SerializerMap.java deleted file mode 100644 index 77df4c7b08..0000000000 --- a/client/src/com/vaadin/terminal/gwt/client/communication/SerializerMap.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2011 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.terminal.gwt.client.communication; - -/** - * Provide a mapping from a type (communicated between the server and the - * client) and a {@link JSONSerializer} instance. - * - * An implementation of this class is created at GWT compilation time by - * SerializerMapGenerator, so this interface can be instantiated with - * GWT.create(). - * - * @since 7.0 - */ -public interface SerializerMap { - - /** - * Returns a serializer instance for a given type. - * - * @param type - * type communicated on between the server and the client - * (currently fully qualified class name) - * @return serializer instance, not null - * @throws RuntimeException - * if no serializer is found - */ - // TODO better error handling in javadoc and in generator - public JSONSerializer getSerializer(String type); - -} diff --git a/client/src/com/vaadin/terminal/gwt/client/communication/Type.java b/client/src/com/vaadin/terminal/gwt/client/communication/Type.java deleted file mode 100644 index ff93234a1d..0000000000 --- a/client/src/com/vaadin/terminal/gwt/client/communication/Type.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2011 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.terminal.gwt.client.communication; - -public class Type { - private final String baseTypeName; - private final Type[] parameterTypes; - - public Type(String baseTypeName, Type[] parameterTypes) { - this.baseTypeName = baseTypeName; - this.parameterTypes = parameterTypes; - } - - public String getBaseTypeName() { - return baseTypeName; - } - - public Type[] getParameterTypes() { - return parameterTypes; - } - - @Override - public String toString() { - String string = baseTypeName; - if (parameterTypes != null) { - string += '<'; - for (int i = 0; i < parameterTypes.length; i++) { - if (i != 0) { - string += ','; - } - string += parameterTypes[i].toString(); - } - string += '>'; - } - - return string; - } - -} diff --git a/client/src/com/vaadin/terminal/gwt/client/metadata/Invoker.java b/client/src/com/vaadin/terminal/gwt/client/metadata/Invoker.java index 5f6a839da6..33e8776429 100644 --- a/client/src/com/vaadin/terminal/gwt/client/metadata/Invoker.java +++ b/client/src/com/vaadin/terminal/gwt/client/metadata/Invoker.java @@ -5,5 +5,5 @@ package com.vaadin.terminal.gwt.client.metadata; public interface Invoker { - public Object invoke(Object target, Object[] params); + public Object invoke(Object target, Object... params); } diff --git a/client/src/com/vaadin/terminal/gwt/client/metadata/Property.java b/client/src/com/vaadin/terminal/gwt/client/metadata/Property.java index 30d864a43f..082d032e64 100644 --- a/client/src/com/vaadin/terminal/gwt/client/metadata/Property.java +++ b/client/src/com/vaadin/terminal/gwt/client/metadata/Property.java @@ -5,16 +5,20 @@ package com.vaadin.terminal.gwt.client.metadata; public class Property { - private final Type type; + private final Type bean; private final String name; - public Property(Type type, String name) { - this.type = type; + public Property(Type bean, String name) { + this.bean = bean; this.name = name; } public Object getValue(Object bean) throws NoDataException { - return TypeDataStore.getGetter(this).invoke(bean, null); + return TypeDataStore.getGetter(this).invoke(bean); + } + + public void setValue(Object bean, Object value) throws NoDataException { + TypeDataStore.getSetter(this).invoke(bean, value); } public String getDelegateToWidgetMethod() { @@ -29,8 +33,12 @@ public class Property { } } + public Type getType() throws NoDataException { + return TypeDataStore.getType(this); + } + public String getSignature() { - return type.toString() + "." + name; + return bean.toString() + "." + name; } @Override @@ -50,4 +58,13 @@ public class Property { return getSignature().hashCode(); } + public String getName() { + return name; + } + + @Override + public String toString() { + return getSignature(); + } + } diff --git a/client/src/com/vaadin/terminal/gwt/client/metadata/Type.java b/client/src/com/vaadin/terminal/gwt/client/metadata/Type.java index 2dc5182845..d869cc2599 100644 --- a/client/src/com/vaadin/terminal/gwt/client/metadata/Type.java +++ b/client/src/com/vaadin/terminal/gwt/client/metadata/Type.java @@ -3,6 +3,10 @@ */ package com.vaadin.terminal.gwt.client.metadata; +import java.util.Collection; + +import com.vaadin.terminal.gwt.client.communication.JSONSerializer; + public class Type { private final String name; private final Type[] parameterTypes; @@ -27,13 +31,17 @@ public class Type { public Object createInstance() throws NoDataException { Invoker invoker = TypeDataStore.getConstructor(this); - return invoker.invoke(null, null); + return invoker.invoke(null); } public Method getMethod(String name) { return new Method(this, name); } + public Collection getProperties() throws NoDataException { + return TypeDataStore.getProperties(this); + } + public Property getProperty(String propertyName) { return new Property(this, propertyName); } @@ -82,4 +90,8 @@ public class Type { .createProxy(invokationHandler); } + public JSONSerializer findSerializer() { + return TypeDataStore.findSerializer(this); + } + } diff --git a/client/src/com/vaadin/terminal/gwt/client/metadata/TypeDataStore.java b/client/src/com/vaadin/terminal/gwt/client/metadata/TypeDataStore.java index f056e46d2d..55740f69da 100644 --- a/client/src/com/vaadin/terminal/gwt/client/metadata/TypeDataStore.java +++ b/client/src/com/vaadin/terminal/gwt/client/metadata/TypeDataStore.java @@ -4,17 +4,23 @@ package com.vaadin.terminal.gwt.client.metadata; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; +import com.vaadin.terminal.gwt.client.communication.JSONSerializer; + public class TypeDataStore { private static final String CONSTRUCTOR_NAME = "!new"; private final Map> identifiers = new HashMap>(); + private final Map serializerFactories = new HashMap(); private final Map proxyHandlers = new HashMap(); + private final Map> properties = new HashMap>(); private final Set delayedMethods = new HashSet(); private final Set lastonlyMethods = new HashSet(); @@ -23,6 +29,8 @@ public class TypeDataStore { private final Map invokers = new HashMap(); private final Map paramTypes = new HashMap(); + private final Map propertyTypes = new HashMap(); + private final Map setters = new HashMap(); private final Map getters = new HashMap(); private final Map delegateToWidget = new HashMap(); @@ -85,6 +93,10 @@ public class TypeDataStore { return getter; } + public void setGetter(Class clazz, String propertyName, Invoker invoker) { + getters.put(new Property(getType(clazz), propertyName), invoker); + } + public static String getDelegateToWidget(Property property) { return get().delegateToWidget.get(property); } @@ -148,4 +160,61 @@ public class TypeDataStore { public void setLastonly(Class clazz, String methodName) { lastonlyMethods.add(getType(clazz).getMethod(methodName)); } + + public static Collection getProperties(Type type) + throws NoDataException { + Collection properties = get().properties.get(type); + if (properties == null) { + throw new NoDataException("No property list for " + + type.getSignature()); + } + return properties; + } + + public void setProperties(Class clazz, String[] propertyNames) { + Set properties = new HashSet(); + Type type = getType(clazz); + for (String name : propertyNames) { + properties.add(new Property(type, name)); + } + this.properties.put(type, Collections.unmodifiableSet(properties)); + } + + public static Type getType(Property property) throws NoDataException { + Type type = get().propertyTypes.get(property); + if (type == null) { + throw new NoDataException("No return type for " + + property.getSignature()); + } + return type; + } + + public void setPropertyType(Class clazz, String propertName, Type type) { + propertyTypes.put(new Property(getType(clazz), propertName), type); + } + + public static Invoker getSetter(Property property) throws NoDataException { + Invoker setter = get().setters.get(property); + if (setter == null) { + throw new NoDataException("No setter for " + + property.getSignature()); + } + return setter; + } + + public void setSetter(Class clazz, String propertyName, Invoker setter) { + setters.put(new Property(getType(clazz), propertyName), setter); + } + + public void setSerializerFactory(Class clazz, Invoker factory) { + serializerFactories.put(getType(clazz), factory); + } + + public static JSONSerializer findSerializer(Type type) { + Invoker factoryCreator = get().serializerFactories.get(type); + if (factoryCreator == null) { + return null; + } + return (JSONSerializer) factoryCreator.invoke(null); + } }