Browse Source

Use ConnectorBundle for JSON encoding and decoding (#9371)

tags/7.0.0.beta1
Leif Åstrand 11 years ago
parent
commit
02878bd07a
24 changed files with 1154 additions and 1048 deletions
  1. 147
    16
      client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/ConnectorBundleLoaderFactory.java
  2. 0
    484
      client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerGenerator.java
  3. 0
    377
      client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerMapGenerator.java
  4. 90
    0
      client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/ArraySerializer.java
  5. 6
    0
      client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/ClientRpcVisitor.java
  6. 309
    36
      client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/ConnectorBundle.java
  7. 43
    0
      client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/CustomSerializer.java
  8. 58
    0
      client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/EnumSerializer.java
  9. 26
    0
      client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/GeneratedSerializer.java
  10. 88
    0
      client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/JsonSerializer.java
  11. 124
    0
      client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/MethodProperty.java
  12. 84
    0
      client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/Property.java
  13. 6
    0
      client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/ServerRpcVisitor.java
  14. 2
    0
      client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/StateInitVisitor.java
  15. 0
    8
      client/src/com/vaadin/Vaadin.gwt.xml
  16. 0
    8
      client/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java
  17. 37
    11
      client/src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java
  18. 29
    5
      client/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java
  19. 0
    44
      client/src/com/vaadin/terminal/gwt/client/communication/SerializerMap.java
  20. 0
    52
      client/src/com/vaadin/terminal/gwt/client/communication/Type.java
  21. 1
    1
      client/src/com/vaadin/terminal/gwt/client/metadata/Invoker.java
  22. 22
    5
      client/src/com/vaadin/terminal/gwt/client/metadata/Property.java
  23. 13
    1
      client/src/com/vaadin/terminal/gwt/client/metadata/Type.java
  24. 69
    0
      client/src/com/vaadin/terminal/gwt/client/metadata/TypeDataStore.java

+ 147
- 16
client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/ConnectorBundleLoaderFactory.java View File

@@ -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<JType, GeneratedSerializer> serializers = bundle.getSerializers();
for (Entry<JType, GeneratedSerializer> 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<Property> 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<Property> 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<Property> 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<JClassType> needsPropertyListing = bundle.getNeedsPropertyListing();
for (JClassType type : needsPropertyListing) {
w.print("store.setProperties(");
writeClassLiteral(w, type);
w.print(", new String[] {");

Set<String> usedPropertyNames = new HashSet<String>();
Collection<Property> 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<JClassType> 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<JMethod> 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<JMethod> 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<JClassType> 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<TypeVisitor> 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(")");
}

+ 0
- 484
client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerGenerator.java View File

@@ -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<String> usedFieldNames = new HashSet<String>();

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<JMethod> getSetters(JClassType beanType) {

List<JMethod> setterMethods = new ArrayList<JMethod>();

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;
}
}
}

+ 0
- 377
client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerMapGenerator.java View File

@@ -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<JClassType> typesNeedingSerializers = findTypesNeedingSerializers(
typeOracle, logger);
checkForUnserializableTypes(typesNeedingSerializers, typeOracle,
logger);
Set<JClassType> typesWithExistingSerializers = findTypesWithExistingSerializers(
typeOracle, logger);
Set<JClassType> serializerMappings = new HashSet<JClassType>();
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<JClassType> 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<JClassType> 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<JClassType> types = new HashSet<JClassType>();
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<JClassType> 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<JClassType> findTypesNeedingSerializers(TypeOracle typeOracle,
TreeLogger logger) {
logger.log(Type.DEBUG, "Detecting serializable data types...");

HashSet<JClassType> types = new HashSet<JClassType>();

// 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<JClassType> 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<JClassType> 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<JClassType> 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<String>. It is
// possible that we need to handle the cases somehow, for
// instance for List<MyObject>.
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<Class<?>> frameworkHandledTypes = new HashSet<Class<?>>();
{
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;
}
}

+ 90
- 0
client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/ArraySerializer.java View File

@@ -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;");
}

}

