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