summaryrefslogtreecommitdiffstats
path: root/client-compiler/src/com/vaadin/server
diff options
context:
space:
mode:
authorArtur Signell <artur@vaadin.com>2012-08-29 12:29:14 +0300
committerArtur Signell <artur@vaadin.com>2012-08-29 12:29:20 +0300
commitf5f73df7937d6fc8008ae34cc5a7d39e60de266b (patch)
tree028a4ff386f0fdc8b166274138f376957ef0f0f2 /client-compiler/src/com/vaadin/server
parent63595217a224b9fbc6de4b8bdc2e6b317e0558d5 (diff)
downloadvaadin-framework-f5f73df7937d6fc8008ae34cc5a7d39e60de266b.tar.gz
vaadin-framework-f5f73df7937d6fc8008ae34cc5a7d39e60de266b.zip
Renamed com.vaadin.terminal.gwt.widgetsetutils -> com.vaadin.server.widgetsetutils (#9431)
Diffstat (limited to 'client-compiler/src/com/vaadin/server')
-rw-r--r--client-compiler/src/com/vaadin/server/widgetsetutils/AcceptCriteriaFactoryGenerator.java139
-rw-r--r--client-compiler/src/com/vaadin/server/widgetsetutils/ClassPathExplorer.java474
-rw-r--r--client-compiler/src/com/vaadin/server/widgetsetutils/ConnectorBundleLoaderFactory.java684
-rw-r--r--client-compiler/src/com/vaadin/server/widgetsetutils/CustomWidgetMapGenerator.java96
-rw-r--r--client-compiler/src/com/vaadin/server/widgetsetutils/EagerWidgetMapGenerator.java41
-rw-r--r--client-compiler/src/com/vaadin/server/widgetsetutils/LazyWidgetMapGenerator.java35
-rw-r--r--client-compiler/src/com/vaadin/server/widgetsetutils/WidgetMapGenerator.java434
-rw-r--r--client-compiler/src/com/vaadin/server/widgetsetutils/WidgetSetBuilder.java213
-rw-r--r--client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ArraySerializer.java90
-rw-r--r--client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ClientRpcVisitor.java45
-rw-r--r--client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ConnectorBundle.java611
-rw-r--r--client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ConnectorInitVisitor.java27
-rw-r--r--client-compiler/src/com/vaadin/server/widgetsetutils/metadata/CustomSerializer.java43
-rw-r--r--client-compiler/src/com/vaadin/server/widgetsetutils/metadata/EnumSerializer.java58
-rw-r--r--client-compiler/src/com/vaadin/server/widgetsetutils/metadata/FieldProperty.java86
-rw-r--r--client-compiler/src/com/vaadin/server/widgetsetutils/metadata/GeneratedSerializer.java26
-rw-r--r--client-compiler/src/com/vaadin/server/widgetsetutils/metadata/JsonSerializer.java88
-rw-r--r--client-compiler/src/com/vaadin/server/widgetsetutils/metadata/MethodProperty.java130
-rw-r--r--client-compiler/src/com/vaadin/server/widgetsetutils/metadata/Property.java89
-rw-r--r--client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ServerRpcVisitor.java48
-rw-r--r--client-compiler/src/com/vaadin/server/widgetsetutils/metadata/StateInitVisitor.java25
-rw-r--r--client-compiler/src/com/vaadin/server/widgetsetutils/metadata/TypeVisitor.java58
-rw-r--r--client-compiler/src/com/vaadin/server/widgetsetutils/metadata/WidgetInitVisitor.java73
23 files changed, 3613 insertions, 0 deletions
diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/AcceptCriteriaFactoryGenerator.java b/client-compiler/src/com/vaadin/server/widgetsetutils/AcceptCriteriaFactoryGenerator.java
new file mode 100644
index 0000000000..5b9cd399c7
--- /dev/null
+++ b/client-compiler/src/com/vaadin/server/widgetsetutils/AcceptCriteriaFactoryGenerator.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.server.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.client.ui.dd.VAcceptCriterion;
+import com.vaadin.client.ui.dd.VAcceptCriterionFactory;
+import com.vaadin.shared.ui.dd.AcceptCriterion;
+
+/**
+ * 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.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/server/widgetsetutils/ClassPathExplorer.java b/client-compiler/src/com/vaadin/server/widgetsetutils/ClassPathExplorer.java
new file mode 100644
index 0000000000..70120b944d
--- /dev/null
+++ b/client-compiler/src/com/vaadin/server/widgetsetutils/ClassPathExplorer.java
@@ -0,0 +1,474 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.server.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/server/widgetsetutils/ConnectorBundleLoaderFactory.java b/client-compiler/src/com/vaadin/server/widgetsetutils/ConnectorBundleLoaderFactory.java
new file mode 100644
index 0000000000..aa220225c9
--- /dev/null
+++ b/client-compiler/src/com/vaadin/server/widgetsetutils/ConnectorBundleLoaderFactory.java
@@ -0,0 +1,684 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.server.widgetsetutils;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+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.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.user.rebind.ClassSourceFileComposerFactory;
+import com.google.gwt.user.rebind.SourceWriter;
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.metadata.ConnectorBundleLoader;
+import com.vaadin.client.metadata.InvokationHandler;
+import com.vaadin.client.metadata.ProxyHandler;
+import com.vaadin.client.metadata.TypeData;
+import com.vaadin.client.metadata.TypeDataBundle;
+import com.vaadin.client.metadata.TypeDataStore;
+import com.vaadin.client.ui.UnknownComponentConnector;
+import com.vaadin.server.widgetsetutils.metadata.ClientRpcVisitor;
+import com.vaadin.server.widgetsetutils.metadata.ConnectorBundle;
+import com.vaadin.server.widgetsetutils.metadata.ConnectorInitVisitor;
+import com.vaadin.server.widgetsetutils.metadata.GeneratedSerializer;
+import com.vaadin.server.widgetsetutils.metadata.Property;
+import com.vaadin.server.widgetsetutils.metadata.ServerRpcVisitor;
+import com.vaadin.server.widgetsetutils.metadata.StateInitVisitor;
+import com.vaadin.server.widgetsetutils.metadata.TypeVisitor;
+import com.vaadin.server.widgetsetutils.metadata.WidgetInitVisitor;
+import com.vaadin.shared.annotations.Delayed;
+import com.vaadin.shared.annotations.DelegateToWidget;
+import com.vaadin.shared.communication.ClientRpc;
+import com.vaadin.shared.communication.ServerRpc;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.shared.ui.Connect.LoadStyle;
+
+public class ConnectorBundleLoaderFactory extends Generator {
+
+ @Override
+ public String generate(TreeLogger logger, GeneratorContext context,
+ String typeName) throws UnableToCompleteException {
+ TypeOracle typeOracle = context.getTypeOracle();
+
+ try {
+ JClassType classType = typeOracle.getType(typeName);
+ String packageName = classType.getPackage().getName();
+ String className = classType.getSimpleSourceName() + "Impl";
+
+ generateClass(logger, context, packageName, className, typeName);
+
+ return packageName + "." + className;
+ } catch (UnableToCompleteException e) {
+ // Just rethrow
+ throw e;
+ } catch (Exception e) {
+ logger.log(Type.ERROR, getClass() + " failed", e);
+ throw new UnableToCompleteException();
+ }
+
+ }
+
+ private void generateClass(TreeLogger logger, GeneratorContext context,
+ String packageName, String className, String requestedType)
+ throws Exception {
+ PrintWriter printWriter = context.tryCreate(logger, packageName,
+ className);
+ if (printWriter == null) {
+ return;
+ }
+
+ List<ConnectorBundle> bundles = buildBundles(logger,
+ context.getTypeOracle());
+
+ ClassSourceFileComposerFactory composer = new ClassSourceFileComposerFactory(
+ packageName, className);
+ composer.setSuperclass(requestedType);
+
+ SourceWriter w = composer.createSourceWriter(context, printWriter);
+
+ w.println("public void init() {");
+ w.indent();
+
+ for (ConnectorBundle bundle : bundles) {
+ String name = bundle.getName();
+ boolean isEager = name
+ .equals(ConnectorBundleLoader.EAGER_BUNDLE_NAME);
+
+ w.print("addAsyncBlockLoader(new AsyncBundleLoader(\"");
+ w.print(escape(name));
+ w.print("\", ");
+
+ w.print("new String[] {");
+ for (Entry<JClassType, Set<String>> entry : bundle.getIdentifiers()
+ .entrySet()) {
+ Set<String> identifiers = entry.getValue();
+ for (String id : identifiers) {
+ w.print("\"");
+ w.print(escape(id));
+ w.print("\",");
+ }
+ }
+ w.println("}) {");
+ w.indent();
+
+ w.print("protected void load(final ");
+ w.print(TypeDataStore.class.getName());
+ w.println(" store) {");
+ w.indent();
+
+ if (!isEager) {
+ w.print(GWT.class.getName());
+ w.print(".runAsync(");
+ }
+
+ w.print("new ");
+ w.print(TypeDataBundle.class.getName());
+ w.println("(getName()) {");
+ w.indent();
+
+ w.println("public void load() {");
+ w.indent();
+
+ printBundleData(logger, w, bundle);
+
+ // Close load method
+ w.outdent();
+ w.println("}");
+
+ // Close new TypeDataBundle() {}
+ w.outdent();
+ w.print("}");
+
+ if (isEager) {
+ w.println(".onSuccess();");
+ } else {
+ w.println(");");
+ }
+
+ // Close load method
+ w.outdent();
+ w.println("}");
+
+ // Close add(new ...
+ w.outdent();
+ w.println("});");
+ }
+
+ w.outdent();
+ w.println("}");
+
+ w.commit(logger);
+ }
+
+ private void printBundleData(TreeLogger logger, SourceWriter w,
+ ConnectorBundle bundle) throws UnableToCompleteException {
+ writeIdentifiers(w, bundle);
+ writeGwtConstructors(w, bundle);
+ writeReturnTypes(w, bundle);
+ writeInvokers(w, bundle);
+ writeParamTypes(w, bundle);
+ writeProxys(w, bundle);
+ wirteDelayedInfo(w, bundle);
+ writeProperites(logger, w, bundle);
+ writePropertyTypes(w, bundle);
+ writeSetters(logger, w, bundle);
+ writeGetters(logger, w, bundle);
+ writeSerializers(logger, w, bundle);
+ writeDelegateToWidget(logger, w, bundle);
+ }
+
+ private void writeDelegateToWidget(TreeLogger logger, SourceWriter w,
+ ConnectorBundle bundle) {
+ Set<Property> needsDelegateToWidget = bundle.getNeedsDelegateToWidget();
+ for (Property property : needsDelegateToWidget) {
+ w.println("store.setDelegateToWidget(%s, \"%s\", \"%s\");",
+ getClassLiteralString(property.getBeanType()),
+ property.getName(),
+ property.getAnnotation(DelegateToWidget.class).value());
+ }
+ }
+
+ private void writeSerializers(TreeLogger logger, SourceWriter w,
+ ConnectorBundle bundle) throws UnableToCompleteException {
+ Map<JType, GeneratedSerializer> serializers = bundle.getSerializers();
+ for (Entry<JType, GeneratedSerializer> entry : serializers.entrySet()) {
+ JType type = entry.getKey();
+ GeneratedSerializer serializer = entry.getValue();
+
+ w.print("store.setSerializerFactory(");
+ writeClassLiteral(w, type);
+ w.print(", ");
+ w.println("new Invoker() {");
+ w.indent();
+
+ w.println("public Object invoke(Object target, Object[] params) {");
+ w.indent();
+
+ serializer.writeSerializerInstantiator(logger, w);
+
+ w.outdent();
+ w.println("}");
+
+ w.outdent();
+ w.print("}");
+ w.println(");");
+ }
+ }
+
+ private void writeGetters(TreeLogger logger, SourceWriter w,
+ ConnectorBundle bundle) {
+ Set<Property> properties = bundle.getNeedsSetter();
+ for (Property property : properties) {
+ w.print("store.setGetter(");
+ writeClassLiteral(w, property.getBeanType());
+ w.print(", \"");
+ w.print(escape(property.getName()));
+ w.println("\", new Invoker() {");
+ w.indent();
+
+ w.println("public Object invoke(Object bean, Object[] params) {");
+ w.indent();
+
+ property.writeGetterBody(logger, w, "bean");
+ w.println();
+
+ w.outdent();
+ w.println("}");
+
+ w.outdent();
+ w.println("});");
+ }
+ }
+
+ private void writeSetters(TreeLogger logger, SourceWriter w,
+ ConnectorBundle bundle) {
+ Set<Property> properties = bundle.getNeedsSetter();
+ for (Property property : properties) {
+ w.print("store.setSetter(");
+ writeClassLiteral(w, property.getBeanType());
+ w.print(", \"");
+ w.print(escape(property.getName()));
+ w.println("\", new Invoker() {");
+ w.indent();
+
+ w.println("public Object invoke(Object bean, Object[] params) {");
+ w.indent();
+
+ property.writeSetterBody(logger, w, "bean", "params[0]");
+
+ w.println("return null;");
+
+ w.outdent();
+ w.println("}");
+
+ w.outdent();
+ w.println("});");
+ }
+ }
+
+ private void writePropertyTypes(SourceWriter w, ConnectorBundle bundle) {
+ Set<Property> properties = bundle.getNeedsType();
+ for (Property property : properties) {
+ w.print("store.setPropertyType(");
+ writeClassLiteral(w, property.getBeanType());
+ w.print(", \"");
+ w.print(escape(property.getName()));
+ w.print("\", ");
+ writeTypeCreator(w, property.getPropertyType());
+ w.println(");");
+ }
+ }
+
+ private void writeProperites(TreeLogger logger, SourceWriter w,
+ ConnectorBundle bundle) throws UnableToCompleteException {
+ Set<JClassType> needsPropertyListing = bundle.getNeedsPropertyListing();
+ for (JClassType type : needsPropertyListing) {
+ w.print("store.setProperties(");
+ writeClassLiteral(w, type);
+ w.print(", new String[] {");
+
+ Set<String> usedPropertyNames = new HashSet<String>();
+ Collection<Property> properties = bundle.getProperties(type);
+ for (Property property : properties) {
+ String name = property.getName();
+ if (!usedPropertyNames.add(name)) {
+ logger.log(
+ Type.ERROR,
+ type.getQualifiedSourceName()
+ + " has multiple properties with the name "
+ + name
+ + ". This can happen if there are multiple setters with identical names exect casing.");
+ throw new UnableToCompleteException();
+ }
+
+ w.print("\"");
+ w.print(name);
+ w.print("\", ");
+ }
+
+ w.println("});");
+ }
+ }
+
+ private void wirteDelayedInfo(SourceWriter w, ConnectorBundle bundle) {
+ Map<JClassType, Set<JMethod>> needsDelayedInfo = bundle
+ .getNeedsDelayedInfo();
+ Set<Entry<JClassType, Set<JMethod>>> entrySet = needsDelayedInfo
+ .entrySet();
+ for (Entry<JClassType, Set<JMethod>> entry : entrySet) {
+ JClassType type = entry.getKey();
+ Set<JMethod> methods = entry.getValue();
+ for (JMethod method : methods) {
+ Delayed annotation = method.getAnnotation(Delayed.class);
+ if (annotation != null) {
+ w.print("store.setDelayed(");
+ writeClassLiteral(w, type);
+ w.print(", \"");
+ w.print(escape(method.getName()));
+ w.println("\");");
+
+ if (annotation.lastonly()) {
+ w.print("store.setLastonly(");
+ writeClassLiteral(w, type);
+ w.print(", \"");
+ w.print(escape(method.getName()));
+ w.println("\");");
+ }
+ }
+ }
+ }
+ }
+
+ private void writeProxys(SourceWriter w, ConnectorBundle bundle) {
+ Set<JClassType> needsProxySupport = bundle.getNeedsProxySupport();
+ for (JClassType type : needsProxySupport) {
+ w.print("store.setProxyHandler(");
+ writeClassLiteral(w, type);
+ w.print(", new ");
+ w.print(ProxyHandler.class.getCanonicalName());
+ w.println("() {");
+ w.indent();
+
+ w.println("public Object createProxy(final "
+ + InvokationHandler.class.getName() + " handler) {");
+ w.indent();
+
+ w.print("return new ");
+ w.print(type.getQualifiedSourceName());
+ w.println("() {");
+ w.indent();
+
+ JMethod[] methods = type.getOverridableMethods();
+ for (JMethod method : methods) {
+ if (method.isAbstract()) {
+ w.print("public ");
+ w.print(method.getReturnType().getQualifiedSourceName());
+ w.print(" ");
+ w.print(method.getName());
+ w.print("(");
+
+ JType[] types = method.getParameterTypes();
+ for (int i = 0; i < types.length; i++) {
+ if (i != 0) {
+ w.print(", ");
+ }
+ w.print(types[i].getQualifiedSourceName());
+ w.print(" p");
+ w.print(Integer.toString(i));
+ }
+
+ w.println(") {");
+ w.indent();
+
+ if (!method.getReturnType().getQualifiedSourceName()
+ .equals("void")) {
+ w.print("return ");
+ }
+
+ w.print("handler.invoke(this, ");
+ w.print(TypeData.class.getCanonicalName());
+ w.print(".getType(");
+ writeClassLiteral(w, type);
+ w.print(").getMethod(\"");
+ w.print(escape(method.getName()));
+ w.print("\"), new Object [] {");
+ for (int i = 0; i < types.length; i++) {
+ w.print("p" + i + ", ");
+ }
+ w.println("});");
+
+ w.outdent();
+ w.println("}");
+ }
+ }
+
+ w.outdent();
+ w.println("};");
+
+ w.outdent();
+ w.println("}");
+
+ w.outdent();
+ w.println("});");
+
+ }
+ }
+
+ private void writeParamTypes(SourceWriter w, ConnectorBundle bundle) {
+ Map<JClassType, Set<JMethod>> needsParamTypes = bundle
+ .getNeedsParamTypes();
+ for (Entry<JClassType, Set<JMethod>> entry : needsParamTypes.entrySet()) {
+ JClassType type = entry.getKey();
+
+ Set<JMethod> methods = entry.getValue();
+ for (JMethod method : methods) {
+ w.print("store.setParamTypes(");
+ writeClassLiteral(w, type);
+ w.print(", \"");
+ w.print(escape(method.getName()));
+ w.print("\", new Type[] {");
+
+ for (JType parameter : method.getParameterTypes()) {
+ ConnectorBundleLoaderFactory.writeTypeCreator(w, parameter);
+ w.print(", ");
+ }
+
+ w.println("});");
+
+ }
+ }
+ }
+
+ private void writeInvokers(SourceWriter w, ConnectorBundle bundle) {
+ Map<JClassType, Set<JMethod>> needsInvoker = bundle.getNeedsInvoker();
+ for (Entry<JClassType, Set<JMethod>> entry : needsInvoker.entrySet()) {
+ JClassType type = entry.getKey();
+
+ Set<JMethod> methods = entry.getValue();
+ for (JMethod method : methods) {
+ w.print("store.setInvoker(");
+ writeClassLiteral(w, type);
+ w.print(", \"");
+ w.print(escape(method.getName()));
+ w.println("\", new Invoker() {");
+ w.indent();
+
+ w.println("public Object invoke(Object target, Object[] params) {");
+ w.indent();
+
+ JType returnType = method.getReturnType();
+ boolean hasReturnType = !"void".equals(returnType
+ .getQualifiedSourceName());
+ if (hasReturnType) {
+ w.print("return ");
+ }
+
+ JType[] parameterTypes = method.getParameterTypes();
+
+ w.print("((" + type.getQualifiedSourceName() + ") target)."
+ + method.getName() + "(");
+ for (int i = 0; i < parameterTypes.length; i++) {
+ JType parameterType = parameterTypes[i];
+ if (i != 0) {
+ w.print(", ");
+ }
+ String parameterTypeName = getBoxedTypeName(parameterType);
+ w.print("(" + parameterTypeName + ") params[" + i + "]");
+ }
+ w.println(");");
+
+ if (!hasReturnType) {
+ w.println("return null;");
+ }
+
+ w.outdent();
+ w.println("}");
+
+ w.outdent();
+ w.println("});");
+
+ }
+ }
+ }
+
+ private void writeReturnTypes(SourceWriter w, ConnectorBundle bundle) {
+ Map<JClassType, Set<JMethod>> methodReturnTypes = bundle
+ .getMethodReturnTypes();
+ for (Entry<JClassType, Set<JMethod>> entry : methodReturnTypes
+ .entrySet()) {
+ JClassType type = entry.getKey();
+
+ Set<JMethod> methods = entry.getValue();
+ for (JMethod method : methods) {
+ // setReturnType(Class<?> type, String methodName, Type
+ // returnType)
+ w.print("store.setReturnType(");
+ writeClassLiteral(w, type);
+ w.print(", \"");
+ w.print(escape(method.getName()));
+ w.print("\", ");
+ writeTypeCreator(w, method.getReturnType());
+ w.println(");");
+ }
+ }
+ }
+
+ private void writeGwtConstructors(SourceWriter w, ConnectorBundle bundle) {
+ Set<JClassType> constructors = bundle.getGwtConstructors();
+ for (JClassType type : constructors) {
+ w.print("store.setConstructor(");
+ writeClassLiteral(w, type);
+ w.println(", new Invoker() {");
+ w.indent();
+
+ w.println("public Object invoke(Object target, Object[] params) {");
+ w.indent();
+
+ w.print("return ");
+ w.print(GWT.class.getName());
+ w.print(".create(");
+ writeClassLiteral(w, type);
+ w.println(");");
+
+ w.outdent();
+ w.println("}");
+
+ w.outdent();
+ w.println("});");
+ }
+ }
+
+ public static void writeClassLiteral(SourceWriter w, JType type) {
+ w.print(getClassLiteralString(type));
+ }
+
+ public static String getClassLiteralString(JType type) {
+ return type.getQualifiedSourceName() + ".class";
+ }
+
+ private void writeIdentifiers(SourceWriter w, ConnectorBundle bundle) {
+ Map<JClassType, Set<String>> identifiers = bundle.getIdentifiers();
+ for (Entry<JClassType, Set<String>> entry : identifiers.entrySet()) {
+ Set<String> ids = entry.getValue();
+ JClassType type = entry.getKey();
+ for (String id : ids) {
+ w.print("store.setClass(\"");
+ w.print(escape(id));
+ w.print("\", ");
+ writeClassLiteral(w, type);
+ w.println(");");
+ }
+ }
+ }
+
+ private List<ConnectorBundle> buildBundles(TreeLogger logger,
+ TypeOracle typeOracle) throws NotFoundException,
+ UnableToCompleteException {
+
+ Map<LoadStyle, Collection<JClassType>> connectorsByLoadStyle = new HashMap<LoadStyle, Collection<JClassType>>();
+ for (LoadStyle loadStyle : LoadStyle.values()) {
+ connectorsByLoadStyle.put(loadStyle, new ArrayList<JClassType>());
+ }
+
+ JClassType connectorType = typeOracle.getType(ServerConnector.class
+ .getName());
+ JClassType[] subtypes = connectorType.getSubtypes();
+ for (JClassType connectorSubtype : subtypes) {
+ if (!connectorSubtype.isAnnotationPresent(Connect.class)) {
+ continue;
+ }
+ LoadStyle loadStyle = getLoadStyle(connectorSubtype);
+ if (loadStyle != null) {
+ connectorsByLoadStyle.get(loadStyle).add(connectorSubtype);
+ }
+ }
+
+ List<ConnectorBundle> bundles = new ArrayList<ConnectorBundle>();
+
+ Collection<TypeVisitor> visitors = getVisitors(typeOracle);
+
+ ConnectorBundle eagerBundle = new ConnectorBundle(
+ ConnectorBundleLoader.EAGER_BUNDLE_NAME, visitors, typeOracle);
+ TreeLogger eagerLogger = logger.branch(Type.TRACE,
+ "Populating eager bundle");
+
+ // Eager connectors and all RPC interfaces are loaded by default
+ eagerBundle.processTypes(eagerLogger,
+ connectorsByLoadStyle.get(LoadStyle.EAGER));
+ eagerBundle.processType(eagerLogger, typeOracle
+ .findType(UnknownComponentConnector.class.getCanonicalName()));
+ eagerBundle.processSubTypes(eagerLogger,
+ typeOracle.getType(ClientRpc.class.getName()));
+ eagerBundle.processSubTypes(eagerLogger,
+ typeOracle.getType(ServerRpc.class.getName()));
+
+ bundles.add(eagerBundle);
+
+ ConnectorBundle deferredBundle = new ConnectorBundle(
+ ConnectorBundleLoader.DEFERRED_BUNDLE_NAME, eagerBundle);
+ TreeLogger deferredLogger = logger.branch(Type.TRACE,
+ "Populating deferred bundle");
+ deferredBundle.processTypes(deferredLogger,
+ connectorsByLoadStyle.get(LoadStyle.DEFERRED));
+
+ bundles.add(deferredBundle);
+
+ Collection<JClassType> lazy = connectorsByLoadStyle.get(LoadStyle.LAZY);
+ for (JClassType type : lazy) {
+ ConnectorBundle bundle = new ConnectorBundle(type.getName(),
+ eagerBundle);
+ TreeLogger subLogger = logger.branch(Type.TRACE, "Populating "
+ + type.getName() + " bundle");
+ bundle.processType(subLogger, type);
+
+ bundles.add(bundle);
+ }
+
+ return bundles;
+ }
+
+ private Collection<TypeVisitor> getVisitors(TypeOracle oracle)
+ throws NotFoundException {
+ List<TypeVisitor> visitors = Arrays.<TypeVisitor> asList(
+ new ConnectorInitVisitor(), new StateInitVisitor(),
+ new WidgetInitVisitor(), new ClientRpcVisitor(),
+ new ServerRpcVisitor());
+ for (TypeVisitor typeVisitor : visitors) {
+ typeVisitor.init(oracle);
+ }
+ return visitors;
+ }
+
+ protected LoadStyle getLoadStyle(JClassType connectorType) {
+ Connect annotation = connectorType.getAnnotation(Connect.class);
+ return annotation.loadStyle();
+ }
+
+ public static String getBoxedTypeName(JType type) {
+ if (type.isPrimitive() != null) {
+ // Used boxed types for primitives
+ return type.isPrimitive().getQualifiedBoxedSourceName();
+ } else {
+ return type.getErasedType().getQualifiedSourceName();
+ }
+ }
+
+ public static void writeTypeCreator(SourceWriter sourceWriter, JType type) {
+ String typeName = ConnectorBundleLoaderFactory.getBoxedTypeName(type);
+ JParameterizedType parameterized = type.isParameterized();
+ if (parameterized != null) {
+ sourceWriter.print("new Type(\"" + typeName + "\", ");
+ sourceWriter.print("new Type[] {");
+ JClassType[] typeArgs = parameterized.getTypeArgs();
+ for (JClassType jClassType : typeArgs) {
+ writeTypeCreator(sourceWriter, jClassType);
+ sourceWriter.print(", ");
+ }
+ sourceWriter.print("}");
+ } else {
+ sourceWriter.print("new Type(" + typeName + ".class");
+ }
+ sourceWriter.print(")");
+ }
+
+}
diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/CustomWidgetMapGenerator.java b/client-compiler/src/com/vaadin/server/widgetsetutils/CustomWidgetMapGenerator.java
new file mode 100644
index 0000000000..250bfbb4a1
--- /dev/null
+++ b/client-compiler/src/com/vaadin/server/widgetsetutils/CustomWidgetMapGenerator.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.server.widgetsetutils;
+
+import java.util.Collection;
+import java.util.HashSet;
+
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.ServerConnector;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.shared.ui.Connect.LoadStyle;
+
+/**
+ * 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/server/widgetsetutils/EagerWidgetMapGenerator.java b/client-compiler/src/com/vaadin/server/widgetsetutils/EagerWidgetMapGenerator.java
new file mode 100644
index 0000000000..568dc939e7
--- /dev/null
+++ b/client-compiler/src/com/vaadin/server/widgetsetutils/EagerWidgetMapGenerator.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.server.widgetsetutils;
+
+import com.vaadin.client.ServerConnector;
+import com.vaadin.shared.ui.Connect.LoadStyle;
+
+/**
+ * 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/server/widgetsetutils/LazyWidgetMapGenerator.java b/client-compiler/src/com/vaadin/server/widgetsetutils/LazyWidgetMapGenerator.java
new file mode 100644
index 0000000000..9e46b6d9dd
--- /dev/null
+++ b/client-compiler/src/com/vaadin/server/widgetsetutils/LazyWidgetMapGenerator.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.server.widgetsetutils;
+
+import com.vaadin.client.ServerConnector;
+import com.vaadin.shared.ui.Connect.LoadStyle;
+
+/**
+ * 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/server/widgetsetutils/WidgetMapGenerator.java b/client-compiler/src/com/vaadin/server/widgetsetutils/WidgetMapGenerator.java
new file mode 100644
index 0000000000..b90791d61b
--- /dev/null
+++ b/client-compiler/src/com/vaadin/server/widgetsetutils/WidgetMapGenerator.java
@@ -0,0 +1,434 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.server.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.Map;
+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.client.ServerConnector;
+import com.vaadin.client.ui.UnknownComponentConnector;
+import com.vaadin.client.ui.UI.UIConnector;
+import com.vaadin.server.ClientConnector;
+import com.vaadin.shared.Connector;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.shared.ui.Connect.LoadStyle;
+
+/**
+ * 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.server.widgetsetutils.MyWidgetMapGenerator"&gt;
+ * &lt;when-type-is class="com.vaadin.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
+ * @throws UnableToCompleteException
+ */
+ private void generateClass(TreeLogger logger, GeneratorContext context)
+ throws UnableToCompleteException {
+ // 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.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(logger, 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 == UIConnector.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 logger
+ * logger to print messages to
+ * @param sourceWriter
+ * Source writer to output source code
+ * @param paintablesHavingWidgetAnnotation
+ * @throws UnableToCompleteException
+ */
+ private void generateImplementationDetector(
+ TreeLogger logger,
+ SourceWriter sourceWriter,
+ Collection<Class<? extends ServerConnector>> paintablesHavingWidgetAnnotation)
+ throws UnableToCompleteException {
+ sourceWriter
+ .println("public Class<? extends "
+ + serverConnectorClassName
+ + "> "
+ + "getConnectorClassForServerSideClassName(String fullyQualifiedName) {");
+ sourceWriter.indent();
+ sourceWriter
+ .println("fullyQualifiedName = fullyQualifiedName.intern();");
+
+ // Keep track of encountered mappings to detect conflicts
+ Map<Class<? extends ClientConnector>, Class<? extends ServerConnector>> mappings = new HashMap<Class<? extends ClientConnector>, Class<? extends ServerConnector>>();
+
+ for (Class<? extends ServerConnector> connectorClass : paintablesHavingWidgetAnnotation) {
+ Class<? extends ClientConnector> clientConnectorClass = getClientConnectorClass(connectorClass);
+
+ // Check for conflicts
+ Class<? extends ServerConnector> prevousMapping = mappings.put(
+ clientConnectorClass, connectorClass);
+ if (prevousMapping != null) {
+ logger.log(Type.ERROR,
+ "Both " + connectorClass.getName() + " and "
+ + prevousMapping.getName()
+ + " have @Connect referring to "
+ + clientConnectorClass.getName() + ".");
+ throw new UnableToCompleteException();
+ }
+
+ 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/server/widgetsetutils/WidgetSetBuilder.java b/client-compiler/src/com/vaadin/server/widgetsetutils/WidgetSetBuilder.java
new file mode 100644
index 0000000000..4137662b35
--- /dev/null
+++ b/client-compiler/src/com/vaadin/server/widgetsetutils/WidgetSetBuilder.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.server.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/server/widgetsetutils/metadata/ArraySerializer.java b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ArraySerializer.java
new file mode 100644
index 0000000000..4ab5cccb2e
--- /dev/null
+++ b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ArraySerializer.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.server.widgetsetutils.metadata;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.typeinfo.JArrayType;
+import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.json.client.JSONArray;
+import com.google.gwt.user.rebind.SourceWriter;
+import com.vaadin.client.communication.JsonDecoder;
+import com.vaadin.client.communication.JsonEncoder;
+import com.vaadin.server.widgetsetutils.ConnectorBundleLoaderFactory;
+
+public class ArraySerializer extends JsonSerializer {
+
+ private final JArrayType arrayType;
+
+ public ArraySerializer(JArrayType arrayType) {
+ super(arrayType);
+ this.arrayType = arrayType;
+ }
+
+ @Override
+ protected void printDeserializerBody(TreeLogger logger, SourceWriter w,
+ String type, String jsonValue, String connection) {
+ JType leafType = arrayType.getLeafType();
+ int rank = arrayType.getRank();
+
+ w.println(JSONArray.class.getName() + " jsonArray = " + jsonValue
+ + ".isArray();");
+
+ // Type value = new Type[jsonArray.size()][][];
+ w.print(arrayType.getQualifiedSourceName() + " value = new "
+ + leafType.getQualifiedSourceName() + "[jsonArray.size()]");
+ for (int i = 1; i < rank; i++) {
+ w.print("[]");
+ }
+ w.println(";");
+
+ w.println("for(int i = 0 ; i < value.length; i++) {");
+ w.indent();
+
+ JType componentType = arrayType.getComponentType();
+
+ w.print("value[i] = ("
+ + ConnectorBundleLoaderFactory.getBoxedTypeName(componentType)
+ + ") " + JsonDecoder.class.getName() + ".decodeValue(");
+ ConnectorBundleLoaderFactory.writeTypeCreator(w, componentType);
+ w.print(", jsonArray.get(i), null, " + connection + ")");
+
+ w.println(";");
+
+ w.outdent();
+ w.println("}");
+
+ w.println("return value;");
+ }
+
+ @Override
+ protected void printSerializerBody(TreeLogger logger, SourceWriter w,
+ String value, String applicationConnection) {
+ w.println(JSONArray.class.getName() + " values = new "
+ + JSONArray.class.getName() + "();");
+ // JPrimitiveType primitive = componentType.isPrimitive();
+ w.println("for (int i = 0; i < " + value + ".length; i++) {");
+ w.indent();
+ w.print("values.set(i, ");
+ w.print(JsonEncoder.class.getName() + ".encode(" + value
+ + "[i], false, " + applicationConnection + ")");
+ w.println(");");
+ w.outdent();
+ w.println("}");
+ w.println("return values;");
+ }
+
+}
diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ClientRpcVisitor.java b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ClientRpcVisitor.java
new file mode 100644
index 0000000000..3b78519a0d
--- /dev/null
+++ b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ClientRpcVisitor.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.server.widgetsetutils.metadata;
+
+import java.util.Set;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JType;
+
+public class ClientRpcVisitor extends TypeVisitor {
+ @Override
+ public void visitClientRpc(TreeLogger logger, JClassType type,
+ ConnectorBundle bundle) {
+ Set<? extends JClassType> hierarchy = type
+ .getFlattenedSupertypeHierarchy();
+ for (JClassType subType : hierarchy) {
+ JMethod[] methods = subType.getMethods();
+ for (JMethod method : methods) {
+ bundle.setNeedsInvoker(type, method);
+ bundle.setNeedsParamTypes(type, method);
+
+ JType[] parameterTypes = method.getParameterTypes();
+ for (JType paramType : parameterTypes) {
+ bundle.setNeedsSerialize(paramType);
+ }
+ }
+ }
+ }
+}
diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ConnectorBundle.java b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ConnectorBundle.java
new file mode 100644
index 0000000000..1d37d12a45
--- /dev/null
+++ b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ConnectorBundle.java
@@ -0,0 +1,611 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.server.widgetsetutils.metadata;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.TreeLogger.Type;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JArrayType;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JEnumType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JParameterizedType;
+import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.core.ext.typeinfo.NotFoundException;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.json.client.JSONValue;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.communication.JSONSerializer;
+import com.vaadin.client.ui.UnknownComponentConnector;
+import com.vaadin.shared.communication.ClientRpc;
+import com.vaadin.shared.communication.ServerRpc;
+import com.vaadin.shared.ui.Connect;
+
+public class ConnectorBundle {
+ private static final String FAIL_IF_NOT_SERIALIZABLE = "vFailIfNotSerializable";
+
+ private final String name;
+ private final ConnectorBundle previousBundle;
+ private final Collection<TypeVisitor> visitors;
+ private final Map<JType, JClassType> customSerializers;
+
+ private final Set<JType> hasSerializeSupport = new HashSet<JType>();
+ private final Set<JType> needsSerializeSupport = new HashSet<JType>();
+ private final Map<JType, GeneratedSerializer> serializers = new HashMap<JType, GeneratedSerializer>();
+
+ private final Set<JClassType> needsPropertyList = new HashSet<JClassType>();
+ private final Set<JClassType> needsGwtConstructor = new HashSet<JClassType>();
+ private final Set<JClassType> visitedTypes = new HashSet<JClassType>();
+ private final Set<JClassType> needsProxySupport = new HashSet<JClassType>();
+
+ private final Map<JClassType, Set<String>> identifiers = new HashMap<JClassType, Set<String>>();
+ private final Map<JClassType, Set<JMethod>> needsReturnType = new HashMap<JClassType, Set<JMethod>>();
+ private final Map<JClassType, Set<JMethod>> needsInvoker = new HashMap<JClassType, Set<JMethod>>();
+ private final Map<JClassType, Set<JMethod>> needsParamTypes = new HashMap<JClassType, Set<JMethod>>();
+ private final Map<JClassType, Set<JMethod>> needsDelayedInfo = new HashMap<JClassType, Set<JMethod>>();
+
+ private final Set<Property> needsSetter = new HashSet<Property>();
+ private final Set<Property> needsType = new HashSet<Property>();
+ private final Set<Property> needsGetter = new HashSet<Property>();
+ private final Set<Property> needsDelegateToWidget = new HashSet<Property>();
+
+ private ConnectorBundle(String name, ConnectorBundle previousBundle,
+ Collection<TypeVisitor> visitors,
+ Map<JType, JClassType> customSerializers) {
+ this.name = name;
+ this.previousBundle = previousBundle;
+ this.visitors = visitors;
+ this.customSerializers = customSerializers;
+ }
+
+ public ConnectorBundle(String name, ConnectorBundle previousBundle) {
+ this(name, previousBundle, previousBundle.visitors,
+ previousBundle.customSerializers);
+ }
+
+ public ConnectorBundle(String name, Collection<TypeVisitor> visitors,
+ TypeOracle oracle) throws NotFoundException {
+ this(name, null, visitors, findCustomSerializers(oracle));
+ }
+
+ private static Map<JType, JClassType> findCustomSerializers(
+ TypeOracle oracle) throws NotFoundException {
+ Map<JType, JClassType> serializers = new HashMap<JType, JClassType>();
+
+ JClassType serializerInterface = oracle.findType(JSONSerializer.class
+ .getName());
+ JType[] deserializeParamTypes = new JType[] {
+ oracle.findType(com.vaadin.client.metadata.Type.class
+ .getName()),
+ oracle.findType(JSONValue.class.getName()),
+ oracle.findType(ApplicationConnection.class.getName()) };
+ String deserializeMethodName = "deserialize";
+ // Just test that the method exists
+ serializerInterface.getMethod(deserializeMethodName,
+ deserializeParamTypes);
+
+ for (JClassType serializer : serializerInterface.getSubtypes()) {
+ JMethod deserializeMethod = serializer.findMethod(
+ deserializeMethodName, deserializeParamTypes);
+ if (deserializeMethod == null) {
+ continue;
+ }
+ JType returnType = deserializeMethod.getReturnType();
+
+ serializers.put(returnType, serializer);
+ }
+ return serializers;
+ }
+
+ public void setNeedsGwtConstructor(JClassType type) {
+ if (!needsGwtConstructor(type)) {
+ needsGwtConstructor.add(type);
+ }
+ }
+
+ private boolean needsGwtConstructor(JClassType type) {
+ if (needsGwtConstructor.contains(type)) {
+ return true;
+ } else {
+ return previousBundle != null
+ && previousBundle.needsGwtConstructor(type);
+ }
+ }
+
+ public void setIdentifier(JClassType type, String identifier) {
+ if (!hasIdentifier(type, identifier)) {
+ addMapping(identifiers, type, identifier);
+ }
+ }
+
+ private boolean hasIdentifier(JClassType type, String identifier) {
+ if (hasMapping(identifiers, type, identifier)) {
+ return true;
+ } else {
+ return previousBundle != null
+ && previousBundle.hasIdentifier(type, identifier);
+ }
+ }
+
+ public ConnectorBundle getPreviousBundle() {
+ return previousBundle;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Map<JClassType, Set<String>> getIdentifiers() {
+ return Collections.unmodifiableMap(identifiers);
+ }
+
+ public Set<JClassType> getGwtConstructors() {
+ return Collections.unmodifiableSet(needsGwtConstructor);
+ }
+
+ public void processTypes(TreeLogger logger, Collection<JClassType> types)
+ throws UnableToCompleteException {
+ for (JClassType type : types) {
+ processType(logger, type);
+ }
+ }
+
+ public void processType(TreeLogger logger, JClassType type)
+ throws UnableToCompleteException {
+ if (!isTypeVisited(type)) {
+ for (TypeVisitor typeVisitor : visitors) {
+ invokeVisitor(logger, type, typeVisitor);
+ }
+ visitedTypes.add(type);
+ purgeSerializeSupportQueue(logger);
+ }
+ }
+
+ private boolean isTypeVisited(JClassType type) {
+ if (visitedTypes.contains(type)) {
+ return true;
+ } else {
+ return previousBundle != null && previousBundle.isTypeVisited(type);
+ }
+ }
+
+ private void purgeSerializeSupportQueue(TreeLogger logger)
+ throws UnableToCompleteException {
+ while (!needsSerializeSupport.isEmpty()) {
+ Iterator<JType> iterator = needsSerializeSupport.iterator();
+ JType type = iterator.next();
+ iterator.remove();
+
+ if (hasSserializeSupport(type)) {
+ continue;
+ }
+
+ addSerializeSupport(logger, type);
+ }
+ }
+
+ private void addSerializeSupport(TreeLogger logger, JType type)
+ throws UnableToCompleteException {
+ hasSerializeSupport.add(type);
+
+ JParameterizedType parametrized = type.isParameterized();
+ if (parametrized != null) {
+ for (JClassType parameterType : parametrized.getTypeArgs()) {
+ setNeedsSerialize(parameterType);
+ }
+ }
+
+ if (serializationHandledByFramework(type)) {
+ return;
+ }
+
+ JClassType customSerializer = customSerializers.get(type);
+ JClassType typeAsClass = type.isClass();
+ JEnumType enumType = type.isEnum();
+ JArrayType arrayType = type.isArray();
+
+ if (customSerializer != null) {
+ logger.log(Type.INFO, "Will serialize " + type + " using "
+ + customSerializer.getName());
+ setSerializer(type, new CustomSerializer(customSerializer));
+ } else if (arrayType != null) {
+ logger.log(Type.INFO, "Will serialize " + type + " as an array");
+ setSerializer(type, new ArraySerializer(arrayType));
+ setNeedsSerialize(arrayType.getComponentType());
+ } else if (enumType != null) {
+ logger.log(Type.INFO, "Will serialize " + type + " as an enum");
+ setSerializer(type, new EnumSerializer(enumType));
+ } else if (typeAsClass != null) {
+ // Bean
+ checkSerializable(logger, typeAsClass);
+
+ logger.log(Type.INFO, "Will serialize " + type + " as a bean");
+
+ setNeedsPropertyList(typeAsClass);
+
+ for (Property property : getProperties(typeAsClass)) {
+ setNeedsGwtConstructor(property.getBeanType());
+ setNeedsSetter(property);
+
+ // Getters needed for reading previous value that should be
+ // passed to sub encoder
+ setNeedsGetter(property);
+ setNeedsType(property);
+
+ JType propertyType = property.getPropertyType();
+ setNeedsSerialize(propertyType);
+ }
+ }
+ }
+
+ private void checkSerializable(TreeLogger logger, JClassType type)
+ throws UnableToCompleteException {
+ JClassType javaSerializable = type.getOracle().findType(
+ Serializable.class.getName());
+ boolean serializable = type.isAssignableTo(javaSerializable);
+ if (!serializable) {
+ boolean abortCompile = "true".equals(System
+ .getProperty(FAIL_IF_NOT_SERIALIZABLE));
+ logger.log(
+ abortCompile ? Type.ERROR : Type.WARN,
+ type
+ + " is used in RPC or shared state but does not implement "
+ + Serializable.class.getName()
+ + ". Communication will work but the Application on server side cannot be serialized if it refers to objects of this type. "
+ + "If the system property "
+ + FAIL_IF_NOT_SERIALIZABLE
+ + " is set to \"true\", this causes the compilation to fail instead of just emitting a warning.");
+ if (abortCompile) {
+ throw new UnableToCompleteException();
+ }
+ }
+ }
+
+ private void setSerializer(JType type, GeneratedSerializer serializer) {
+ if (!hasSerializer(type)) {
+ serializers.put(type, serializer);
+ }
+ }
+
+ private boolean hasSerializer(JType type) {
+ if (serializers.containsKey(type)) {
+ return true;
+ } else {
+ return previousBundle != null && previousBundle.hasSerializer(type);
+ }
+ }
+
+ public Map<JType, GeneratedSerializer> getSerializers() {
+ return Collections.unmodifiableMap(serializers);
+ }
+
+ private void setNeedsGetter(Property property) {
+ if (!isNeedsGetter(property)) {
+ needsGetter.add(property);
+ }
+ }
+
+ private boolean isNeedsGetter(Property property) {
+ if (needsGetter.contains(property)) {
+ return true;
+ } else {
+ return previousBundle != null
+ && previousBundle.isNeedsGetter(property);
+ }
+ }
+
+ public Set<Property> getNeedsGetter() {
+ return Collections.unmodifiableSet(needsGetter);
+ }
+
+ private void setNeedsType(Property property) {
+ if (!isNeedsType(property)) {
+ needsType.add(property);
+ }
+ }
+
+ public Set<Property> getNeedsType() {
+ return Collections.unmodifiableSet(needsType);
+ }
+
+ private boolean isNeedsType(Property property) {
+ if (needsType.contains(property)) {
+ return true;
+ } else {
+ return previousBundle != null
+ && previousBundle.isNeedsType(property);
+ }
+ }
+
+ public void setNeedsSetter(Property property) {
+ if (!isNeedsSetter(property)) {
+ needsSetter.add(property);
+ }
+ }
+
+ private boolean isNeedsSetter(Property property) {
+ if (needsSetter.contains(property)) {
+ return true;
+ } else {
+ return previousBundle != null
+ && previousBundle.isNeedsSetter(property);
+ }
+ }
+
+ public Set<Property> getNeedsSetter() {
+ return Collections.unmodifiableSet(needsSetter);
+ }
+
+ private void setNeedsPropertyList(JClassType type) {
+ if (!isNeedsPropertyList(type)) {
+ needsPropertyList.add(type);
+ }
+ }
+
+ private boolean isNeedsPropertyList(JClassType type) {
+ if (needsPropertyList.contains(type)) {
+ return true;
+ } else {
+ return previousBundle != null
+ && previousBundle.isNeedsPropertyList(type);
+ }
+ }
+
+ public Set<JClassType> getNeedsPropertyListing() {
+ return Collections.unmodifiableSet(needsPropertyList);
+ }
+
+ public Collection<Property> getProperties(JClassType type) {
+ HashSet<Property> properties = new HashSet<Property>();
+
+ properties.addAll(MethodProperty.findProperties(type));
+ properties.addAll(FieldProperty.findProperties(type));
+
+ return properties;
+ }
+
+ private void invokeVisitor(TreeLogger logger, JClassType type,
+ TypeVisitor typeVisitor) throws UnableToCompleteException {
+ TreeLogger subLogger = logger.branch(Type.TRACE,
+ "Visiting " + type.getName() + " with "
+ + typeVisitor.getClass().getSimpleName());
+ if (isConnectedConnector(type)) {
+ typeVisitor.visitConnector(subLogger, type, this);
+ }
+ if (isClientRpc(type)) {
+ typeVisitor.visitClientRpc(subLogger, type, this);
+ }
+ if (isServerRpc(type)) {
+ typeVisitor.visitServerRpc(subLogger, type, this);
+ }
+ }
+
+ public void processSubTypes(TreeLogger logger, JClassType type)
+ throws UnableToCompleteException {
+ processTypes(logger, Arrays.asList(type.getSubtypes()));
+ }
+
+ public void setNeedsReturnType(JClassType type, JMethod method) {
+ if (!isNeedsReturnType(type, method)) {
+ addMapping(needsReturnType, type, method);
+ }
+ }
+
+ private boolean isNeedsReturnType(JClassType type, JMethod method) {
+ if (hasMapping(needsReturnType, type, method)) {
+ return true;
+ } else {
+ return previousBundle != null
+ && previousBundle.isNeedsReturnType(type, method);
+ }
+ }
+
+ public Map<JClassType, Set<JMethod>> getMethodReturnTypes() {
+ return Collections.unmodifiableMap(needsReturnType);
+ }
+
+ private static boolean isClientRpc(JClassType type) {
+ return isType(type, ClientRpc.class);
+ }
+
+ private static boolean isServerRpc(JClassType type) {
+ return isType(type, ServerRpc.class);
+ }
+
+ public static boolean isConnectedConnector(JClassType type) {
+ return isConnected(type) && isType(type, ServerConnector.class);
+ }
+
+ private static boolean isConnected(JClassType type) {
+ return type.isAnnotationPresent(Connect.class)
+ || type.getQualifiedSourceName().equals(
+ UnknownComponentConnector.class.getCanonicalName());
+ }
+
+ public static boolean isConnectedComponentConnector(JClassType type) {
+ return isConnected(type) && isType(type, ComponentConnector.class);
+ }
+
+ private static boolean isType(JClassType type, Class<?> class1) {
+ try {
+ return type.getOracle().getType(class1.getName())
+ .isAssignableFrom(type);
+ } catch (NotFoundException e) {
+ throw new RuntimeException("Could not find " + class1.getName(), e);
+ }
+ }
+
+ public void setNeedsInvoker(JClassType type, JMethod method) {
+ if (!isNeedsInvoker(type, method)) {
+ addMapping(needsInvoker, type, method);
+ }
+ }
+
+ private <K, V> void addMapping(Map<K, Set<V>> map, K key, V value) {
+ Set<V> set = map.get(key);
+ if (set == null) {
+ set = new HashSet<V>();
+ map.put(key, set);
+ }
+ set.add(value);
+ }
+
+ private <K, V> boolean hasMapping(Map<K, Set<V>> map, K key, V value) {
+ return map.containsKey(key) && map.get(key).contains(value);
+ }
+
+ private boolean isNeedsInvoker(JClassType type, JMethod method) {
+ if (hasMapping(needsInvoker, type, method)) {
+ return true;
+ } else {
+ return previousBundle != null
+ && previousBundle.isNeedsInvoker(type, method);
+ }
+ }
+
+ public Map<JClassType, Set<JMethod>> getNeedsInvoker() {
+ return Collections.unmodifiableMap(needsInvoker);
+ }
+
+ public void setNeedsParamTypes(JClassType type, JMethod method) {
+ if (!isNeedsParamTypes(type, method)) {
+ addMapping(needsParamTypes, type, method);
+ }
+ }
+
+ private boolean isNeedsParamTypes(JClassType type, JMethod method) {
+ if (hasMapping(needsParamTypes, type, method)) {
+ return true;
+ } else {
+ return previousBundle != null
+ && previousBundle.isNeedsParamTypes(type, method);
+ }
+ }
+
+ public Map<JClassType, Set<JMethod>> getNeedsParamTypes() {
+ return Collections.unmodifiableMap(needsParamTypes);
+ }
+
+ public void setNeedsProxySupport(JClassType type) {
+ if (!isNeedsProxySupport(type)) {
+ needsProxySupport.add(type);
+ }
+ }
+
+ private boolean isNeedsProxySupport(JClassType type) {
+ if (needsProxySupport.contains(type)) {
+ return true;
+ } else {
+ return previousBundle != null
+ && previousBundle.isNeedsProxySupport(type);
+ }
+ }
+
+ public Set<JClassType> getNeedsProxySupport() {
+ return Collections.unmodifiableSet(needsProxySupport);
+ }
+
+ public void setNeedsDelayedInfo(JClassType type, JMethod method) {
+ if (!isNeedsDelayedInfo(type, method)) {
+ addMapping(needsDelayedInfo, type, method);
+ }
+ }
+
+ private boolean isNeedsDelayedInfo(JClassType type, JMethod method) {
+ if (hasMapping(needsDelayedInfo, type, method)) {
+ return true;
+ } else {
+ return previousBundle != null
+ && previousBundle.isNeedsDelayedInfo(type, method);
+ }
+ }
+
+ public Map<JClassType, Set<JMethod>> getNeedsDelayedInfo() {
+ return Collections.unmodifiableMap(needsDelayedInfo);
+ }
+
+ public void setNeedsSerialize(JType type) {
+ if (!hasSserializeSupport(type)) {
+ needsSerializeSupport.add(type);
+ }
+ }
+
+ private static Set<Class<?>> frameworkHandledTypes = new HashSet<Class<?>>();
+ {
+ frameworkHandledTypes.add(String.class);
+ frameworkHandledTypes.add(Boolean.class);
+ frameworkHandledTypes.add(Integer.class);
+ frameworkHandledTypes.add(Float.class);
+ frameworkHandledTypes.add(Double.class);
+ frameworkHandledTypes.add(Long.class);
+ frameworkHandledTypes.add(Enum.class);
+ frameworkHandledTypes.add(String[].class);
+ frameworkHandledTypes.add(Object[].class);
+ frameworkHandledTypes.add(Map.class);
+ frameworkHandledTypes.add(List.class);
+ frameworkHandledTypes.add(Set.class);
+ frameworkHandledTypes.add(Byte.class);
+ frameworkHandledTypes.add(Character.class);
+
+ }
+
+ private boolean serializationHandledByFramework(JType setterType) {
+ // Some types are handled by the framework at the moment. See #8449
+ // This method should be removed at some point.
+ if (setterType.isPrimitive() != null) {
+ return true;
+ }
+
+ String qualifiedName = setterType.getQualifiedSourceName();
+ for (Class<?> cls : frameworkHandledTypes) {
+ if (qualifiedName.equals(cls.getName())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private boolean hasSserializeSupport(JType type) {
+ if (hasSerializeSupport.contains(type)) {
+ return true;
+ } else {
+ return previousBundle != null
+ && previousBundle.hasSserializeSupport(type);
+ }
+ }
+
+ public void setNeedsDelegateToWidget(Property property) {
+ if (!isNeedsDelegateToWidget(property)) {
+ needsDelegateToWidget.add(property);
+ }
+ }
+
+ private boolean isNeedsDelegateToWidget(Property property) {
+ if (needsDelegateToWidget.contains(property)) {
+ return true;
+ } else {
+ return previousBundle != null
+ && previousBundle.isNeedsDelegateToWidget(property);
+ }
+ }
+
+ public Set<Property> getNeedsDelegateToWidget() {
+ return Collections.unmodifiableSet(needsDelegateToWidget);
+ }
+} \ No newline at end of file
diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ConnectorInitVisitor.java b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ConnectorInitVisitor.java
new file mode 100644
index 0000000000..3e863516a5
--- /dev/null
+++ b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ConnectorInitVisitor.java
@@ -0,0 +1,27 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.server.widgetsetutils.metadata;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.TreeLogger.Type;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.vaadin.shared.ui.Connect;
+
+public class ConnectorInitVisitor extends TypeVisitor {
+
+ @Override
+ public void visitConnector(TreeLogger logger, JClassType type,
+ ConnectorBundle bundle) {
+ Connect connectAnnotation = type.getAnnotation(Connect.class);
+ if (connectAnnotation != null) {
+ logger.log(Type.INFO, type.getName() + " will be in the "
+ + bundle.getName().replaceAll("^_*", "") + " bundle");
+ bundle.setIdentifier(type, connectAnnotation.value()
+ .getCanonicalName());
+ bundle.setNeedsGwtConstructor(type);
+ }
+ }
+
+}
diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/CustomSerializer.java b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/CustomSerializer.java
new file mode 100644
index 0000000000..a0105b4bf3
--- /dev/null
+++ b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/CustomSerializer.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.server.widgetsetutils.metadata;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.user.rebind.SourceWriter;
+import com.vaadin.server.widgetsetutils.ConnectorBundleLoaderFactory;
+
+public class CustomSerializer implements GeneratedSerializer {
+
+ private final JClassType serializerType;
+
+ public CustomSerializer(JClassType serializerType) {
+ this.serializerType = serializerType;
+ }
+
+ @Override
+ public void writeSerializerInstantiator(TreeLogger logger, SourceWriter w)
+ throws UnableToCompleteException {
+ w.print("return ");
+ w.print(GWT.class.getCanonicalName());
+ w.print(".create(");
+ ConnectorBundleLoaderFactory.writeClassLiteral(w, serializerType);
+ w.println(");");
+ }
+}
diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/EnumSerializer.java b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/EnumSerializer.java
new file mode 100644
index 0000000000..5590e6cd7a
--- /dev/null
+++ b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/EnumSerializer.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.server.widgetsetutils.metadata;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.typeinfo.JEnumConstant;
+import com.google.gwt.core.ext.typeinfo.JEnumType;
+import com.google.gwt.json.client.JSONString;
+import com.google.gwt.user.rebind.SourceWriter;
+
+public class EnumSerializer extends JsonSerializer {
+
+ private final JEnumType enumType;
+
+ public EnumSerializer(JEnumType type) {
+ super(type);
+ enumType = type;
+ }
+
+ @Override
+ protected void printDeserializerBody(TreeLogger logger, SourceWriter w,
+ String type, String jsonValue, String connection) {
+ w.println("String enumIdentifier = ((" + JSONString.class.getName()
+ + ")" + jsonValue + ").stringValue();");
+ for (JEnumConstant e : enumType.getEnumConstants()) {
+ w.println("if (\"" + e.getName() + "\".equals(enumIdentifier)) {");
+ w.indent();
+ w.println("return " + enumType.getQualifiedSourceName() + "."
+ + e.getName() + ";");
+ w.outdent();
+ w.println("}");
+ }
+ w.println("return null;");
+ }
+
+ @Override
+ protected void printSerializerBody(TreeLogger logger, SourceWriter w,
+ String value, String applicationConnection) {
+ // return new JSONString(castedValue.name());
+ w.println("return new " + JSONString.class.getName() + "(" + value
+ + ".name());");
+ }
+
+}
diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/FieldProperty.java b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/FieldProperty.java
new file mode 100644
index 0000000000..a7896a5bf6
--- /dev/null
+++ b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/FieldProperty.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.server.widgetsetutils.metadata;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JField;
+import com.google.gwt.user.rebind.SourceWriter;
+
+public class FieldProperty extends Property {
+
+ private final JField field;
+
+ private FieldProperty(JClassType beanType, JField field) {
+ super(field.getName(), beanType, field.getType());
+ this.field = field;
+ }
+
+ @Override
+ public void writeSetterBody(TreeLogger logger, SourceWriter w,
+ String beanVariable, String valueVariable) {
+ w.print("((%s) %s).%s = (%s)%s;", getBeanType()
+ .getQualifiedSourceName(), beanVariable, getName(),
+ getUnboxedPropertyTypeName(), valueVariable);
+ }
+
+ @Override
+ public void writeGetterBody(TreeLogger logger, SourceWriter w,
+ String beanVariable) {
+ w.print("return ((%s) %s).%s;", getBeanType().getQualifiedSourceName(),
+ beanVariable, getName());
+ }
+
+ public static Collection<FieldProperty> findProperties(JClassType type) {
+ Collection<FieldProperty> properties = new ArrayList<FieldProperty>();
+
+ List<JField> fields = getPublicFields(type);
+ for (JField field : fields) {
+ properties.add(new FieldProperty(type, field));
+ }
+
+ return properties;
+ }
+
+ private static List<JField> getPublicFields(JClassType type) {
+ Set<String> names = new HashSet<String>();
+ ArrayList<JField> fields = new ArrayList<JField>();
+ for (JClassType subType : type.getFlattenedSupertypeHierarchy()) {
+ JField[] subFields = subType.getFields();
+ for (JField field : subFields) {
+ if (field.isPublic() && !field.isStatic()
+ && names.add(field.getName())) {
+ fields.add(field);
+ }
+ }
+ }
+ return fields;
+ }
+
+ @Override
+ public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
+ return field.getAnnotation(annotationClass);
+ }
+
+}
diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/GeneratedSerializer.java b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/GeneratedSerializer.java
new file mode 100644
index 0000000000..8f6c8696fd
--- /dev/null
+++ b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/GeneratedSerializer.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.server.widgetsetutils.metadata;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.user.rebind.SourceWriter;
+
+public interface GeneratedSerializer {
+ public void writeSerializerInstantiator(TreeLogger logger, SourceWriter w)
+ throws UnableToCompleteException;
+}
diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/JsonSerializer.java b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/JsonSerializer.java
new file mode 100644
index 0000000000..6e521dc763
--- /dev/null
+++ b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/JsonSerializer.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.server.widgetsetutils.metadata;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.json.client.JSONValue;
+import com.google.gwt.user.rebind.SourceWriter;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.communication.JSONSerializer;
+
+public abstract class JsonSerializer implements GeneratedSerializer {
+
+ private final JType type;
+
+ public JsonSerializer(JType type) {
+ this.type = type;
+ }
+
+ @Override
+ public void writeSerializerInstantiator(TreeLogger logger, SourceWriter w)
+ throws UnableToCompleteException {
+
+ w.print("return new ");
+ w.print(JSONSerializer.class.getCanonicalName());
+ w.print("<");
+ w.print(type.getQualifiedSourceName());
+ w.println(">() {");
+ w.indent();
+
+ writeSerializerBody(logger, w);
+
+ w.outdent();
+ w.println("};");
+ }
+
+ protected void writeSerializerBody(TreeLogger logger, SourceWriter w) {
+ String qualifiedSourceName = type.getQualifiedSourceName();
+ w.println("public " + JSONValue.class.getName() + " serialize("
+ + qualifiedSourceName + " value, "
+ + ApplicationConnection.class.getName() + " connection) {");
+ w.indent();
+ // MouseEventDetails castedValue = (MouseEventDetails) value;
+ w.println(qualifiedSourceName + " castedValue = ("
+ + qualifiedSourceName + ") value;");
+
+ printSerializerBody(logger, w, "castedValue", "connection");
+
+ // End of serializer method
+ w.outdent();
+ w.println("}");
+
+ // Deserializer
+ // T deserialize(Type type, JSONValue jsonValue, ApplicationConnection
+ // connection);
+ w.println("public " + qualifiedSourceName + " deserialize(Type type, "
+ + JSONValue.class.getName() + " jsonValue, "
+ + ApplicationConnection.class.getName() + " connection) {");
+ w.indent();
+
+ printDeserializerBody(logger, w, "type", "jsonValue", "connection");
+
+ w.outdent();
+ w.println("}");
+ }
+
+ protected abstract void printDeserializerBody(TreeLogger logger,
+ SourceWriter w, String type, String jsonValue, String connection);
+
+ protected abstract void printSerializerBody(TreeLogger logger,
+ SourceWriter w, String value, String applicationConnection);
+
+}
diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/MethodProperty.java b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/MethodProperty.java
new file mode 100644
index 0000000000..318519f37d
--- /dev/null
+++ b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/MethodProperty.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.server.widgetsetutils.metadata;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.user.rebind.SourceWriter;
+
+public class MethodProperty extends Property {
+
+ private final JMethod setter;
+
+ private MethodProperty(JClassType beanType, JMethod setter) {
+ super(getTransportFieldName(setter), beanType, setter
+ .getParameterTypes()[0]);
+ this.setter = setter;
+ }
+
+ public static Collection<MethodProperty> findProperties(JClassType type) {
+ Collection<MethodProperty> properties = new ArrayList<MethodProperty>();
+
+ List<JMethod> setters = getSetters(type);
+ for (JMethod setter : setters) {
+ properties.add(new MethodProperty(type, setter));
+ }
+
+ return properties;
+ }
+
+ /**
+ * Returns a list of all setters found in the beanType or its parent class
+ *
+ * @param beanType
+ * The type to check
+ * @return A list of setter methods from the class and its parents
+ */
+ private static List<JMethod> getSetters(JClassType beanType) {
+ List<JMethod> setterMethods = new ArrayList<JMethod>();
+
+ while (beanType != null
+ && !beanType.getQualifiedSourceName().equals(
+ Object.class.getName())) {
+ for (JMethod method : beanType.getMethods()) {
+ // Process all setters that have corresponding fields
+ if (!method.isPublic() || method.isStatic()
+ || !method.getName().startsWith("set")
+ || method.getParameterTypes().length != 1) {
+ // Not setter, skip to next method
+ continue;
+ }
+ setterMethods.add(method);
+ }
+ beanType = beanType.getSuperclass();
+ }
+
+ return setterMethods;
+ }
+
+ @Override
+ public void writeSetterBody(TreeLogger logger, SourceWriter w,
+ String beanVariable, String valueVariable) {
+ w.print("((");
+ w.print(getBeanType().getQualifiedSourceName());
+ w.print(") ");
+ w.print(beanVariable);
+ w.print(").");
+ w.print(setter.getName());
+ w.print("((");
+ w.print(getUnboxedPropertyTypeName());
+ w.print(") ");
+ w.print(valueVariable);
+ w.println(");");
+ }
+
+ @Override
+ public void writeGetterBody(TreeLogger logger, SourceWriter w,
+ String beanVariable) {
+ w.print("return ((");
+ w.print(getBeanType().getQualifiedSourceName());
+ w.print(") ");
+ w.print(beanVariable);
+ w.print(").");
+ w.print(findGetter(getBeanType(), setter));
+ w.print("();");
+ }
+
+ private String findGetter(JClassType beanType, JMethod setterMethod) {
+ JType setterParameterType = setterMethod.getParameterTypes()[0];
+ String fieldName = setterMethod.getName().substring(3);
+ if (setterParameterType.getQualifiedSourceName().equals(
+ boolean.class.getName())) {
+ return "is" + fieldName;
+ } else {
+ return "get" + fieldName;
+ }
+ }
+
+ private static String getTransportFieldName(JMethod setter) {
+ String baseName = setter.getName().substring(3);
+ return Character.toLowerCase(baseName.charAt(0))
+ + baseName.substring(1);
+ }
+
+ @Override
+ public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
+ return setter.getAnnotation(annotationClass);
+ }
+
+}
diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/Property.java b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/Property.java
new file mode 100644
index 0000000000..b55b70784a
--- /dev/null
+++ b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/Property.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.server.widgetsetutils.metadata;
+
+import java.lang.annotation.Annotation;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
+import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.user.rebind.SourceWriter;
+
+public abstract class Property {
+ private final String name;
+ private final JClassType beanType;
+ private final JType propertyType;
+
+ protected Property(String name, JClassType beanType, JType propertyType) {
+ this.name = name;
+ this.beanType = beanType;
+ this.propertyType = propertyType;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public JType getPropertyType() {
+ return propertyType;
+ }
+
+ public String getUnboxedPropertyTypeName() {
+ JType propertyType = getPropertyType();
+ JPrimitiveType primitive = propertyType.isPrimitive();
+ if (primitive != null) {
+ return primitive.getQualifiedBoxedSourceName();
+ } else {
+ return propertyType.getQualifiedSourceName();
+ }
+ }
+
+ public JClassType getBeanType() {
+ return beanType;
+ }
+
+ public abstract void writeSetterBody(TreeLogger logger, SourceWriter w,
+ String beanVariable, String valueVariable);
+
+ public abstract void writeGetterBody(TreeLogger logger, SourceWriter w,
+ String beanVariable);
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ } else if (obj instanceof Property) {
+ Property other = (Property) obj;
+ return other.getClass() == getClass()
+ && other.getBeanType().equals(getBeanType())
+ && other.getName().equals(getName());
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return getClass().hashCode() * 31 ^ 2 + getBeanType().hashCode() * 31
+ + getName().hashCode();
+ }
+
+ public abstract <T extends Annotation> T getAnnotation(
+ Class<T> annotationClass);
+
+}
diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ServerRpcVisitor.java b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ServerRpcVisitor.java
new file mode 100644
index 0000000000..cbcfc3075b
--- /dev/null
+++ b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ServerRpcVisitor.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.server.widgetsetutils.metadata;
+
+import java.util.Set;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JType;
+
+public class ServerRpcVisitor extends TypeVisitor {
+ @Override
+ public void visitServerRpc(TreeLogger logger, JClassType type,
+ ConnectorBundle bundle) {
+ bundle.setNeedsProxySupport(type);
+
+ Set<? extends JClassType> superTypes = type
+ .getFlattenedSupertypeHierarchy();
+ for (JClassType subType : superTypes) {
+ if (subType.isInterface() != null) {
+ JMethod[] methods = subType.getMethods();
+ for (JMethod method : methods) {
+ bundle.setNeedsDelayedInfo(type, method);
+
+ JType[] parameterTypes = method.getParameterTypes();
+ for (JType paramType : parameterTypes) {
+ bundle.setNeedsSerialize(paramType);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/StateInitVisitor.java b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/StateInitVisitor.java
new file mode 100644
index 0000000000..17ea62b249
--- /dev/null
+++ b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/StateInitVisitor.java
@@ -0,0 +1,25 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.server.widgetsetutils.metadata;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JType;
+
+public class StateInitVisitor extends TypeVisitor {
+ @Override
+ public void visitConnector(TreeLogger logger, JClassType type,
+ ConnectorBundle bundle) {
+ JMethod getState = findInheritedMethod(type, "getState");
+ bundle.setNeedsReturnType(type, getState);
+
+ bundle.setNeedsSerialize(getState.getReturnType());
+
+ JType stateType = getState.getReturnType();
+ bundle.setNeedsGwtConstructor(stateType.isClass());
+ }
+
+}
diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/TypeVisitor.java b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/TypeVisitor.java
new file mode 100644
index 0000000000..7191093755
--- /dev/null
+++ b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/TypeVisitor.java
@@ -0,0 +1,58 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.server.widgetsetutils.metadata;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.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;
+
+public abstract class TypeVisitor {
+ public void init(TypeOracle oracle) throws NotFoundException {
+ // Default does nothing
+ }
+
+ public void visitConnector(TreeLogger logger, JClassType type,
+ ConnectorBundle bundle) throws UnableToCompleteException {
+ // Default does nothing
+ }
+
+ public void visitClientRpc(TreeLogger logger, JClassType type,
+ ConnectorBundle bundle) {
+ // Default does nothing
+ }
+
+ public void visitServerRpc(TreeLogger logger, JClassType type,
+ ConnectorBundle bundle) {
+ // Default does nothing
+ }
+
+ protected JMethod findInheritedMethod(JClassType type, String methodName,
+ JType... params) {
+
+ JClassType currentType = type;
+ while (currentType != null) {
+ JMethod method = currentType.findMethod(methodName, params);
+ if (method != null) {
+ return method;
+ }
+ currentType = currentType.getSuperclass();
+ }
+
+ JClassType[] interfaces = type.getImplementedInterfaces();
+ for (JClassType iface : interfaces) {
+ JMethod method = iface.findMethod(methodName, params);
+ if (method != null) {
+ return method;
+ }
+ }
+
+ return null;
+ }
+
+}
diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/WidgetInitVisitor.java b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/WidgetInitVisitor.java
new file mode 100644
index 0000000000..fbbabe084d
--- /dev/null
+++ b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/WidgetInitVisitor.java
@@ -0,0 +1,73 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.server.widgetsetutils.metadata;
+
+import java.util.Collection;
+
+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.vaadin.client.ui.AbstractComponentConnector;
+import com.vaadin.shared.annotations.DelegateToWidget;
+
+public class WidgetInitVisitor extends TypeVisitor {
+
+ @Override
+ public void visitConnector(TreeLogger logger, JClassType type,
+ ConnectorBundle bundle) throws UnableToCompleteException {
+ if (ConnectorBundle.isConnectedComponentConnector(type)) {
+ JClassType createWidgetClass = findInheritedMethod(type,
+ "createWidget").getEnclosingType();
+ boolean createWidgetOverridden = !createWidgetClass
+ .getQualifiedSourceName()
+ .equals(AbstractComponentConnector.class.getCanonicalName());
+ if (createWidgetOverridden) {
+ // Don't generate if createWidget is already overridden
+ return;
+ }
+
+ JMethod getWidget = findInheritedMethod(type, "getWidget");
+ bundle.setNeedsReturnType(type, getWidget);
+
+ JClassType widgetType = getWidget.getReturnType().isClass();
+ bundle.setNeedsGwtConstructor(widgetType);
+
+ JMethod getState = findInheritedMethod(type, "getState");
+ JClassType stateType = getState.getReturnType().isClass();
+
+ Collection<Property> properties = bundle.getProperties(stateType);
+ for (Property property : properties) {
+ DelegateToWidget delegateToWidget = property
+ .getAnnotation(DelegateToWidget.class);
+ if (delegateToWidget != null) {
+ bundle.setNeedsDelegateToWidget(property);
+ String methodName = DelegateToWidget.Helper
+ .getDelegateTarget(property.getName(),
+ delegateToWidget.value());
+ JMethod delegatedSetter = findInheritedMethod(widgetType,
+ methodName, property.getPropertyType());
+ if (delegatedSetter == null) {
+ logger.log(
+ Type.ERROR,
+ widgetType.getName()
+ + "."
+ + methodName
+ + "("
+ + property.getPropertyType()
+ .getSimpleSourceName()
+ + ") required by @DelegateToWidget for "
+ + stateType.getName() + "."
+ + property.getName()
+ + " can not be found.");
+ throw new UnableToCompleteException();
+ }
+ bundle.setNeedsInvoker(widgetType, delegatedSetter);
+ }
+ }
+ }
+ }
+}