+ 6
- 0
client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/ClientRpcVisitor.java View File

@@ -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);
}
}
}
}

+ 309
- 36
client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/ConnectorBundle.java View File

@@ -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<TypeVisitor> visitors;
private final Map<JType, JClassType> customSerializers;

private final Set<JType> hasSerializeSupport = new HashSet<JType>();
private final Set<JType> needsSerializeSupport = new HashSet<JType>();
private final Map<JType, GeneratedSerializer> serializers = new HashMap<JType, GeneratedSerializer>();

private final Set<JClassType> needsPropertyList = new HashSet<JClassType>();
private final Set<JClassType> needsGwtConstructor = new HashSet<JClassType>();
private final Map<JClassType, Set<String>> identifiers = new HashMap<JClassType, Set<String>>();
private final Set<JClassType> visitedTypes = new HashSet<JClassType>();
private final Set<JClassType> visitQueue = new HashSet<JClassType>();
private final Map<JClassType, Set<JMethod>> needsReturnType = new HashMap<JClassType, Set<JMethod>>();

private final Collection<TypeVisitor> visitors;

private final Set<JClassType> needsProxySupport = new HashSet<JClassType>();

private final Map<JClassType, Set<String>> identifiers = new HashMap<JClassType, Set<String>>();
private final Map<JClassType, Set<JMethod>> needsReturnType = new HashMap<JClassType, Set<JMethod>>();
private final Map<JClassType, Set<JMethod>> needsInvoker = new HashMap<JClassType, Set<JMethod>>();
private final Map<JClassType, Set<JMethod>> needsParamTypes = new HashMap<JClassType, Set<JMethod>>();
private final Map<JClassType, Set<JMethod>> needsDelayedInfo = new HashMap<JClassType, Set<JMethod>>();

private final Set<Property> needsSetter = new HashSet<Property>();
private final Set<Property> needsType = new HashSet<Property>();
private final Set<Property> needsGetter = new HashSet<Property>();

private ConnectorBundle(String name, ConnectorBundle previousBundle,
Collection<TypeVisitor> visitors) {
Collection<TypeVisitor> visitors,
Map<JType, JClassType> 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<TypeVisitor> visitors,
TypeOracle oracle) throws NotFoundException {
this(name, null, visitors, findCustomSerializers(oracle));
}

private static Map<JType, JClassType> findCustomSerializers(
TypeOracle oracle) throws NotFoundException {
Map<JType, JClassType> serializers = new HashMap<JType, JClassType>();

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<TypeVisitor> 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<JType> 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<JClassType> 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<JType, GeneratedSerializer> 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<Property> getNeedsGetter() {
return Collections.unmodifiableSet(needsGetter);
}

private void setNeedsType(Property property) {
if (!isNeedsType(property)) {
needsType.add(property);
}
}

public Set<Property> 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<Property> 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<JClassType> getNeedsPropertyListing() {
return Collections.unmodifiableSet(needsPropertyList);
}

public Collection<Property> getProperties(JClassType type) {
HashSet<Property> properties = new HashSet<Property>();

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<JClassType, Set<JMethod>> getNeedsDelayedInfo() {
return Collections.unmodifiableMap(needsDelayedInfo);
}

public void setNeedsSerialize(JType type) {
if (!hasSserializeSupport(type)) {
needsSerializeSupport.add(type);
}
}

private static Set<Class<?>> frameworkHandledTypes = new HashSet<Class<?>>();
{
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);
}
}
}

+ 43
- 0
client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/CustomSerializer.java View File

@@ -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(");");
}
}

+ 58
- 0
client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/EnumSerializer.java View File

@@ -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());");
}

}

+ 26
- 0
client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/GeneratedSerializer.java View File

@@ -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;
}

+ 88
- 0
client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/JsonSerializer.java View File

@@ -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);

}

+ 124
- 0
client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/MethodProperty.java View File

@@ -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<MethodProperty> findProperties(JClassType type) {
Collection<MethodProperty> properties = new ArrayList<MethodProperty>();

List<JMethod> 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<JMethod> getSetters(JClassType beanType) {
List<JMethod> setterMethods = new ArrayList<JMethod>();

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);
}

}

