summaryrefslogtreecommitdiffstats
path: root/client-compiler
diff options
context:
space:
mode:
authorArtur Signell <artur@vaadin.com>2012-08-14 15:11:40 +0300
committerArtur Signell <artur@vaadin.com>2012-08-14 16:42:25 +0300
commit66d60ab6015ffaadce3bf6c906b9463c30649b81 (patch)
tree28a1a33db4347046c94800379a50c4599837bdec /client-compiler
parenteb51296d21be72954a0fea624519bcc66bb29f87 (diff)
downloadvaadin-framework-66d60ab6015ffaadce3bf6c906b9463c30649b81.tar.gz
vaadin-framework-66d60ab6015ffaadce3bf6c906b9463c30649b81.zip
Moved client compiler sources to own source folder (#9299)
Diffstat (limited to 'client-compiler')
-rw-r--r--client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/AbstractConnectorClassBasedFactoryGenerator.java145
-rw-r--r--client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/AcceptCriteriaFactoryGenerator.java127
-rw-r--r--client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/ClassPathExplorer.java462
-rw-r--r--client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/ConnectorStateFactoryGenerator.java29
-rw-r--r--client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/ConnectorWidgetFactoryGenerator.java29
-rw-r--r--client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/CustomWidgetMapGenerator.java84
-rw-r--r--client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/EagerWidgetMapGenerator.java29
-rw-r--r--client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/GeneratedRpcMethodProviderGenerator.java211
-rw-r--r--client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/LazyWidgetMapGenerator.java23
-rw-r--r--client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/RpcProxyCreatorGenerator.java126
-rw-r--r--client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/RpcProxyGenerator.java142
-rw-r--r--client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerGenerator.java458
-rw-r--r--client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerMapGenerator.java365
-rw-r--r--client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/WidgetMapGenerator.java398
-rw-r--r--client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/WidgetSetBuilder.java201
-rwxr-xr-xclient-compiler/src/com/vaadin/tools/WidgetsetCompiler.java94
16 files changed, 2923 insertions, 0 deletions
diff --git a/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/AbstractConnectorClassBasedFactoryGenerator.java b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/AbstractConnectorClassBasedFactoryGenerator.java
new file mode 100644
index 0000000000..3a13fceece
--- /dev/null
+++ b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/AbstractConnectorClassBasedFactoryGenerator.java
@@ -0,0 +1,145 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.terminal.gwt.widgetsetutils;
+
+import java.io.PrintWriter;
+import java.util.Date;
+
+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.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+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.user.rebind.ClassSourceFileComposerFactory;
+import com.google.gwt.user.rebind.SourceWriter;
+import com.vaadin.terminal.gwt.client.ServerConnector;
+import com.vaadin.terminal.gwt.client.ui.ConnectorClassBasedFactory;
+import com.vaadin.terminal.gwt.client.ui.ConnectorClassBasedFactory.Creator;
+
+/**
+ * GWT generator that creates a lookup method for
+ * {@link ConnectorClassBasedFactory} instances.
+ *
+ * @since 7.0
+ */
+public abstract class AbstractConnectorClassBasedFactoryGenerator extends
+ Generator {
+
+ @Override
+ public String generate(TreeLogger logger, GeneratorContext context,
+ String typeName) throws UnableToCompleteException {
+
+ try {
+ // get classType and save instance variables
+ return generateConnectorClassBasedFactory(typeName, logger, context);
+ } catch (Exception e) {
+ logger.log(TreeLogger.ERROR, typeName + " creation failed", e);
+ throw new UnableToCompleteException();
+ }
+ }
+
+ private String generateConnectorClassBasedFactory(String typeName,
+ TreeLogger logger, GeneratorContext context)
+ throws NotFoundException {
+ TypeOracle typeOracle = context.getTypeOracle();
+
+ JClassType classType = typeOracle.getType(typeName);
+ String superName = classType.getSimpleSourceName();
+ String packageName = classType.getPackage().getName();
+ String className = superName + "Impl";
+
+ // 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 packageName + "." + className;
+ }
+
+ Date date = new Date();
+
+ // init composer, set class properties, create source writer
+ ClassSourceFileComposerFactory composer = null;
+ composer = new ClassSourceFileComposerFactory(packageName, className);
+ composer.addImport(GWT.class.getName());
+ composer.addImport(Creator.class.getCanonicalName());
+ composer.setSuperclass(superName);
+
+ SourceWriter sourceWriter = composer.createSourceWriter(context,
+ printWriter);
+ sourceWriter.indent();
+
+ // public ConnectorStateFactoryImpl() {
+ sourceWriter.println("public " + className + "() {");
+ sourceWriter.indent();
+
+ JClassType serverConnectorType = typeOracle.getType(getConnectorType()
+ .getCanonicalName());
+ for (JClassType connector : serverConnectorType.getSubtypes()) {
+ // addCreator(TextAreaConnector.class, new Creator<SharedState>() {
+ if (connector.isInterface() != null || connector.isAbstract()) {
+ continue;
+ }
+
+ JClassType targetType = getTargetType(connector);
+ if (targetType.isAbstract()) {
+ continue;
+ }
+
+ sourceWriter.println("addCreator("
+ + connector.getQualifiedSourceName()
+ + ".class, new Creator<"
+ + targetType.getQualifiedSourceName() + ">() {");
+ // public SharedState create() {
+ sourceWriter.println("public "
+ + targetType.getQualifiedSourceName() + " create() {");
+ // return GWT.create(TextAreaState.class);
+ sourceWriter.println("return GWT.create("
+ + targetType.getQualifiedSourceName() + ".class);");
+ // }
+ sourceWriter.println("}");
+ // });
+ sourceWriter.println("});");
+ }
+
+ // End of constructor
+ 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)");
+ return packageName + "." + className;
+
+ }
+
+ protected abstract Class<? extends ServerConnector> getConnectorType();
+
+ protected abstract JClassType getTargetType(JClassType connectorType);
+
+ protected JClassType getGetterReturnType(JClassType connector,
+ String getterName) {
+ try {
+ JMethod getMethod = connector.getMethod(getterName, new JType[] {});
+ return (JClassType) getMethod.getReturnType();
+ } catch (NotFoundException e) {
+ return getGetterReturnType(connector.getSuperclass(), getterName);
+ }
+
+ }
+
+}
diff --git a/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/AcceptCriteriaFactoryGenerator.java b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/AcceptCriteriaFactoryGenerator.java
new file mode 100644
index 0000000000..e5e2ee1f2c
--- /dev/null
+++ b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/AcceptCriteriaFactoryGenerator.java
@@ -0,0 +1,127 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.terminal.gwt.widgetsetutils;
+
+import java.io.PrintWriter;
+import java.util.Date;
+
+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.JClassType;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
+import com.google.gwt.user.rebind.SourceWriter;
+import com.vaadin.shared.ui.dd.AcceptCriterion;
+import com.vaadin.terminal.gwt.client.ui.dd.VAcceptCriterion;
+import com.vaadin.terminal.gwt.client.ui.dd.VAcceptCriterionFactory;
+
+/**
+ * GWT generator to build {@link VAcceptCriterionFactory} implementation
+ * dynamically based on {@link AcceptCriterion} annotations available in
+ * classpath.
+ *
+ */
+public class AcceptCriteriaFactoryGenerator extends Generator {
+
+ private String packageName;
+ private String className;
+
+ @Override
+ public String generate(TreeLogger logger, GeneratorContext context,
+ String typeName) throws UnableToCompleteException {
+
+ try {
+ TypeOracle typeOracle = context.getTypeOracle();
+
+ // get classType and save instance variables
+ JClassType classType = typeOracle.getType(typeName);
+ packageName = classType.getPackage().getName();
+ className = classType.getSimpleSourceName() + "Impl";
+ // Generate class source code
+ generateClass(logger, context);
+ } catch (Exception e) {
+ logger.log(TreeLogger.ERROR,
+ "Accept criterion factory creation failed", e);
+ }
+ // return the fully qualifed name of the class generated
+ return packageName + "." + className;
+ }
+
+ /**
+ * Generate source code for WidgetMapImpl
+ *
+ * @param logger
+ * Logger object
+ * @param context
+ * Generator context
+ */
+ private void generateClass(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,
+ // return (WidgetMap is equal to all permutations atm)
+ if (printWriter == null) {
+ return;
+ }
+ logger.log(Type.INFO, "Detecting available criteria ...");
+ Date date = new Date();
+
+ // 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.setSuperclass("com.vaadin.terminal.gwt.client.ui.dd.VAcceptCriterionFactory");
+ SourceWriter sourceWriter = composer.createSourceWriter(context,
+ printWriter);
+
+ // generator constructor source code
+ generateInstantiatorMethod(sourceWriter, context, logger);
+ // 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)");
+
+ }
+
+ private void generateInstantiatorMethod(SourceWriter sourceWriter,
+ GeneratorContext context, TreeLogger logger) {
+
+ sourceWriter.println("public VAcceptCriterion get(String name) {");
+ sourceWriter.indent();
+
+ sourceWriter.println("name = name.intern();");
+
+ JClassType criteriaType = context.getTypeOracle().findType(
+ VAcceptCriterion.class.getName());
+ for (JClassType clientClass : criteriaType.getSubtypes()) {
+ AcceptCriterion annotation = clientClass
+ .getAnnotation(AcceptCriterion.class);
+ if (annotation != null) {
+ String clientClassName = clientClass.getQualifiedSourceName();
+ Class<?> serverClass = clientClass.getAnnotation(
+ AcceptCriterion.class).value();
+ String serverClassName = serverClass.getCanonicalName();
+ logger.log(Type.INFO, "creating mapping for " + serverClassName);
+ sourceWriter.print("if (\"");
+ sourceWriter.print(serverClassName);
+ sourceWriter.print("\" == name) return GWT.create(");
+ sourceWriter.print(clientClassName);
+ sourceWriter.println(".class );");
+ sourceWriter.print("else ");
+ }
+ }
+
+ sourceWriter.println("return null;");
+ sourceWriter.outdent();
+ sourceWriter.println("}");
+ }
+}
diff --git a/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/ClassPathExplorer.java b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/ClassPathExplorer.java
new file mode 100644
index 0000000000..6ee30183c1
--- /dev/null
+++ b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/ClassPathExplorer.java
@@ -0,0 +1,462 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.terminal.gwt.widgetsetutils;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.net.JarURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Utility class to collect widgetset related information from classpath.
+ * Utility will seek all directories from classpaths, and jar files having
+ * "Vaadin-Widgetsets" key in their manifest file.
+ * <p>
+ * Used by WidgetMapGenerator and ide tools to implement some monkey coding for
+ * you.
+ * <p>
+ * Developer notice: If you end up reading this comment, I guess you have faced
+ * a sluggish performance of widget compilation or unreliable detection of
+ * components in your classpaths. The thing you might be able to do is to use
+ * annotation processing tool like apt to generate the needed information. Then
+ * either use that information in {@link WidgetMapGenerator} or create the
+ * appropriate monkey code for gwt directly in annotation processor and get rid
+ * of {@link WidgetMapGenerator}. Using annotation processor might be a good
+ * idea when dropping Java 1.5 support (integrated to javac in 6).
+ *
+ */
+public class ClassPathExplorer {
+
+ private static final String VAADIN_ADDON_VERSION_ATTRIBUTE = "Vaadin-Package-Version";
+
+ /**
+ * File filter that only accepts directories.
+ */
+ private final static FileFilter DIRECTORIES_ONLY = new FileFilter() {
+ @Override
+ public boolean accept(File f) {
+ if (f.exists() && f.isDirectory()) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ };
+
+ /**
+ * Raw class path entries as given in the java class path string. Only
+ * entries that could include widgets/widgetsets are listed (primarily
+ * directories, Vaadin JARs and add-on JARs).
+ */
+ private static List<String> rawClasspathEntries = getRawClasspathEntries();
+
+ /**
+ * Map from identifiers (either a package name preceded by the path and a
+ * slash, or a URL for a JAR file) to the corresponding URLs. This is
+ * constructed from the class path.
+ */
+ private static Map<String, URL> classpathLocations = getClasspathLocations(rawClasspathEntries);
+
+ /**
+ * No instantiation from outside, callable methods are static.
+ */
+ private ClassPathExplorer() {
+ }
+
+ /**
+ * Finds the names and locations of widgetsets available on the class path.
+ *
+ * @return map from widgetset classname to widgetset location URL
+ */
+ public static Map<String, URL> getAvailableWidgetSets() {
+ long start = System.currentTimeMillis();
+ Map<String, URL> widgetsets = new HashMap<String, URL>();
+ Set<String> keySet = classpathLocations.keySet();
+ for (String location : keySet) {
+ searchForWidgetSets(location, widgetsets);
+ }
+ long end = System.currentTimeMillis();
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("Widgetsets found from classpath:\n");
+ for (String ws : widgetsets.keySet()) {
+ sb.append("\t");
+ sb.append(ws);
+ sb.append(" in ");
+ sb.append(widgetsets.get(ws));
+ sb.append("\n");
+ }
+ final Logger logger = getLogger();
+ logger.info(sb.toString());
+ logger.info("Search took " + (end - start) + "ms");
+ return widgetsets;
+ }
+
+ /**
+ * Finds all GWT modules / Vaadin widgetsets in a valid location.
+ *
+ * If the location is a directory, all GWT modules (files with the
+ * ".gwt.xml" extension) are added to widgetsets.
+ *
+ * If the location is a JAR file, the comma-separated values of the
+ * "Vaadin-Widgetsets" attribute in its manifest are added to widgetsets.
+ *
+ * @param locationString
+ * an entry in {@link #classpathLocations}
+ * @param widgetsets
+ * a map from widgetset name (including package, with dots as
+ * separators) to a URL (see {@link #classpathLocations}) - new
+ * entries are added to this map
+ */
+ private static void searchForWidgetSets(String locationString,
+ Map<String, URL> widgetsets) {
+
+ URL location = classpathLocations.get(locationString);
+ File directory = new File(location.getFile());
+
+ if (directory.exists() && !directory.isHidden()) {
+ // Get the list of the files contained in the directory
+ String[] files = directory.list();
+ for (int i = 0; i < files.length; i++) {
+ // we are only interested in .gwt.xml files
+ if (!files[i].endsWith(".gwt.xml")) {
+ continue;
+ }
+
+ // remove the .gwt.xml extension
+ String classname = files[i].substring(0, files[i].length() - 8);
+ String packageName = locationString.substring(locationString
+ .lastIndexOf("/") + 1);
+ classname = packageName + "." + classname;
+
+ if (!WidgetSetBuilder.isWidgetset(classname)) {
+ // Only return widgetsets and not GWT modules to avoid
+ // comparing modules and widgetsets
+ continue;
+ }
+
+ if (!widgetsets.containsKey(classname)) {
+ String packagePath = packageName.replaceAll("\\.", "/");
+ String basePath = location.getFile().replaceAll(
+ "/" + packagePath + "$", "");
+ try {
+ URL url = new URL(location.getProtocol(),
+ location.getHost(), location.getPort(),
+ basePath);
+ widgetsets.put(classname, url);
+ } catch (MalformedURLException e) {
+ // should never happen as based on an existing URL,
+ // only changing end of file name/path part
+ getLogger().log(Level.SEVERE,
+ "Error locating the widgetset " + classname, e);
+ }
+ }
+ }
+ } else {
+
+ try {
+ // check files in jar file, entries will list all directories
+ // and files in jar
+
+ URLConnection openConnection = location.openConnection();
+ if (openConnection instanceof JarURLConnection) {
+ JarURLConnection conn = (JarURLConnection) openConnection;
+
+ JarFile jarFile = conn.getJarFile();
+
+ Manifest manifest = jarFile.getManifest();
+ if (manifest == null) {
+ // No manifest so this is not a Vaadin Add-on
+ return;
+ }
+ String value = manifest.getMainAttributes().getValue(
+ "Vaadin-Widgetsets");
+ if (value != null) {
+ String[] widgetsetNames = value.split(",");
+ for (int i = 0; i < widgetsetNames.length; i++) {
+ String widgetsetname = widgetsetNames[i].trim()
+ .intern();
+ if (!widgetsetname.equals("")) {
+ widgetsets.put(widgetsetname, location);
+ }
+ }
+ }
+ }
+ } catch (IOException e) {
+ getLogger().log(Level.WARNING, "Error parsing jar file", e);
+ }
+
+ }
+ }
+
+ /**
+ * Splits the current class path into entries, and filters them accepting
+ * directories, Vaadin add-on JARs with widgetsets and Vaadin JARs.
+ *
+ * Some other non-JAR entries may also be included in the result.
+ *
+ * @return filtered list of class path entries
+ */
+ private final static List<String> getRawClasspathEntries() {
+ // try to keep the order of the classpath
+ List<String> locations = new ArrayList<String>();
+
+ String pathSep = System.getProperty("path.separator");
+ String classpath = System.getProperty("java.class.path");
+
+ if (classpath.startsWith("\"")) {
+ classpath = classpath.substring(1);
+ }
+ if (classpath.endsWith("\"")) {
+ classpath = classpath.substring(0, classpath.length() - 1);
+ }
+
+ getLogger().fine("Classpath: " + classpath);
+
+ String[] split = classpath.split(pathSep);
+ for (int i = 0; i < split.length; i++) {
+ String classpathEntry = split[i];
+ if (acceptClassPathEntry(classpathEntry)) {
+ locations.add(classpathEntry);
+ }
+ }
+
+ return locations;
+ }
+
+ /**
+ * Determine every URL location defined by the current classpath, and it's
+ * associated package name.
+ *
+ * See {@link #classpathLocations} for information on output format.
+ *
+ * @param rawClasspathEntries
+ * raw class path entries as split from the Java class path
+ * string
+ * @return map of classpath locations, see {@link #classpathLocations}
+ */
+ private final static Map<String, URL> getClasspathLocations(
+ List<String> rawClasspathEntries) {
+ long start = System.currentTimeMillis();
+ // try to keep the order of the classpath
+ Map<String, URL> locations = new LinkedHashMap<String, URL>();
+ for (String classpathEntry : rawClasspathEntries) {
+ File file = new File(classpathEntry);
+ include(null, file, locations);
+ }
+ long end = System.currentTimeMillis();
+ Logger logger = getLogger();
+ if (logger.isLoggable(Level.FINE)) {
+ logger.fine("getClassPathLocations took " + (end - start) + "ms");
+ }
+ return locations;
+ }
+
+ /**
+ * Checks a class path entry to see whether it can contain widgets and
+ * widgetsets.
+ *
+ * All directories are automatically accepted. JARs are accepted if they
+ * have the "Vaadin-Widgetsets" attribute in their manifest or the JAR file
+ * name contains "vaadin-" or ".vaadin.".
+ *
+ * Also other non-JAR entries may be accepted, the caller should be prepared
+ * to handle them.
+ *
+ * @param classpathEntry
+ * class path entry string as given in the Java class path
+ * @return true if the entry should be considered when looking for widgets
+ * or widgetsets
+ */
+ private static boolean acceptClassPathEntry(String classpathEntry) {
+ if (!classpathEntry.endsWith(".jar")) {
+ // accept all non jars (practically directories)
+ return true;
+ } else {
+ // accepts jars that comply with vaadin-component packaging
+ // convention (.vaadin. or vaadin- as distribution packages),
+ if (classpathEntry.contains("vaadin-")
+ || classpathEntry.contains(".vaadin.")) {
+ return true;
+ } else {
+ URL url;
+ try {
+ url = new URL("file:"
+ + new File(classpathEntry).getCanonicalPath());
+ url = new URL("jar:" + url.toExternalForm() + "!/");
+ JarURLConnection conn = (JarURLConnection) url
+ .openConnection();
+ getLogger().fine(url.toString());
+ JarFile jarFile = conn.getJarFile();
+ Manifest manifest = jarFile.getManifest();
+ if (manifest != null) {
+ Attributes mainAttributes = manifest
+ .getMainAttributes();
+ if (mainAttributes.getValue("Vaadin-Widgetsets") != null) {
+ return true;
+ }
+ }
+ } catch (MalformedURLException e) {
+ getLogger().log(Level.FINEST, "Failed to inspect JAR file",
+ e);
+ } catch (IOException e) {
+ getLogger().log(Level.FINEST, "Failed to inspect JAR file",
+ e);
+ }
+
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Recursively add subdirectories and jar files to locations - see
+ * {@link #classpathLocations}.
+ *
+ * @param name
+ * @param file
+ * @param locations
+ */
+ private final static void include(String name, File file,
+ Map<String, URL> locations) {
+ if (!file.exists()) {
+ return;
+ }
+ if (!file.isDirectory()) {
+ // could be a JAR file
+ includeJar(file, locations);
+ return;
+ }
+
+ if (file.isHidden() || file.getPath().contains(File.separator + ".")) {
+ return;
+ }
+
+ if (name == null) {
+ name = "";
+ } else {
+ name += ".";
+ }
+
+ // add all directories recursively
+ File[] dirs = file.listFiles(DIRECTORIES_ONLY);
+ for (int i = 0; i < dirs.length; i++) {
+ try {
+ // add the present directory
+ if (!dirs[i].isHidden()
+ && !dirs[i].getPath().contains(File.separator + ".")) {
+ String key = dirs[i].getCanonicalPath() + "/" + name
+ + dirs[i].getName();
+ locations.put(key,
+ new URL("file://" + dirs[i].getCanonicalPath()));
+ }
+ } catch (Exception ioe) {
+ return;
+ }
+ include(name + dirs[i].getName(), dirs[i], locations);
+ }
+ }
+
+ /**
+ * Add a jar file to locations - see {@link #classpathLocations}.
+ *
+ * @param name
+ * @param locations
+ */
+ private static void includeJar(File file, Map<String, URL> locations) {
+ try {
+ URL url = new URL("file:" + file.getCanonicalPath());
+ url = new URL("jar:" + url.toExternalForm() + "!/");
+ JarURLConnection conn = (JarURLConnection) url.openConnection();
+ JarFile jarFile = conn.getJarFile();
+ if (jarFile != null) {
+ // the key does not matter here as long as it is unique
+ locations.put(url.toString(), url);
+ }
+ } catch (Exception e) {
+ // e.printStackTrace();
+ return;
+ }
+
+ }
+
+ /**
+ * Find and return the default source directory where to create new
+ * widgetsets.
+ *
+ * Return the first directory (not a JAR file etc.) on the classpath by
+ * default.
+ *
+ * TODO this could be done better...
+ *
+ * @return URL
+ */
+ public static URL getDefaultSourceDirectory() {
+
+ final Logger logger = getLogger();
+
+ if (logger.isLoggable(Level.FINE)) {
+ logger.fine("classpathLocations values:");
+ ArrayList<String> locations = new ArrayList<String>(
+ classpathLocations.keySet());
+ for (String location : locations) {
+ logger.fine(String.valueOf(classpathLocations.get(location)));
+ }
+ }
+
+ Iterator<String> it = rawClasspathEntries.iterator();
+ while (it.hasNext()) {
+ String entry = it.next();
+
+ File directory = new File(entry);
+ if (directory.exists() && !directory.isHidden()
+ && directory.isDirectory()) {
+ try {
+ return new URL("file://" + directory.getCanonicalPath());
+ } catch (MalformedURLException e) {
+ logger.log(Level.FINEST, "Ignoring exception", e);
+ // ignore: continue to the next classpath entry
+ } catch (IOException e) {
+ logger.log(Level.FINEST, "Ignoring exception", e);
+ // ignore: continue to the next classpath entry
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Test method for helper tool
+ */
+ public static void main(String[] args) {
+ getLogger().info("Searching available widgetsets...");
+
+ Map<String, URL> availableWidgetSets = ClassPathExplorer
+ .getAvailableWidgetSets();
+ for (String string : availableWidgetSets.keySet()) {
+
+ getLogger().info(string + " in " + availableWidgetSets.get(string));
+ }
+ }
+
+ private static final Logger getLogger() {
+ return Logger.getLogger(ClassPathExplorer.class.getName());
+ }
+
+}
diff --git a/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/ConnectorStateFactoryGenerator.java b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/ConnectorStateFactoryGenerator.java
new file mode 100644
index 0000000000..33406ef85f
--- /dev/null
+++ b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/ConnectorStateFactoryGenerator.java
@@ -0,0 +1,29 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.terminal.gwt.widgetsetutils;
+
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.vaadin.terminal.gwt.client.ServerConnector;
+
+/**
+ * GWT generator that creates a SharedState class for a given Connector class,
+ * based on the return type of getState()
+ *
+ * @since 7.0
+ */
+public class ConnectorStateFactoryGenerator extends
+ AbstractConnectorClassBasedFactoryGenerator {
+
+ @Override
+ protected JClassType getTargetType(JClassType connectorType) {
+ return getGetterReturnType(connectorType, "getState");
+ }
+
+ @Override
+ protected Class<? extends ServerConnector> getConnectorType() {
+ return ServerConnector.class;
+ }
+
+}
diff --git a/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/ConnectorWidgetFactoryGenerator.java b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/ConnectorWidgetFactoryGenerator.java
new file mode 100644
index 0000000000..55a2857ce0
--- /dev/null
+++ b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/ConnectorWidgetFactoryGenerator.java
@@ -0,0 +1,29 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.terminal.gwt.widgetsetutils;
+
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.vaadin.terminal.gwt.client.ComponentConnector;
+import com.vaadin.terminal.gwt.client.ServerConnector;
+
+/**
+ * GWT generator that creates a Widget class for a given Connector class, based
+ * on the return type of getWidget()
+ *
+ * @since 7.0
+ */
+public class ConnectorWidgetFactoryGenerator extends
+ AbstractConnectorClassBasedFactoryGenerator {
+ @Override
+ protected JClassType getTargetType(JClassType connectorType) {
+ return getGetterReturnType(connectorType, "getWidget");
+ }
+
+ @Override
+ protected Class<? extends ServerConnector> getConnectorType() {
+ return ComponentConnector.class;
+ }
+
+} \ No newline at end of file
diff --git a/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/CustomWidgetMapGenerator.java b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/CustomWidgetMapGenerator.java
new file mode 100644
index 0000000000..89045c63b2
--- /dev/null
+++ b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/CustomWidgetMapGenerator.java
@@ -0,0 +1,84 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.terminal.gwt.widgetsetutils;
+
+import java.util.Collection;
+import java.util.HashSet;
+
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.shared.ui.Connect.LoadStyle;
+import com.vaadin.terminal.gwt.client.ComponentConnector;
+import com.vaadin.terminal.gwt.client.ServerConnector;
+
+/**
+ * An abstract helper class that can be used to easily build a widgetset with
+ * customized load styles for each components. In three abstract methods one can
+ * override the default values given in {@link Connect} annotations.
+ *
+ * @see WidgetMapGenerator
+ *
+ */
+public abstract class CustomWidgetMapGenerator extends WidgetMapGenerator {
+
+ private Collection<Class<? extends ComponentConnector>> eagerPaintables = new HashSet<Class<? extends ComponentConnector>>();
+ private Collection<Class<? extends ComponentConnector>> lazyPaintables = new HashSet<Class<? extends ComponentConnector>>();
+ private Collection<Class<? extends ComponentConnector>> deferredPaintables = new HashSet<Class<? extends ComponentConnector>>();
+
+ @Override
+ protected LoadStyle getLoadStyle(Class<? extends ServerConnector> connector) {
+ if (eagerPaintables == null) {
+ init();
+ }
+ if (eagerPaintables.contains(connector)) {
+ return LoadStyle.EAGER;
+ }
+ if (lazyPaintables.contains(connector)) {
+ return LoadStyle.LAZY;
+ }
+ if (deferredPaintables.contains(connector)) {
+ return LoadStyle.DEFERRED;
+ }
+ return super.getLoadStyle(connector);
+ }
+
+ private void init() {
+ Class<? extends ComponentConnector>[] eagerComponents = getEagerComponents();
+ if (eagerComponents != null) {
+ for (Class<? extends ComponentConnector> class1 : eagerComponents) {
+ eagerPaintables.add(class1);
+ }
+ }
+ Class<? extends ComponentConnector>[] lazyComponents = getEagerComponents();
+ if (lazyComponents != null) {
+ for (Class<? extends ComponentConnector> class1 : lazyComponents) {
+ lazyPaintables.add(class1);
+ }
+ }
+ Class<? extends ComponentConnector>[] deferredComponents = getEagerComponents();
+ if (deferredComponents != null) {
+ for (Class<? extends ComponentConnector> class1 : deferredComponents) {
+ deferredPaintables.add(class1);
+ }
+ }
+ }
+
+ /**
+ * @return an array of components whose load style should be overridden to
+ * {@link LoadStyle#EAGER}
+ */
+ protected abstract Class<? extends ComponentConnector>[] getEagerComponents();
+
+ /**
+ * @return an array of components whose load style should be overridden to
+ * {@link LoadStyle#LAZY}
+ */
+ protected abstract Class<? extends ComponentConnector>[] getLazyComponents();
+
+ /**
+ * @return an array of components whose load style should be overridden to
+ * {@link LoadStyle#DEFERRED}
+ */
+ protected abstract Class<? extends ComponentConnector>[] getDeferredComponents();
+
+}
diff --git a/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/EagerWidgetMapGenerator.java b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/EagerWidgetMapGenerator.java
new file mode 100644
index 0000000000..4ff0592ede
--- /dev/null
+++ b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/EagerWidgetMapGenerator.java
@@ -0,0 +1,29 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.terminal.gwt.widgetsetutils;
+
+import com.vaadin.shared.ui.Connect.LoadStyle;
+import com.vaadin.terminal.gwt.client.ServerConnector;
+
+/**
+ * WidgetMap generator that builds a widgetset that packs all included widgets
+ * into a single JavaScript file loaded at application initialization. Initially
+ * loaded data will be relatively large, but minimal amount of server requests
+ * will be done.
+ * <p>
+ * This is the default generator in version 6.4 and produces similar type of
+ * widgetset as in previous versions of Vaadin. To activate "code splitting",
+ * use the {@link WidgetMapGenerator} instead, that loads most components
+ * deferred.
+ *
+ * @see WidgetMapGenerator
+ *
+ */
+public class EagerWidgetMapGenerator extends WidgetMapGenerator {
+
+ @Override
+ protected LoadStyle getLoadStyle(Class<? extends ServerConnector> connector) {
+ return LoadStyle.EAGER;
+ }
+}
diff --git a/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/GeneratedRpcMethodProviderGenerator.java b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/GeneratedRpcMethodProviderGenerator.java
new file mode 100644
index 0000000000..e11a12a3b5
--- /dev/null
+++ b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/GeneratedRpcMethodProviderGenerator.java
@@ -0,0 +1,211 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.terminal.gwt.widgetsetutils;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import com.google.gwt.core.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.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.TypeOracle;
+import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
+import com.google.gwt.user.rebind.SourceWriter;
+import com.vaadin.shared.communication.ClientRpc;
+import com.vaadin.terminal.gwt.client.communication.GeneratedRpcMethodProvider;
+import com.vaadin.terminal.gwt.client.communication.RpcManager;
+import com.vaadin.terminal.gwt.client.communication.RpcMethod;
+
+/**
+ * GWT generator that creates an implementation for {@link RpcManager} on the
+ * client side classes for executing RPC calls received from the the server.
+ *
+ * @since 7.0
+ */
+public class GeneratedRpcMethodProviderGenerator extends Generator {
+
+ @Override
+ public String generate(TreeLogger logger, GeneratorContext context,
+ String typeName) throws UnableToCompleteException {
+
+ String packageName = null;
+ String className = null;
+ try {
+ TypeOracle typeOracle = context.getTypeOracle();
+
+ // get classType and save instance variables
+ JClassType classType = typeOracle.getType(typeName);
+ packageName = classType.getPackage().getName();
+ className = classType.getSimpleSourceName() + "Impl";
+ // Generate class source code for SerializerMapImpl
+ generateClass(logger, context, packageName, className);
+ } catch (Exception e) {
+ logger.log(TreeLogger.ERROR,
+ "SerializerMapGenerator creation failed", e);
+ }
+ // return the fully qualifed name of the class generated
+ return packageName + "." + className;
+ }
+
+ /**
+ * Generate source code for RpcManagerImpl
+ *
+ * @param logger
+ * Logger object
+ * @param context
+ * Generator context
+ * @param packageName
+ * package name for the class to generate
+ * @param className
+ * class name for the class to generate
+ */
+ private void generateClass(TreeLogger logger, GeneratorContext context,
+ String packageName, String className) {
+ // 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;
+ }
+ logger.log(Type.INFO,
+ "Detecting server to client RPC interface types...");
+ Date date = new Date();
+ TypeOracle typeOracle = context.getTypeOracle();
+ JClassType serverToClientRpcType = typeOracle.findType(ClientRpc.class
+ .getName());
+ JClassType[] rpcInterfaceSubtypes = serverToClientRpcType.getSubtypes();
+
+ // 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.addImport(RpcMethod.class.getName());
+ composer.addImport(ClientRpc.class.getName());
+ composer.addImport(com.vaadin.terminal.gwt.client.communication.Type.class
+ .getName());
+ composer.addImplementedInterface(GeneratedRpcMethodProvider.class
+ .getName());
+ SourceWriter sourceWriter = composer.createSourceWriter(context,
+ printWriter);
+ sourceWriter.indent();
+
+ List<JMethod> rpcMethods = new ArrayList<JMethod>();
+
+ sourceWriter
+ .println("public java.util.Collection<RpcMethod> getGeneratedRpcMethods() {");
+ sourceWriter.indent();
+
+ sourceWriter
+ .println("java.util.ArrayList<RpcMethod> list = new java.util.ArrayList<RpcMethod>();");
+
+ // iterate over RPC interfaces and create helper methods for each
+ // interface
+ for (JClassType type : rpcInterfaceSubtypes) {
+ if (null == type.isInterface()) {
+ // only interested in interfaces here, not implementations
+ continue;
+ }
+
+ // loop over the methods of the interface and its superinterfaces
+ // methods
+ for (JClassType currentType : type.getFlattenedSupertypeHierarchy()) {
+ for (JMethod method : currentType.getMethods()) {
+
+ // RpcMethod(String interfaceName, String methodName,
+ // Type... parameterTypes)
+ sourceWriter.print("list.add(new RpcMethod(\""
+ + type.getQualifiedSourceName() + "\", \""
+ + method.getName() + "\"");
+ JType[] parameterTypes = method.getParameterTypes();
+ for (JType parameter : parameterTypes) {
+ sourceWriter.print(", ");
+ writeTypeCreator(sourceWriter, parameter);
+ }
+ sourceWriter.println(") {");
+ sourceWriter.indent();
+
+ sourceWriter
+ .println("public void applyInvocation(ClientRpc target, Object... parameters) {");
+ sourceWriter.indent();
+
+ sourceWriter.print("((" + type.getQualifiedSourceName()
+ + ")target)." + method.getName() + "(");
+ for (int i = 0; i < parameterTypes.length; i++) {
+ JType parameterType = parameterTypes[i];
+ if (i != 0) {
+ sourceWriter.print(", ");
+ }
+ String parameterTypeName = getBoxedTypeName(parameterType);
+ sourceWriter.print("(" + parameterTypeName
+ + ") parameters[" + i + "]");
+ }
+ sourceWriter.println(");");
+
+ sourceWriter.outdent();
+ sourceWriter.println("}");
+
+ sourceWriter.outdent();
+ sourceWriter.println("});");
+ }
+ }
+ }
+
+ sourceWriter.println("return list;");
+
+ sourceWriter.outdent();
+ sourceWriter.println("}");
+ 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 static void writeTypeCreator(SourceWriter sourceWriter, JType type) {
+ String typeName = getBoxedTypeName(type);
+ sourceWriter.print("new Type(\"" + typeName + "\", ");
+ JParameterizedType parameterized = type.isParameterized();
+ if (parameterized != null) {
+ sourceWriter.print("new Type[] {");
+ JClassType[] typeArgs = parameterized.getTypeArgs();
+ for (JClassType jClassType : typeArgs) {
+ writeTypeCreator(sourceWriter, jClassType);
+ sourceWriter.print(", ");
+ }
+ sourceWriter.print("}");
+ } else {
+ sourceWriter.print("null");
+ }
+ sourceWriter.print(")");
+ }
+
+ public static String getBoxedTypeName(JType type) {
+ if (type.isPrimitive() != null) {
+ // Used boxed types for primitives
+ return type.isPrimitive().getQualifiedBoxedSourceName();
+ } else {
+ return type.getErasedType().getQualifiedSourceName();
+ }
+ }
+
+ private String getInvokeMethodName(JClassType type) {
+ return "invoke" + type.getQualifiedSourceName().replaceAll("\\.", "_");
+ }
+}
diff --git a/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/LazyWidgetMapGenerator.java b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/LazyWidgetMapGenerator.java
new file mode 100644
index 0000000000..28f3dab482
--- /dev/null
+++ b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/LazyWidgetMapGenerator.java
@@ -0,0 +1,23 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.terminal.gwt.widgetsetutils;
+
+import com.vaadin.shared.ui.Connect.LoadStyle;
+import com.vaadin.terminal.gwt.client.ServerConnector;
+
+/**
+ * WidgetMap generator that builds a widgetset that optimizes the transferred
+ * data. Widgets are loaded only when used if the widgetset is built with this
+ * generator.
+ *
+ * @see WidgetMapGenerator
+ *
+ */
+public class LazyWidgetMapGenerator extends WidgetMapGenerator {
+ @Override
+ protected LoadStyle getLoadStyle(Class<? extends ServerConnector> connector) {
+ return LoadStyle.LAZY;
+ }
+
+}
diff --git a/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/RpcProxyCreatorGenerator.java b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/RpcProxyCreatorGenerator.java
new file mode 100644
index 0000000000..8a6c374187
--- /dev/null
+++ b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/RpcProxyCreatorGenerator.java
@@ -0,0 +1,126 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.terminal.gwt.widgetsetutils;
+
+import java.io.PrintWriter;
+import java.util.Date;
+
+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.JClassType;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
+import com.google.gwt.user.rebind.SourceWriter;
+import com.vaadin.shared.communication.ServerRpc;
+import com.vaadin.terminal.gwt.client.ServerConnector;
+import com.vaadin.terminal.gwt.client.communication.InitializableServerRpc;
+import com.vaadin.terminal.gwt.client.communication.RpcProxy.RpcProxyCreator;
+
+public class RpcProxyCreatorGenerator extends Generator {
+
+ @Override
+ public String generate(TreeLogger logger, GeneratorContext ctx,
+ String requestedClassName) throws UnableToCompleteException {
+ logger.log(TreeLogger.DEBUG, "Running RpcProxyCreatorGenerator");
+ TypeOracle typeOracle = ctx.getTypeOracle();
+ assert (typeOracle != null);
+
+ JClassType requestedType = typeOracle.findType(requestedClassName);
+ if (requestedType == null) {
+ logger.log(TreeLogger.ERROR, "Unable to find metadata for type '"
+ + requestedClassName + "'", null);
+ throw new UnableToCompleteException();
+ }
+ String packageName = requestedType.getPackage().getName();
+ String className = requestedType.getSimpleSourceName() + "Impl";
+
+ createType(logger, ctx, packageName, className);
+ return packageName + "." + className;
+ }
+
+ private void createType(TreeLogger logger, GeneratorContext context,
+ String packageName, String className) {
+ ClassSourceFileComposerFactory composer = new ClassSourceFileComposerFactory(
+ packageName, className);
+
+ PrintWriter printWriter = context.tryCreate(logger,
+ composer.getCreatedPackage(),
+ composer.getCreatedClassShortName());
+ if (printWriter == null) {
+ // print writer is null if source code has already been generated
+ return;
+ }
+ Date date = new Date();
+ TypeOracle typeOracle = context.getTypeOracle();
+
+ // init composer, set class properties, create source writer
+ composer.addImport(GWT.class.getCanonicalName());
+ composer.addImport(ServerRpc.class.getCanonicalName());
+ composer.addImport(ServerConnector.class.getCanonicalName());
+ composer.addImport(InitializableServerRpc.class.getCanonicalName());
+ composer.addImport(IllegalArgumentException.class.getCanonicalName());
+ composer.addImplementedInterface(RpcProxyCreator.class
+ .getCanonicalName());
+
+ SourceWriter sourceWriter = composer.createSourceWriter(context,
+ printWriter);
+ sourceWriter.indent();
+
+ sourceWriter
+ .println("public <T extends ServerRpc> T create(Class<T> rpcInterface, ServerConnector connector) {");
+ sourceWriter.indent();
+
+ sourceWriter
+ .println("if (rpcInterface == null || connector == null) {");
+ sourceWriter.indent();
+ sourceWriter
+ .println("throw new IllegalArgumentException(\"RpcInterface and/or connector cannot be null\");");
+ sourceWriter.outdent();
+
+ JClassType initializableInterface = typeOracle.findType(ServerRpc.class
+ .getCanonicalName());
+
+ for (JClassType rpcType : initializableInterface.getSubtypes()) {
+ String rpcClassName = rpcType.getQualifiedSourceName();
+ if (InitializableServerRpc.class.getCanonicalName().equals(
+ rpcClassName)) {
+ // InitializableClientToServerRpc is a special marker interface
+ // that should not get a generated class
+ continue;
+ }
+ sourceWriter.println("} else if (rpcInterface == " + rpcClassName
+ + ".class) {");
+ sourceWriter.indent();
+ sourceWriter.println(rpcClassName + " rpc = GWT.create("
+ + rpcClassName + ".class);");
+ sourceWriter.println("((" + InitializableServerRpc.class.getName()
+ + ") rpc).initRpc(connector);");
+ sourceWriter.println("return (T) rpc;");
+ sourceWriter.outdent();
+ }
+
+ sourceWriter.println("} else {");
+ sourceWriter.indent();
+ sourceWriter
+ .println("throw new IllegalArgumentException(\"No RpcInterface of type \"+ rpcInterface.getName() + \" was found.\");");
+ sourceWriter.outdent();
+ // End of if
+ sourceWriter.println("}");
+ // End of method
+ sourceWriter.println("}");
+
+ // close generated class
+ sourceWriter.outdent();
+ sourceWriter.println("}");
+ // commit generated class
+ context.commit(logger, printWriter);
+ logger.log(Type.INFO, composer.getCreatedClassName() + " created in "
+ + (new Date().getTime() - date.getTime()) / 1000 + "seconds");
+
+ }
+}
diff --git a/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/RpcProxyGenerator.java b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/RpcProxyGenerator.java
new file mode 100644
index 0000000000..7a908e5b4d
--- /dev/null
+++ b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/RpcProxyGenerator.java
@@ -0,0 +1,142 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.terminal.gwt.widgetsetutils;
+
+import java.io.PrintWriter;
+
+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.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JParameter;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
+import com.google.gwt.user.rebind.SourceWriter;
+import com.vaadin.shared.communication.MethodInvocation;
+import com.vaadin.shared.communication.ServerRpc;
+import com.vaadin.terminal.gwt.client.ApplicationConnection;
+import com.vaadin.terminal.gwt.client.ServerConnector;
+import com.vaadin.terminal.gwt.client.communication.InitializableServerRpc;
+
+/**
+ * GWT generator that creates client side proxy classes for making RPC calls
+ * from the client to the server.
+ *
+ * GWT.create() calls for interfaces extending {@link ServerRpc} are affected,
+ * and a proxy implementation is created. Note that the init(...) method of the
+ * proxy must be called before the proxy is used.
+ *
+ * @since 7.0
+ */
+public class RpcProxyGenerator extends Generator {
+ @Override
+ public String generate(TreeLogger logger, GeneratorContext ctx,
+ String requestedClassName) throws UnableToCompleteException {
+ logger.log(TreeLogger.DEBUG, "Running RpcProxyGenerator", null);
+
+ TypeOracle typeOracle = ctx.getTypeOracle();
+ assert (typeOracle != null);
+
+ JClassType requestedType = typeOracle.findType(requestedClassName);
+ if (requestedType == null) {
+ logger.log(TreeLogger.ERROR, "Unable to find metadata for type '"
+ + requestedClassName + "'", null);
+ throw new UnableToCompleteException();
+ }
+
+ String generatedClassName = "ServerRpc_"
+ + requestedType.getName().replaceAll("[$.]", "_");
+
+ JClassType initializableInterface = typeOracle
+ .findType(InitializableServerRpc.class.getCanonicalName());
+
+ ClassSourceFileComposerFactory composer = new ClassSourceFileComposerFactory(
+ requestedType.getPackage().getName(), generatedClassName);
+ composer.addImplementedInterface(requestedType.getQualifiedSourceName());
+ composer.addImplementedInterface(initializableInterface
+ .getQualifiedSourceName());
+ composer.addImport(MethodInvocation.class.getCanonicalName());
+
+ PrintWriter printWriter = ctx.tryCreate(logger,
+ composer.getCreatedPackage(),
+ composer.getCreatedClassShortName());
+ if (printWriter != null) {
+ logger.log(Type.INFO, "Generating client proxy for RPC interface '"
+ + requestedType.getQualifiedSourceName() + "'");
+ SourceWriter writer = composer.createSourceWriter(ctx, printWriter);
+
+ // constructor
+ writer.println("public " + generatedClassName + "() {}");
+
+ // initialization etc.
+ writeCommonFieldsAndMethods(logger, writer, typeOracle);
+
+ // actual proxy methods forwarding calls to the server
+ writeRemoteProxyMethods(logger, writer, typeOracle, requestedType,
+ requestedType.isClassOrInterface().getInheritableMethods());
+
+ // End of class
+ writer.outdent();
+ writer.println("}");
+
+ ctx.commit(logger, printWriter);
+ }
+
+ return composer.getCreatedClassName();
+ }
+
+ private void writeCommonFieldsAndMethods(TreeLogger logger,
+ SourceWriter writer, TypeOracle typeOracle) {
+ JClassType applicationConnectionClass = typeOracle
+ .findType(ApplicationConnection.class.getCanonicalName());
+
+ // fields
+ writer.println("private " + ServerConnector.class.getName()
+ + " connector;");
+
+ // init method from the RPC interface
+ writer.println("public void initRpc(" + ServerConnector.class.getName()
+ + " connector) {");
+ writer.indent();
+ writer.println("this.connector = connector;");
+ writer.outdent();
+ writer.println("}");
+ }
+
+ private static void writeRemoteProxyMethods(TreeLogger logger,
+ SourceWriter writer, TypeOracle typeOracle,
+ JClassType requestedType, JMethod[] methods) {
+ for (JMethod m : methods) {
+ writer.print(m.getReadableDeclaration(false, false, false, false,
+ true));
+ writer.println(" {");
+ writer.indent();
+
+ writer.print("this.connector.getConnection().addMethodInvocationToQueue(new MethodInvocation(this.connector.getConnectorId(), \""
+ + requestedType.getQualifiedBinaryName() + "\", \"");
+ writer.print(m.getName());
+ writer.print("\", new Object[] {");
+ // new Object[] { ... } for parameters - autoboxing etc. by the
+ // compiler
+ JParameter[] parameters = m.getParameters();
+ boolean first = true;
+ for (JParameter p : parameters) {
+ if (!first) {
+ writer.print(", ");
+ }
+ first = false;
+
+ writer.print(p.getName());
+ }
+ writer.println("}), true);");
+
+ writer.outdent();
+ writer.println("}");
+ }
+ }
+}
diff --git a/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerGenerator.java b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerGenerator.java
new file mode 100644
index 0000000000..1951f8ba40
--- /dev/null
+++ b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerGenerator.java
@@ -0,0 +1,458 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+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.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 = "___";
+ private static String serializerPackageName = SerializerMap.class
+ .getPackage().getName();
+
+ @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, serializerPackageName,
+ 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.communication.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] = ("
+ + GeneratedRpcMethodProviderGenerator
+ .getBoxedTypeName(componentType) + ") "
+ + JsonDecoder.class.getName() + ".decodeValue(");
+ GeneratedRpcMethodProviderGenerator.writeTypeCreator(sourceWriter,
+ componentType);
+ sourceWriter.print(", jsonArray.get(i), null, connection)");
+
+ sourceWriter.println(";");
+
+ sourceWriter.outdent();
+ sourceWriter.println("}");
+
+ sourceWriter.println("return value;");
+ }
+
+ private void writeBeanDeserializer(TreeLogger logger,
+ SourceWriter sourceWriter, JClassType beanType) {
+ String beanQualifiedSourceName = beanType.getQualifiedSourceName();
+
+ // 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(");
+ GeneratedRpcMethodProviderGenerator.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 serializerPackageName + "." + getSerializerSimpleClassName(type);
+ }
+}
diff --git a/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerMapGenerator.java b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerMapGenerator.java
new file mode 100644
index 0000000000..3f1ad24066
--- /dev/null
+++ b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/SerializerMapGenerator.java
@@ -0,0 +1,365 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+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.communication.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;
+ }
+}
diff --git a/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/WidgetMapGenerator.java b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/WidgetMapGenerator.java
new file mode 100644
index 0000000000..0d062ec4ff
--- /dev/null
+++ b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/WidgetMapGenerator.java
@@ -0,0 +1,398 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.terminal.gwt.widgetsetutils;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.TreeSet;
+
+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.JClassType;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
+import com.google.gwt.user.rebind.SourceWriter;
+import com.vaadin.shared.Connector;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.shared.ui.Connect.LoadStyle;
+import com.vaadin.terminal.gwt.client.ServerConnector;
+import com.vaadin.terminal.gwt.client.ui.UnknownComponentConnector;
+import com.vaadin.terminal.gwt.client.ui.root.RootConnector;
+import com.vaadin.terminal.gwt.server.ClientConnector;
+
+/**
+ * WidgetMapGenerator's are GWT generator to build WidgetMapImpl dynamically
+ * based on {@link Connect} annotations available in workspace. By modifying the
+ * generator it is possible to do some fine tuning for the generated widgetset
+ * (aka client side engine). The components to be included in the client side
+ * engine can modified be overriding {@link #getUsedConnectors()}.
+ * <p>
+ * The generator also decides how the client side component implementations are
+ * loaded to the browser. The default generator is
+ * {@link EagerWidgetMapGenerator} that builds a monolithic client side engine
+ * that loads all widget implementation on application initialization. This has
+ * been the only option until Vaadin 6.4.
+ * <p>
+ * This generator uses the loadStyle hints from the {@link Connect} annotations.
+ * Depending on the {@link LoadStyle} used, the widget may be included in the
+ * initially loaded JavaScript, loaded when the application has started and
+ * there is no communication to server or lazy loaded when the implementation is
+ * absolutely needed.
+ * <p>
+ * The GWT module description file of the widgetset (
+ * <code>...Widgetset.gwt.xml</code>) can be used to define the
+ * WidgetMapGenarator. An example that defines this generator to be used:
+ *
+ * <pre>
+ * <code>
+ * &lt;generate-with
+ * class="com.vaadin.terminal.gwt.widgetsetutils.MyWidgetMapGenerator"&gt;
+ * &lt;when-type-is class="com.vaadin.terminal.gwt.client.WidgetMap" /&gt;
+ * &lt;/generate-with&gt;
+ *
+ * </code>
+ * </pre>
+ *
+ * <p>
+ * Vaadin package also includes {@link LazyWidgetMapGenerator}, which is a good
+ * option if the transferred data should be minimized, and
+ * {@link CustomWidgetMapGenerator} for easy overriding of loading strategies.
+ *
+ */
+public class WidgetMapGenerator extends Generator {
+
+ private static String serverConnectorClassName = ServerConnector.class
+ .getName();
+
+ private String packageName;
+ private String className;
+
+ @Override
+ public String generate(TreeLogger logger, GeneratorContext context,
+ String typeName) throws UnableToCompleteException {
+
+ try {
+ TypeOracle typeOracle = context.getTypeOracle();
+
+ // get classType and save instance variables
+ JClassType classType = typeOracle.getType(typeName);
+ packageName = classType.getPackage().getName();
+ className = classType.getSimpleSourceName() + "Impl";
+ // Generate class source code
+ generateClass(logger, context);
+ } catch (Exception e) {
+ logger.log(TreeLogger.ERROR, "WidgetMap creation failed", e);
+ }
+ // return the fully qualifed name of the class generated
+ return packageName + "." + className;
+ }
+
+ /**
+ * Generate source code for WidgetMapImpl
+ *
+ * @param logger
+ * Logger object
+ * @param context
+ * Generator context
+ */
+ private void generateClass(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,
+ // return (WidgetMap is equal to all permutations atm)
+ if (printWriter == null) {
+ return;
+ }
+ logger.log(Type.INFO,
+ "Detecting Vaadin connectors in classpath to generate WidgetMapImpl.java ...");
+ Date date = new Date();
+
+ // 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.addImport("java.util.HashMap");
+ composer.addImport("com.google.gwt.core.client.RunAsyncCallback");
+ composer.setSuperclass("com.vaadin.terminal.gwt.client.WidgetMap");
+ SourceWriter sourceWriter = composer.createSourceWriter(context,
+ printWriter);
+
+ Collection<Class<? extends ServerConnector>> connectors = getUsedConnectors(context
+ .getTypeOracle());
+
+ validateConnectors(logger, connectors);
+ logConnectors(logger, context, connectors);
+
+ // generator constructor source code
+ generateImplementationDetector(sourceWriter, connectors);
+ generateInstantiatorMethod(sourceWriter, connectors);
+ // 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)");
+
+ }
+
+ private void validateConnectors(TreeLogger logger,
+ Collection<Class<? extends ServerConnector>> connectors) {
+
+ Iterator<Class<? extends ServerConnector>> iter = connectors.iterator();
+ while (iter.hasNext()) {
+ Class<? extends ServerConnector> connectorClass = iter.next();
+ Connect annotation = connectorClass.getAnnotation(Connect.class);
+ if (!ClientConnector.class.isAssignableFrom(annotation.value())) {
+ logger.log(
+ Type.WARN,
+ "Connector class "
+ + annotation.value().getName()
+ + " defined in @Connect annotation is not a subclass of "
+ + ClientConnector.class.getName()
+ + ". The component connector "
+ + connectorClass.getName()
+ + " will not be included in the widgetset.");
+ iter.remove();
+ }
+ }
+
+ }
+
+ private void logConnectors(TreeLogger logger, GeneratorContext context,
+ Collection<Class<? extends ServerConnector>> connectors) {
+ logger.log(Type.INFO,
+ "Widget set will contain implementations for following component connectors: ");
+
+ TreeSet<String> classNames = new TreeSet<String>();
+ HashMap<String, String> loadStyle = new HashMap<String, String>();
+ for (Class<? extends ServerConnector> connectorClass : connectors) {
+ String className = connectorClass.getCanonicalName();
+ classNames.add(className);
+ if (getLoadStyle(connectorClass) == LoadStyle.DEFERRED) {
+ loadStyle.put(className, "DEFERRED");
+ } else if (getLoadStyle(connectorClass) == LoadStyle.LAZY) {
+ loadStyle.put(className, "LAZY");
+ }
+
+ }
+ for (String className : classNames) {
+ String msg = className;
+ if (loadStyle.containsKey(className)) {
+ msg += " (load style: " + loadStyle.get(className) + ")";
+ }
+ logger.log(Type.INFO, "\t" + msg);
+ }
+ }
+
+ /**
+ * This method is protected to allow creation of optimized widgetsets. The
+ * Widgetset will contain only implementation returned by this function. If
+ * one knows which widgets are needed for the application, returning only
+ * them here will significantly optimize the size of the produced JS.
+ *
+ * @return a collections of Vaadin components that will be added to
+ * widgetset
+ */
+ @SuppressWarnings("unchecked")
+ private Collection<Class<? extends ServerConnector>> getUsedConnectors(
+ TypeOracle typeOracle) {
+ JClassType connectorType = typeOracle.findType(Connector.class
+ .getName());
+ Collection<Class<? extends ServerConnector>> connectors = new HashSet<Class<? extends ServerConnector>>();
+ for (JClassType jClassType : connectorType.getSubtypes()) {
+ Connect annotation = jClassType.getAnnotation(Connect.class);
+ if (annotation != null) {
+ try {
+ Class<? extends ServerConnector> clazz = (Class<? extends ServerConnector>) Class
+ .forName(jClassType.getQualifiedSourceName());
+ connectors.add(clazz);
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ return connectors;
+ }
+
+ /**
+ * Returns true if the widget for given component will be lazy loaded by the
+ * client. The default implementation reads the information from the
+ * {@link Connect} annotation.
+ * <p>
+ * The method can be overridden to optimize the widget loading mechanism. If
+ * the Widgetset is wanted to be optimized for a network with a high latency
+ * or for a one with a very fast throughput, it may be good to return false
+ * for every component.
+ *
+ * @param connector
+ * @return true iff the widget for given component should be lazy loaded by
+ * the client side engine
+ */
+ protected LoadStyle getLoadStyle(Class<? extends ServerConnector> connector) {
+ Connect annotation = connector.getAnnotation(Connect.class);
+ return annotation.loadStyle();
+ }
+
+ private void generateInstantiatorMethod(
+ SourceWriter sourceWriter,
+ Collection<Class<? extends ServerConnector>> connectorsHavingComponentAnnotation) {
+
+ Collection<Class<?>> deferredWidgets = new LinkedList<Class<?>>();
+
+ // TODO detect if it would be noticably faster to instantiate with a
+ // lookup with index than with the hashmap
+
+ sourceWriter.println("public void ensureInstantiator(Class<? extends "
+ + serverConnectorClassName + "> classType) {");
+ sourceWriter.println("if(!instmap.containsKey(classType)){");
+ boolean first = true;
+
+ ArrayList<Class<? extends ServerConnector>> lazyLoadedConnectors = new ArrayList<Class<? extends ServerConnector>>();
+
+ HashSet<Class<? extends ServerConnector>> connectorsWithInstantiator = new HashSet<Class<? extends ServerConnector>>();
+
+ for (Class<? extends ServerConnector> class1 : connectorsHavingComponentAnnotation) {
+ Class<? extends ServerConnector> clientClass = class1;
+ if (connectorsWithInstantiator.contains(clientClass)) {
+ continue;
+ }
+ if (clientClass == RootConnector.class) {
+ // Roots are not instantiated by widgetset
+ continue;
+ }
+ if (!first) {
+ sourceWriter.print(" else ");
+ } else {
+ first = false;
+ }
+ sourceWriter.print("if( classType == " + clientClass.getName()
+ + ".class) {");
+
+ String instantiator = "new WidgetInstantiator() {\n public "
+ + serverConnectorClassName
+ + " get() {\n return GWT.create(" + clientClass.getName()
+ + ".class );\n}\n}\n";
+
+ LoadStyle loadStyle = getLoadStyle(class1);
+
+ if (loadStyle != LoadStyle.EAGER) {
+ sourceWriter
+ .print("ApplicationConfiguration.startWidgetLoading();\n"
+ + "GWT.runAsync( \n"
+ + "new WidgetLoader() { void addInstantiator() {instmap.put("
+ + clientClass.getName()
+ + ".class,"
+ + instantiator + ");}});\n");
+ lazyLoadedConnectors.add(class1);
+
+ if (loadStyle == LoadStyle.DEFERRED) {
+ deferredWidgets.add(class1);
+ }
+
+ } else {
+ // widget implementation in initially loaded js script
+ sourceWriter.print("instmap.put(");
+ sourceWriter.print(clientClass.getName());
+ sourceWriter.print(".class, ");
+ sourceWriter.print(instantiator);
+ sourceWriter.print(");");
+ }
+ sourceWriter.print("}");
+ connectorsWithInstantiator.add(clientClass);
+ }
+
+ sourceWriter.println("}");
+
+ sourceWriter.println("}");
+
+ sourceWriter.println("public Class<? extends "
+ + serverConnectorClassName
+ + ">[] getDeferredLoadedConnectors() {");
+
+ sourceWriter.println("return new Class[] {");
+ first = true;
+ for (Class<?> class2 : deferredWidgets) {
+ if (!first) {
+ sourceWriter.println(",");
+ }
+ first = false;
+ sourceWriter.print(class2.getName() + ".class");
+ }
+
+ sourceWriter.println("};");
+ sourceWriter.println("}");
+
+ // in constructor add a "thread" that lazyly loads lazy loaded widgets
+ // if communication to server idles
+
+ // TODO an array of lazy loaded widgets
+
+ // TODO an index of last ensured widget in array
+
+ sourceWriter.println("public " + serverConnectorClassName
+ + " instantiate(Class<? extends " + serverConnectorClassName
+ + "> classType) {");
+ sourceWriter.indent();
+ sourceWriter.println(serverConnectorClassName
+ + " p = super.instantiate(classType); if(p!= null) return p;");
+ sourceWriter.println("return instmap.get(classType).get();");
+
+ sourceWriter.outdent();
+ sourceWriter.println("}");
+
+ }
+
+ /**
+ *
+ * @param sourceWriter
+ * Source writer to output source code
+ * @param paintablesHavingWidgetAnnotation
+ */
+ private void generateImplementationDetector(
+ SourceWriter sourceWriter,
+ Collection<Class<? extends ServerConnector>> paintablesHavingWidgetAnnotation) {
+ sourceWriter
+ .println("public Class<? extends "
+ + serverConnectorClassName
+ + "> "
+ + "getConnectorClassForServerSideClassName(String fullyQualifiedName) {");
+ sourceWriter.indent();
+ sourceWriter
+ .println("fullyQualifiedName = fullyQualifiedName.intern();");
+
+ for (Class<? extends ServerConnector> connectorClass : paintablesHavingWidgetAnnotation) {
+ Class<? extends ClientConnector> clientConnectorClass = getClientConnectorClass(connectorClass);
+ sourceWriter.print("if ( fullyQualifiedName == \"");
+ sourceWriter.print(clientConnectorClass.getName());
+ sourceWriter.print("\" ) { ensureInstantiator("
+ + connectorClass.getName() + ".class); return ");
+ sourceWriter.print(connectorClass.getName());
+ sourceWriter.println(".class;}");
+ sourceWriter.print("else ");
+ }
+ sourceWriter.println("return "
+ + UnknownComponentConnector.class.getName() + ".class;");
+ sourceWriter.outdent();
+ sourceWriter.println("}");
+
+ }
+
+ private static Class<? extends ClientConnector> getClientConnectorClass(
+ Class<? extends ServerConnector> connectorClass) {
+ Connect annotation = connectorClass.getAnnotation(Connect.class);
+ return (Class<? extends ClientConnector>) annotation.value();
+ }
+}
diff --git a/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/WidgetSetBuilder.java b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/WidgetSetBuilder.java
new file mode 100644
index 0000000000..4c6e334a33
--- /dev/null
+++ b/client-compiler/src/com/vaadin/terminal/gwt/widgetsetutils/WidgetSetBuilder.java
@@ -0,0 +1,201 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.terminal.gwt.widgetsetutils;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintStream;
+import java.io.Reader;
+import java.net.URL;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Helper class to update widgetsets GWT module configuration file. Can be used
+ * command line or via IDE tools.
+ *
+ * <p>
+ * If module definition file contains text "WS Compiler: manually edited", tool
+ * will skip editing file.
+ *
+ */
+public class WidgetSetBuilder {
+
+ public static void main(String[] args) throws IOException {
+ if (args.length == 0) {
+ printUsage();
+ } else {
+ String widgetsetname = args[0];
+ updateWidgetSet(widgetsetname);
+
+ }
+ }
+
+ public static void updateWidgetSet(final String widgetset)
+ throws IOException, FileNotFoundException {
+ boolean changed = false;
+
+ Map<String, URL> availableWidgetSets = ClassPathExplorer
+ .getAvailableWidgetSets();
+
+ URL sourceUrl = availableWidgetSets.get(widgetset);
+ if (sourceUrl == null) {
+ // find first/default source directory
+ sourceUrl = ClassPathExplorer.getDefaultSourceDirectory();
+ }
+
+ String widgetsetfilename = sourceUrl.getFile() + "/"
+ + widgetset.replace(".", "/") + ".gwt.xml";
+
+ File widgetsetFile = new File(widgetsetfilename);
+ if (!widgetsetFile.exists()) {
+ // create empty gwt module file
+ File parent = widgetsetFile.getParentFile();
+ if (parent != null && !parent.exists()) {
+ if (!parent.mkdirs()) {
+ throw new IOException(
+ "Could not create directory for the widgetset: "
+ + parent.getPath());
+ }
+ }
+ widgetsetFile.createNewFile();
+ PrintStream printStream = new PrintStream(new FileOutputStream(
+ widgetsetFile));
+ printStream.print("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ + "<!DOCTYPE module PUBLIC \"-//Google Inc.//DTD "
+ + "Google Web Toolkit 1.7.0//EN\" \"http://google"
+ + "-web-toolkit.googlecode.com/svn/tags/1.7.0/dis"
+ + "tro-source/core/src/gwt-module.dtd\">\n");
+ printStream.print("<module>\n");
+ printStream
+ .print(" <!--\n"
+ + " Uncomment the following to compile the widgetset for one browser only.\n"
+ + " This can reduce the GWT compilation time significantly when debugging.\n"
+ + " The line should be commented out before deployment to production\n"
+ + " environments.\n\n"
+ + " Multiple browsers can be specified for GWT 1.7 as a comma separated\n"
+ + " list. The supported user agents at the moment of writing were:\n"
+ + " ie6,ie8,gecko,gecko1_8,safari,opera\n\n"
+ + " The value gecko1_8 is used for Firefox 3 and later and safari is used for\n"
+ + " webkit based browsers including Google Chrome.\n"
+ + " -->\n"
+ + " <!-- <set-property name=\"user.agent\" value=\"gecko1_8\"/> -->\n");
+ printStream.print("\n</module>\n");
+ printStream.close();
+ changed = true;
+ }
+
+ String content = readFile(widgetsetFile);
+ if (isEditable(content)) {
+ String originalContent = content;
+
+ Collection<String> oldInheritedWidgetsets = getCurrentInheritedWidgetsets(content);
+
+ // add widgetsets that do not exist
+ Iterator<String> i = availableWidgetSets.keySet().iterator();
+ while (i.hasNext()) {
+ String ws = i.next();
+ if (ws.equals(widgetset)) {
+ // do not inherit the module itself
+ continue;
+ }
+ if (!oldInheritedWidgetsets.contains(ws)) {
+ content = addWidgetSet(ws, content);
+ }
+ }
+
+ for (String ws : oldInheritedWidgetsets) {
+ if (!availableWidgetSets.containsKey(ws)) {
+ // widgetset not available in classpath
+ content = removeWidgetSet(ws, content);
+ }
+ }
+
+ changed = changed || !content.equals(originalContent);
+ if (changed) {
+ commitChanges(widgetsetfilename, content);
+ }
+ } else {
+ System.out
+ .println("Widgetset is manually edited. Skipping updates.");
+ }
+ }
+
+ private static boolean isEditable(String content) {
+ return !content.contains("WS Compiler: manually edited");
+ }
+
+ private static String removeWidgetSet(String ws, String content) {
+ return content.replaceFirst("<inherits name=\"" + ws + "\"[^/]*/>", "");
+ }
+
+ private static void commitChanges(String widgetsetfilename, String content)
+ throws IOException {
+ BufferedWriter bufferedWriter = new BufferedWriter(
+ new OutputStreamWriter(new FileOutputStream(widgetsetfilename)));
+ bufferedWriter.write(content);
+ bufferedWriter.close();
+ }
+
+ private static String addWidgetSet(String ws, String content) {
+ return content.replace("</module>", "\n <inherits name=\"" + ws
+ + "\" />" + "\n</module>");
+ }
+
+ private static Collection<String> getCurrentInheritedWidgetsets(
+ String content) {
+ HashSet<String> hashSet = new HashSet<String>();
+ Pattern inheritsPattern = Pattern.compile(" name=\"([^\"]*)\"");
+
+ Matcher matcher = inheritsPattern.matcher(content);
+
+ while (matcher.find()) {
+ String gwtModule = matcher.group(1);
+ if (isWidgetset(gwtModule)) {
+ hashSet.add(gwtModule);
+ }
+ }
+ return hashSet;
+ }
+
+ static boolean isWidgetset(String gwtModuleName) {
+ return gwtModuleName.toLowerCase().contains("widgetset");
+ }
+
+ private static String readFile(File widgetsetFile) throws IOException {
+ Reader fi = new FileReader(widgetsetFile);
+ BufferedReader bufferedReader = new BufferedReader(fi);
+ StringBuilder sb = new StringBuilder();
+ String line;
+ while ((line = bufferedReader.readLine()) != null) {
+ sb.append(line);
+ sb.append("\n");
+ }
+ fi.close();
+ return sb.toString();
+ }
+
+ private static void printUsage() {
+ PrintStream o = System.out;
+ o.println(WidgetSetBuilder.class.getSimpleName() + " usage:");
+ o.println(" 1. Set the same classpath as you will "
+ + "have for the GWT compiler.");
+ o.println(" 2. Give the widgetsetname (to be created or updated)"
+ + " as first parameter");
+ o.println();
+ o.println("All found vaadin widgetsets will be inherited in given widgetset");
+
+ }
+
+}
diff --git a/client-compiler/src/com/vaadin/tools/WidgetsetCompiler.java b/client-compiler/src/com/vaadin/tools/WidgetsetCompiler.java
new file mode 100755
index 0000000000..476136a584
--- /dev/null
+++ b/client-compiler/src/com/vaadin/tools/WidgetsetCompiler.java
@@ -0,0 +1,94 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.tools;
+
+import java.lang.reflect.Method;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.vaadin.terminal.gwt.widgetsetutils.WidgetSetBuilder;
+
+/**
+ * A wrapper for the GWT 1.6 compiler that runs the compiler in a new thread.
+ *
+ * This allows circumventing a J2SE 5.0 bug (6316197) that prevents setting the
+ * stack size for the main thread. Thus, larger widgetsets can be compiled.
+ *
+ * This class takes the same command line arguments as the
+ * com.google.gwt.dev.GWTCompiler class. The old and deprecated compiler is used
+ * for compatibility with GWT 1.5.
+ *
+ * A typical invocation would use e.g. the following arguments
+ *
+ * "-out WebContent/VAADIN/widgetsets com.vaadin.terminal.gwt.DefaultWidgetSet"
+ *
+ * In addition, larger memory usage settings for the VM should be used, e.g.
+ *
+ * "-Xms256M -Xmx512M -Xss8M"
+ *
+ * The source directory containing widgetset and related classes must be
+ * included in the classpath, as well as the gwt-dev-[platform].jar and other
+ * relevant JARs.
+ *
+ * @deprecated with Java 6, can use com.google.gwt.dev.Compiler directly (also
+ * in Eclipse plug-in etc.)
+ */
+@Deprecated
+public class WidgetsetCompiler {
+
+ /**
+ * @param args
+ * same arguments as for com.google.gwt.dev.Compiler
+ */
+ public static void main(final String[] args) {
+ try {
+ // run the compiler in a different thread to enable using the
+ // user-set stack size
+
+ // on Windows, the default stack size is too small for the main
+ // thread and cannot be changed in JRE 1.5 (see
+ // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6316197)
+
+ Runnable runCompiler = new Runnable() {
+ @Override
+ public void run() {
+ try {
+ // GWTCompiler.main(args);
+ // avoid warnings
+
+ String wsname = args[args.length - 1];
+
+ // TODO expecting this is launched via eclipse WTP
+ // project
+ System.out
+ .println("Updating GWT module description file...");
+ WidgetSetBuilder.updateWidgetSet(wsname);
+ System.out.println("Done.");
+
+ System.out.println("Starting GWT compiler");
+ System.setProperty("gwt.nowarn.legacy.tools", "true");
+ Class<?> compilerClass = Class
+ .forName("com.google.gwt.dev.GWTCompiler");
+ Method method = compilerClass.getDeclaredMethod("main",
+ String[].class);
+ method.invoke(null, new Object[] { args });
+ } catch (Throwable thr) {
+ getLogger().log(Level.SEVERE,
+ "Widgetset compilation failed", thr);
+ }
+ }
+ };
+ Thread runThread = new Thread(runCompiler);
+ runThread.start();
+ runThread.join();
+ System.out.println("Widgetset compilation finished");
+ } catch (Throwable thr) {
+ getLogger().log(Level.SEVERE, "Widgetset compilation failed", thr);
+ }
+ }
+
+ private static final Logger getLogger() {
+ return Logger.getLogger(WidgetsetCompiler.class.getName());
+ }
+}