From: Matti Tahvonen Date: Tue, 29 Sep 2009 13:25:47 +0000 (+0000) Subject: vaadin widget jars now detected by details in manifest file, simple widgetsetbuilder... X-Git-Tag: 6.7.0.beta1~2433^2~3 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=43467c624d543a25bce4fc3fd7963777851ac657;p=vaadin-framework.git vaadin widget jars now detected by details in manifest file, simple widgetsetbuilder tool + some refactoring svn changeset:8990/svn branch:2009-09-widget-packaging_3332 --- diff --git a/src/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml b/src/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml index 58d692e99f..0c139aabc4 100644 --- a/src/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml +++ b/src/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml @@ -31,7 +31,7 @@ - + diff --git a/src/com/vaadin/terminal/gwt/rebind/ClassPathExplorer.java b/src/com/vaadin/terminal/gwt/rebind/ClassPathExplorer.java deleted file mode 100644 index 6c81c368c4..0000000000 --- a/src/com/vaadin/terminal/gwt/rebind/ClassPathExplorer.java +++ /dev/null @@ -1,266 +0,0 @@ -package com.vaadin.terminal.gwt.rebind; - -import java.io.File; -import java.io.FileFilter; -import java.io.IOException; -import java.net.JarURLConnection; -import java.net.URL; -import java.net.URLConnection; -import java.util.Collection; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; - -import com.vaadin.terminal.Paintable; -import com.vaadin.ui.ClientWidget; - -/** - * Utility class to find server side widgets with {@link ClientWidget} - * annotation. Used by WidgetMapGenerator to implement some monkey coding for - * you. - *

- * 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 final static FileFilter DIRECTORIES_ONLY = new FileFilter() { - public boolean accept(File f) { - if (f.exists() && f.isDirectory()) { - return true; - } else { - return false; - } - } - }; - - private static Map classpathLocations = getClasspathLocations(); - - private ClassPathExplorer() { - } - - public static Collection> getPaintablesHavingWidgetAnnotation() { - Collection> paintables = new HashSet>(); - Set keySet = classpathLocations.keySet(); - for (URL url : keySet) { - searchForPaintables(url, classpathLocations.get(url), paintables); - } - return paintables; - - } - - /** - * Determine every URL location defined by the current classpath, and it's - * associated package name. - */ - public final static Map getClasspathLocations() { - Map locations = new HashMap(); - - String pathSep = System.getProperty("path.separator"); - String classpath = System.getProperty("java.class.path"); - - String[] split = classpath.split(pathSep); - for (int i = 0; i < split.length; i++) { - String classpathEntry = split[i]; - if (acceptClassPathEntry(classpathEntry)) { - File file = new File(classpathEntry); - include(null, file, locations); - } - } - - return locations; - } - - 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 { - return false; - } - } - } - - /** - * Recursively add subdirectories and jar files to classpathlocations - * - * @param name - * @param file - * @param locations - */ - private final static void include(String name, File file, - Map 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 - locations.put(new URL("file://" + dirs[i].getCanonicalPath()), - name + dirs[i].getName()); - } catch (Exception ioe) { - return; - } - include(name + dirs[i].getName(), dirs[i], locations); - } - } - - private static void includeJar(File file, Map 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) { - locations.put(url, ""); - } - } catch (Exception e) { - // e.printStackTrace(); - return; - } - - } - - private static String packageNameFor(JarEntry entry) { - if (entry == null) { - return ""; - } - String s = entry.getName(); - if (s == null) { - return ""; - } - if (s.length() == 0) { - return s; - } - if (s.startsWith("/")) { - s = s.substring(1, s.length()); - } - if (s.endsWith("/")) { - s = s.substring(0, s.length() - 1); - } - return s.replace('/', '.'); - } - - private final static void searchForPaintables(URL location, - String packageName, - Collection> paintables) { - - // Get a File object for the package - 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 .class files - if (files[i].endsWith(".class")) { - // remove the .class extension - String classname = files[i].substring(0, - files[i].length() - 6); - classname = packageName + "." + classname; - tryToAdd(classname, paintables); - } - } - } 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(); - - Enumeration e = jarFile.entries(); - while (e.hasMoreElements()) { - JarEntry entry = e.nextElement(); - String entryname = entry.getName(); - if (!entry.isDirectory() - && entryname.endsWith(".class") - && !entryname.contains("$")) { - String classname = entryname.substring(0, entryname - .length() - 6); - if (classname.startsWith("/")) { - classname = classname.substring(1); - } - classname = classname.replace('/', '.'); - tryToAdd(classname, paintables); - } - } - } - } catch (IOException e) { - System.err.println(e); - } - } - - } - - private static void tryToAdd(final String fullclassName, - Collection> paintables) { - try { - Class c = Class.forName(fullclassName); - if (c.getAnnotation(ClientWidget.class) != null) { - paintables.add((Class) c); - System.out.println("Found paintable " + fullclassName); - } - } catch (ExceptionInInitializerError e) { - // e.printStackTrace(); - } catch (ClassNotFoundException e) { - // e.printStackTrace(); - } catch (NoClassDefFoundError e) { - // NOP - } catch (UnsatisfiedLinkError e) { - // NOP - } catch (Exception e) { - e.printStackTrace(); - } - } - - /** - * Test method for helper tool - */ - public static void main(String[] args) { - Collection> paintables = ClassPathExplorer - .getPaintablesHavingWidgetAnnotation(); - System.out.println("Found annotated paintables:"); - for (Class cls : paintables) { - System.out.println(cls.getCanonicalName()); - } - } -} \ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/rebind/WidgetMapGenerator.java b/src/com/vaadin/terminal/gwt/rebind/WidgetMapGenerator.java deleted file mode 100644 index ba42f4ebaa..0000000000 --- a/src/com/vaadin/terminal/gwt/rebind/WidgetMapGenerator.java +++ /dev/null @@ -1,216 +0,0 @@ -package com.vaadin.terminal.gwt.rebind; - -import java.io.PrintWriter; -import java.util.Collection; -import java.util.Date; -import java.util.Iterator; - -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.UnableToCompleteException; -import com.google.gwt.core.ext.TreeLogger.Type; -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.terminal.Paintable; -import com.vaadin.terminal.gwt.client.ui.VView; -import com.vaadin.ui.ClientWidget; - -/** - * GWT generator to build WidgetMapImpl dynamically based on - * {@link ClientWidget} annotations available in workspace. - * - */ -public class WidgetMapGenerator 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, "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 vaading components 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.setSuperclass("com.vaadin.terminal.gwt.client.WidgetMap"); - SourceWriter sourceWriter = composer.createSourceWriter(context, - printWriter); - - Collection> paintablesHavingWidgetAnnotation = getUsedPaintables(); - - validatePaintables(logger, context, paintablesHavingWidgetAnnotation); - - // generator constructor source code - generateImplementationDetector(sourceWriter, - paintablesHavingWidgetAnnotation); - generateInstantiatorMethod(sourceWriter, - paintablesHavingWidgetAnnotation); - // 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)"); - - } - - /** - * Verifies that all client side components are available for client side - * GWT module. - * - * @param logger - * @param context - * @param paintablesHavingWidgetAnnotation - */ - private void validatePaintables( - TreeLogger logger, - GeneratorContext context, - Collection> paintablesHavingWidgetAnnotation) { - TypeOracle typeOracle = context.getTypeOracle(); - - for (Iterator> iterator = paintablesHavingWidgetAnnotation - .iterator(); iterator.hasNext();) { - Class class1 = iterator.next(); - - ClientWidget annotation = class1.getAnnotation(ClientWidget.class); - - if (typeOracle.findType(annotation.value().getName()) == null) { - // GWT widget not inherited - logger - .log( - Type.WARN, - "Widget implementation for " - + class1.getName() - + " not available for GWT compiler (but mapped " - + "for component found in classpath). If this is not " - + "intentional, check your gwt module definition file."); - iterator.remove(); - } - - } - } - - /** - * This method is protected to allow easy creation of optimized widgetsets. - *

- * TODO we need some sort of mechanism to easily exclude/include components - * from widgetset. Properties in gwt.xml is one option. Now only possible by - * extending this class, overriding getUsedPaintables() method and - * redefining deferred binding rule. - * - * @return a collections of Vaadin components that will be added to - * widgetset - */ - protected Collection> getUsedPaintables() { - return ClassPathExplorer.getPaintablesHavingWidgetAnnotation(); - } - - private void generateInstantiatorMethod( - SourceWriter sourceWriter, - Collection> paintablesHavingWidgetAnnotation) { - sourceWriter - .println("public Paintable instantiate(Class classType) {"); - sourceWriter.indent(); - - sourceWriter - .println("Paintable p = super.instantiate(classType); if(p!= null) return p;"); - - for (Class class1 : paintablesHavingWidgetAnnotation) { - ClientWidget annotation = class1.getAnnotation(ClientWidget.class); - Class clientClass = annotation - .value(); - if (clientClass == VView.class) { - // VView's are not instantiated by widgetset - continue; - } - sourceWriter.print("if ("); - sourceWriter.print(clientClass.getName()); - sourceWriter.print(".class == classType) return GWT.create("); - sourceWriter.print(clientClass.getName()); - sourceWriter.println(".class );"); - sourceWriter.print("else "); - } - sourceWriter - .println("return GWT.create( com.vaadin.terminal.gwt.client.ui.VUnknownComponent.class );"); - sourceWriter.outdent(); - sourceWriter.println("}"); - } - - /** - * - * @param sourceWriter - * Source writer to output source code - * @param paintablesHavingWidgetAnnotation - */ - private void generateImplementationDetector( - SourceWriter sourceWriter, - Collection> paintablesHavingWidgetAnnotation) { - sourceWriter - .println("public Class " - + "getImplementationByServerSideClassName(String fullyQualifiedName) {"); - sourceWriter.indent(); - sourceWriter - .println("fullyQualifiedName = fullyQualifiedName.intern();"); - - for (Class class1 : paintablesHavingWidgetAnnotation) { - ClientWidget annotation = class1.getAnnotation(ClientWidget.class); - Class clientClass = annotation - .value(); - sourceWriter.print("if ( fullyQualifiedName == \""); - sourceWriter.print(class1.getName()); - sourceWriter.print("\" ) return "); - sourceWriter.print(clientClass.getName()); - sourceWriter.println(".class;"); - sourceWriter.print("else "); - } - sourceWriter - .println("return com.vaadin.terminal.gwt.client.ui.VUnknownComponent.class;"); - sourceWriter.outdent(); - sourceWriter.println("}"); - - } - -} diff --git a/src/com/vaadin/terminal/gwt/widgetsetutils/ClassPathExplorer.java b/src/com/vaadin/terminal/gwt/widgetsetutils/ClassPathExplorer.java new file mode 100644 index 0000000000..c51e2a2188 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/widgetsetutils/ClassPathExplorer.java @@ -0,0 +1,349 @@ +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.Collection; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.Manifest; + +import com.vaadin.terminal.Paintable; +import com.vaadin.ui.ClientWidget; + +/** + * 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. + *

+ * Used by WidgetMapGenerator and ide tools to implement some monkey coding for + * you. + *

+ * 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 final static FileFilter DIRECTORIES_ONLY = new FileFilter() { + public boolean accept(File f) { + if (f.exists() && f.isDirectory()) { + return true; + } else { + return false; + } + } + }; + + private static Map classpathLocations = getClasspathLocations(); + + private ClassPathExplorer() { + } + + /** + * Finds server side widgets with {@link ClientWidget} annotation. + */ + public static Collection> getPaintablesHavingWidgetAnnotation() { + Collection> paintables = new HashSet>(); + Set keySet = classpathLocations.keySet(); + for (URL url : keySet) { + searchForPaintables(url, classpathLocations.get(url), paintables); + } + return paintables; + + } + + /** + * Finds available widgetset names. + * + * @return + */ + public static Collection getAvailableWidgetSets() { + Collection widgetsets = new HashSet(); + Set keySet = classpathLocations.keySet(); + for (URL url : keySet) { + searchForWidgetSets(url, widgetsets); + } + return widgetsets; + } + + private static void searchForWidgetSets(URL location, + Collection widgetsets) { + + 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")) { + // remove the extension + String classname = files[i].substring(0, + files[i].length() - 8); + classname = classpathLocations.get(location) + "." + + classname; + widgetsets.add(classname); + } + } + } 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(); + 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(); + widgetsets.add(widgetsetname); + } + } + } + } catch (IOException e) { + System.err.println(e); + } + + } + } + + /** + * Determine every URL location defined by the current classpath, and it's + * associated package name. + */ + private final static Map getClasspathLocations() { + Map locations = new HashMap(); + + String pathSep = System.getProperty("path.separator"); + String classpath = System.getProperty("java.class.path"); + + String[] split = classpath.split(pathSep); + for (int i = 0; i < split.length; i++) { + String classpathEntry = split[i]; + if (acceptClassPathEntry(classpathEntry)) { + File file = new File(classpathEntry); + include(null, file, locations); + } + } + + return locations; + } + + 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(); + JarFile jarFile = conn.getJarFile(); + Manifest manifest = jarFile.getManifest(); + Attributes mainAttributes = manifest.getMainAttributes(); + if (mainAttributes.getValue("Vaadin-Widgetsets") != null) { + return true; + } + } catch (MalformedURLException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + return false; + } + } + } + + /** + * Recursively add subdirectories and jar files to classpathlocations + * + * @param name + * @param file + * @param locations + */ + private final static void include(String name, File file, + Map 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 + locations.put(new URL("file://" + dirs[i].getCanonicalPath()), + name + dirs[i].getName()); + } catch (Exception ioe) { + return; + } + include(name + dirs[i].getName(), dirs[i], locations); + } + } + + private static void includeJar(File file, Map 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) { + locations.put(url, ""); + } + } catch (Exception e) { + // e.printStackTrace(); + return; + } + + } + + private final static void searchForPaintables(URL location, + String packageName, + Collection> paintables) { + + // Get a File object for the package + 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 .class files + if (files[i].endsWith(".class")) { + // remove the .class extension + String classname = files[i].substring(0, + files[i].length() - 6); + classname = packageName + "." + classname; + tryToAdd(classname, paintables); + } + } + } 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(); + + Enumeration e = jarFile.entries(); + while (e.hasMoreElements()) { + JarEntry entry = e.nextElement(); + String entryname = entry.getName(); + if (!entry.isDirectory() + && entryname.endsWith(".class") + && !entryname.contains("$")) { + String classname = entryname.substring(0, entryname + .length() - 6); + if (classname.startsWith("/")) { + classname = classname.substring(1); + } + classname = classname.replace('/', '.'); + tryToAdd(classname, paintables); + } + } + } + } catch (IOException e) { + System.err.println(e); + } + } + + } + + private static void tryToAdd(final String fullclassName, + Collection> paintables) { + try { + Class c = Class.forName(fullclassName); + if (c.getAnnotation(ClientWidget.class) != null) { + paintables.add((Class) c); + // System.out.println("Found paintable " + fullclassName); + } + } catch (ExceptionInInitializerError e) { + // e.printStackTrace(); + } catch (ClassNotFoundException e) { + // e.printStackTrace(); + } catch (NoClassDefFoundError e) { + // NOP + } catch (UnsatisfiedLinkError e) { + // NOP + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Test method for helper tool + */ + public static void main(String[] args) { + Collection> paintables = ClassPathExplorer + .getPaintablesHavingWidgetAnnotation(); + System.out.println("Found annotated paintables:"); + for (Class cls : paintables) { + System.out.println(cls.getCanonicalName()); + } + + System.out.println(); + System.out.println("Searching available widgetsets..."); + + Collection availableWidgetSets = ClassPathExplorer + .getAvailableWidgetSets(); + for (String string : availableWidgetSets) { + System.out.println(string); + } + } +} \ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/widgetsetutils/WidgetMapGenerator.java b/src/com/vaadin/terminal/gwt/widgetsetutils/WidgetMapGenerator.java new file mode 100644 index 0000000000..8bd961d925 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/widgetsetutils/WidgetMapGenerator.java @@ -0,0 +1,216 @@ +package com.vaadin.terminal.gwt.widgetsetutils; + +import java.io.PrintWriter; +import java.util.Collection; +import java.util.Date; +import java.util.Iterator; + +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.UnableToCompleteException; +import com.google.gwt.core.ext.TreeLogger.Type; +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.terminal.Paintable; +import com.vaadin.terminal.gwt.client.ui.VView; +import com.vaadin.ui.ClientWidget; + +/** + * GWT generator to build WidgetMapImpl dynamically based on + * {@link ClientWidget} annotations available in workspace. + * + */ +public class WidgetMapGenerator 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, "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 vaading components 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.setSuperclass("com.vaadin.terminal.gwt.client.WidgetMap"); + SourceWriter sourceWriter = composer.createSourceWriter(context, + printWriter); + + Collection> paintablesHavingWidgetAnnotation = getUsedPaintables(); + + validatePaintables(logger, context, paintablesHavingWidgetAnnotation); + + // generator constructor source code + generateImplementationDetector(sourceWriter, + paintablesHavingWidgetAnnotation); + generateInstantiatorMethod(sourceWriter, + paintablesHavingWidgetAnnotation); + // 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)"); + + } + + /** + * Verifies that all client side components are available for client side + * GWT module. + * + * @param logger + * @param context + * @param paintablesHavingWidgetAnnotation + */ + private void validatePaintables( + TreeLogger logger, + GeneratorContext context, + Collection> paintablesHavingWidgetAnnotation) { + TypeOracle typeOracle = context.getTypeOracle(); + + for (Iterator> iterator = paintablesHavingWidgetAnnotation + .iterator(); iterator.hasNext();) { + Class class1 = iterator.next(); + + ClientWidget annotation = class1.getAnnotation(ClientWidget.class); + + if (typeOracle.findType(annotation.value().getName()) == null) { + // GWT widget not inherited + logger + .log( + Type.WARN, + "Widget implementation for " + + class1.getName() + + " not available for GWT compiler (but mapped " + + "for component found in classpath). If this is not " + + "intentional, check your gwt module definition file."); + iterator.remove(); + } + + } + } + + /** + * This method is protected to allow easy creation of optimized widgetsets. + *

+ * TODO we need some sort of mechanism to easily exclude/include components + * from widgetset. Properties in gwt.xml is one option. Now only possible by + * extending this class, overriding getUsedPaintables() method and + * redefining deferred binding rule. + * + * @return a collections of Vaadin components that will be added to + * widgetset + */ + protected Collection> getUsedPaintables() { + return ClassPathExplorer.getPaintablesHavingWidgetAnnotation(); + } + + private void generateInstantiatorMethod( + SourceWriter sourceWriter, + Collection> paintablesHavingWidgetAnnotation) { + sourceWriter + .println("public Paintable instantiate(Class classType) {"); + sourceWriter.indent(); + + sourceWriter + .println("Paintable p = super.instantiate(classType); if(p!= null) return p;"); + + for (Class class1 : paintablesHavingWidgetAnnotation) { + ClientWidget annotation = class1.getAnnotation(ClientWidget.class); + Class clientClass = annotation + .value(); + if (clientClass == VView.class) { + // VView's are not instantiated by widgetset + continue; + } + sourceWriter.print("if ("); + sourceWriter.print(clientClass.getName()); + sourceWriter.print(".class == classType) return GWT.create("); + sourceWriter.print(clientClass.getName()); + sourceWriter.println(".class );"); + sourceWriter.print("else "); + } + sourceWriter + .println("return GWT.create( com.vaadin.terminal.gwt.client.ui.VUnknownComponent.class );"); + sourceWriter.outdent(); + sourceWriter.println("}"); + } + + /** + * + * @param sourceWriter + * Source writer to output source code + * @param paintablesHavingWidgetAnnotation + */ + private void generateImplementationDetector( + SourceWriter sourceWriter, + Collection> paintablesHavingWidgetAnnotation) { + sourceWriter + .println("public Class " + + "getImplementationByServerSideClassName(String fullyQualifiedName) {"); + sourceWriter.indent(); + sourceWriter + .println("fullyQualifiedName = fullyQualifiedName.intern();"); + + for (Class class1 : paintablesHavingWidgetAnnotation) { + ClientWidget annotation = class1.getAnnotation(ClientWidget.class); + Class clientClass = annotation + .value(); + sourceWriter.print("if ( fullyQualifiedName == \""); + sourceWriter.print(class1.getName()); + sourceWriter.print("\" ) return "); + sourceWriter.print(clientClass.getName()); + sourceWriter.println(".class;"); + sourceWriter.print("else "); + } + sourceWriter + .println("return com.vaadin.terminal.gwt.client.ui.VUnknownComponent.class;"); + sourceWriter.outdent(); + sourceWriter.println("}"); + + } + +} diff --git a/src/com/vaadin/terminal/gwt/widgetsetutils/WidgetSetBuilder.java b/src/com/vaadin/terminal/gwt/widgetsetutils/WidgetSetBuilder.java new file mode 100644 index 0000000000..c2c888c4bb --- /dev/null +++ b/src/com/vaadin/terminal/gwt/widgetsetutils/WidgetSetBuilder.java @@ -0,0 +1,134 @@ +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.util.Collection; +import java.util.HashSet; +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. + */ +public class WidgetSetBuilder { + + public static void main(String[] args) throws IOException { + if (args.length == 0) { + printUsage(); + } else { + String widgetsetname = args[0]; + String sourcepath = args[1]; + updateWidgetSet(widgetsetname, sourcepath); + + } + } + + public static void updateWidgetSet(final String widgetset, String sourcepath) + throws IOException, FileNotFoundException { + String widgetsetfilename = sourcepath + "/" + + widgetset.replace(".", "/") + ".gwt.xml"; + File widgetsetFile = new File(widgetsetfilename); + if (!widgetsetFile.exists()) { + // create empty gwt module file + widgetsetFile.createNewFile(); + PrintStream printStream = new PrintStream(new FileOutputStream( + widgetsetFile)); + printStream.print("\n\n\n"); + printStream.close(); + } + + String content = readFile(widgetsetFile); + + Collection oldInheritedWidgetsets = getCurrentWidgetSets(content); + + Collection availableWidgetSets = ClassPathExplorer + .getAvailableWidgetSets(); + + // add widgetsets that do not exist + for (String ws : availableWidgetSets) { + 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.contains(ws)) { + // widgetset not available in classpath + content = removeWidgetSet(ws, content); + } + } + + commitChanges(widgetsetfilename, content); + } + + private static String removeWidgetSet(String ws, String content) { + return content.replaceFirst("", ""); + } + + 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("", "\n\t" + "\n"); + } + + private static Collection getCurrentWidgetSets(String content) { + HashSet hashSet = new HashSet(); + Pattern inheritsPattern = Pattern.compile(" name=\"([^\"]*)\""); + + Matcher matcher = inheritsPattern.matcher(content); + + while (matcher.find()) { + String possibleWidgetSet = matcher.group(1); + if (possibleWidgetSet.toLowerCase().contains("widgetset")) { + hashSet.add(possibleWidgetSet); + } + } + return hashSet; + } + + 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"); + } + return sb.toString(); + } + + private static void printUsage() { + PrintStream o = System.out; + o.println(WidgetSetBuilder.class.getSimpleName() + " usage:"); + o.println("\t1. Set the same classpath as you will " + + "have for the GWT compiler."); + o.println("\t2. Give the widgetsetname (to be created or updated)" + + " as first parameter, source path as a second parameter"); + o.println(); + o + .println("All found vaadin widgetsets will be inherited in given widgetset"); + + } + +}