+ 84
- 0
client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/Property.java View File

@@ -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();
}

}

+ 6
- 0
client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/ServerRpcVisitor.java View File

@@ -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);
}
}
}
}

+ 2
- 0
client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/metadata/StateInitVisitor.java View File

@@ -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());
}

+ 0
- 8
client/src/com/vaadin/Vaadin.gwt.xml View File

@@ -23,14 +23,6 @@
<when-type-is class="com.google.gwt.core.client.impl.SchedulerImpl" />
</replace-with>

<!-- Generators for serializators for classes used in communication between
server and client -->
<generate-with
class="com.vaadin.terminal.gwt.widgetsetutils.SerializerMapGenerator">
<when-type-is
class="com.vaadin.terminal.gwt.client.communication.SerializerMap" />
</generate-with>

<replace-with class="com.vaadin.terminal.gwt.client.VDebugConsole">
<when-type-is class="com.vaadin.terminal.gwt.client.Console" />
</replace-with>

+ 0
- 8
client/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java View File

@@ -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;
}
}

+ 37
- 11
client/src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java View File

@@ -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<Object> 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<Object> diffSerializer = (DiffJSONSerializer<Object>) serializer;
diffSerializer.update(target, type, jsonValue, connection);
return target;
JSONSerializer<Object> serializer = (JSONSerializer<Object>) type
.findSerializer();
if (serializer != null) {
if (target != null && serializer instanceof DiffJSONSerializer<?>) {
DiffJSONSerializer<Object> diffSerializer = (DiffJSONSerializer<Object>) 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<Property> 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);
}
}
}


+ 29
- 5
client/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java View File

@@ -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<Object> serializer = (JSONSerializer<Object>) type
.findSerializer();
if (serializer != null) {
return serializer.serialize(value, connection);
} else {
try {
Collection<Property> 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);
}
}
}

+ 0
- 44
client/src/com/vaadin/terminal/gwt/client/communication/SerializerMap.java View File

@@ -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);

}

+ 0
- 52
client/src/com/vaadin/terminal/gwt/client/communication/Type.java View File

@@ -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;
}

}

+ 1
- 1
client/src/com/vaadin/terminal/gwt/client/metadata/Invoker.java View File

@@ -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);
}

+ 22
- 5
client/src/com/vaadin/terminal/gwt/client/metadata/Property.java View File

@@ -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();
}

}

+ 13
- 1
client/src/com/vaadin/terminal/gwt/client/metadata/Type.java View File

@@ -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<Property> 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);
}

}

+ 69
- 0
client/src/com/vaadin/terminal/gwt/client/metadata/TypeDataStore.java View File

@@ -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<String, Class<?>> identifiers = new HashMap<String, Class<?>>();

private final Map<Type, Invoker> serializerFactories = new HashMap<Type, Invoker>();
private final Map<Type, ProxyHandler> proxyHandlers = new HashMap<Type, ProxyHandler>();
private final Map<Type, Collection<Property>> properties = new HashMap<Type, Collection<Property>>();

private final Set<Method> delayedMethods = new HashSet<Method>();
private final Set<Method> lastonlyMethods = new HashSet<Method>();
@@ -23,6 +29,8 @@ public class TypeDataStore {
private final Map<Method, Invoker> invokers = new HashMap<Method, Invoker>();
private final Map<Method, Type[]> paramTypes = new HashMap<Method, Type[]>();

private final Map<Property, Type> propertyTypes = new HashMap<Property, Type>();
private final Map<Property, Invoker> setters = new HashMap<Property, Invoker>();
private final Map<Property, Invoker> getters = new HashMap<Property, Invoker>();
private final Map<Property, String> delegateToWidget = new HashMap<Property, String>();

@@ -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<Property> getProperties(Type type)
throws NoDataException {
Collection<Property> 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<Property> properties = new HashSet<Property>();
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);
}
}

Loading…
Cancel
Save