]> source.dussan.org Git - vaadin-framework.git/commitdiff
vaadin widget jars now detected by details in manifest file, simple widgetsetbuilder...
authorMatti Tahvonen <matti.tahvonen@itmill.com>
Tue, 29 Sep 2009 13:25:47 +0000 (13:25 +0000)
committerMatti Tahvonen <matti.tahvonen@itmill.com>
Tue, 29 Sep 2009 13:25:47 +0000 (13:25 +0000)
svn changeset:8990/svn branch:2009-09-widget-packaging_3332

src/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml
src/com/vaadin/terminal/gwt/rebind/ClassPathExplorer.java [deleted file]
src/com/vaadin/terminal/gwt/rebind/WidgetMapGenerator.java [deleted file]
src/com/vaadin/terminal/gwt/widgetsetutils/ClassPathExplorer.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/widgetsetutils/WidgetMapGenerator.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/widgetsetutils/WidgetSetBuilder.java [new file with mode: 0644]

index 58d692e99fa2731d9af5c9bd3e46762371942f54..0c139aabc4cb75656efb82e58196387e62024c3f 100644 (file)
@@ -31,7 +31,7 @@
                <when-property-is name="user.agent" value="ie6"/>
        </replace-with>
 
-       <generate-with class="com.vaadin.terminal.gwt.rebind.WidgetMapGenerator">
+       <generate-with class="com.vaadin.terminal.gwt.widgetsetutils.WidgetMapGenerator">
                <when-type-is class="com.vaadin.terminal.gwt.client.WidgetMap"/>
        </generate-with>
        
diff --git a/src/com/vaadin/terminal/gwt/rebind/ClassPathExplorer.java b/src/com/vaadin/terminal/gwt/rebind/ClassPathExplorer.java
deleted file mode 100644 (file)
index 6c81c36..0000000
+++ /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.
- * <p>
- * 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<URL, String> classpathLocations = getClasspathLocations();
-
-    private ClassPathExplorer() {
-    }
-
-    public static Collection<Class<? extends Paintable>> getPaintablesHavingWidgetAnnotation() {
-        Collection<Class<? extends Paintable>> paintables = new HashSet<Class<? extends Paintable>>();
-        Set<URL> 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<URL, String> getClasspathLocations() {
-        Map<URL, String> locations = new HashMap<URL, String>();
-
-        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<URL, String> 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<URL, String> 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<Class<? extends Paintable>> 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<JarEntry> 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<Class<? extends Paintable>> paintables) {
-        try {
-            Class<?> c = Class.forName(fullclassName);
-            if (c.getAnnotation(ClientWidget.class) != null) {
-                paintables.add((Class<? extends Paintable>) 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<Class<? extends Paintable>> paintables = ClassPathExplorer
-                .getPaintablesHavingWidgetAnnotation();
-        System.out.println("Found annotated paintables:");
-        for (Class<? extends Paintable> 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 (file)
index ba42f4e..0000000
+++ /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<Class<? extends Paintable>> 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<Class<? extends Paintable>> paintablesHavingWidgetAnnotation) {
-        TypeOracle typeOracle = context.getTypeOracle();
-
-        for (Iterator<Class<? extends Paintable>> iterator = paintablesHavingWidgetAnnotation
-                .iterator(); iterator.hasNext();) {
-            Class<? extends Paintable> 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.
-     * <p>
-     * 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<Class<? extends Paintable>> getUsedPaintables() {
-        return ClassPathExplorer.getPaintablesHavingWidgetAnnotation();
-    }
-
-    private void generateInstantiatorMethod(
-            SourceWriter sourceWriter,
-            Collection<Class<? extends Paintable>> paintablesHavingWidgetAnnotation) {
-        sourceWriter
-                .println("public Paintable instantiate(Class<? extends Paintable> classType) {");
-        sourceWriter.indent();
-
-        sourceWriter
-                .println("Paintable p = super.instantiate(classType); if(p!= null) return p;");
-
-        for (Class<? extends Paintable> class1 : paintablesHavingWidgetAnnotation) {
-            ClientWidget annotation = class1.getAnnotation(ClientWidget.class);
-            Class<? extends com.vaadin.terminal.gwt.client.Paintable> 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<Class<? extends Paintable>> paintablesHavingWidgetAnnotation) {
-        sourceWriter
-                .println("public Class<? extends Paintable> "
-                        + "getImplementationByServerSideClassName(String fullyQualifiedName) {");
-        sourceWriter.indent();
-        sourceWriter
-                .println("fullyQualifiedName = fullyQualifiedName.intern();");
-
-        for (Class<? extends Paintable> class1 : paintablesHavingWidgetAnnotation) {
-            ClientWidget annotation = class1.getAnnotation(ClientWidget.class);
-            Class<? extends com.vaadin.terminal.gwt.client.Paintable> 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 (file)
index 0000000..c51e2a2
--- /dev/null
@@ -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.
+ * <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 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<URL, String> classpathLocations = getClasspathLocations();
+
+    private ClassPathExplorer() {
+    }
+
+    /**
+     * Finds server side widgets with {@link ClientWidget} annotation.
+     */
+    public static Collection<Class<? extends Paintable>> getPaintablesHavingWidgetAnnotation() {
+        Collection<Class<? extends Paintable>> paintables = new HashSet<Class<? extends Paintable>>();
+        Set<URL> keySet = classpathLocations.keySet();
+        for (URL url : keySet) {
+            searchForPaintables(url, classpathLocations.get(url), paintables);
+        }
+        return paintables;
+
+    }
+
+    /**
+     * Finds available widgetset names.
+     * 
+     * @return
+     */
+    public static Collection<String> getAvailableWidgetSets() {
+        Collection<String> widgetsets = new HashSet<String>();
+        Set<URL> keySet = classpathLocations.keySet();
+        for (URL url : keySet) {
+            searchForWidgetSets(url, widgetsets);
+        }
+        return widgetsets;
+    }
+
+    private static void searchForWidgetSets(URL location,
+            Collection<String> 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<URL, String> getClasspathLocations() {
+        Map<URL, String> locations = new HashMap<URL, String>();
+
+        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<URL, String> 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<URL, String> 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<Class<? extends Paintable>> 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<JarEntry> 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<Class<? extends Paintable>> paintables) {
+        try {
+            Class<?> c = Class.forName(fullclassName);
+            if (c.getAnnotation(ClientWidget.class) != null) {
+                paintables.add((Class<? extends Paintable>) 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<Class<? extends Paintable>> paintables = ClassPathExplorer
+                .getPaintablesHavingWidgetAnnotation();
+        System.out.println("Found annotated paintables:");
+        for (Class<? extends Paintable> cls : paintables) {
+            System.out.println(cls.getCanonicalName());
+        }
+
+        System.out.println();
+        System.out.println("Searching available widgetsets...");
+
+        Collection<String> 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 (file)
index 0000000..8bd961d
--- /dev/null
@@ -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<Class<? extends Paintable>> 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<Class<? extends Paintable>> paintablesHavingWidgetAnnotation) {
+        TypeOracle typeOracle = context.getTypeOracle();
+
+        for (Iterator<Class<? extends Paintable>> iterator = paintablesHavingWidgetAnnotation
+                .iterator(); iterator.hasNext();) {
+            Class<? extends Paintable> 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.
+     * <p>
+     * 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<Class<? extends Paintable>> getUsedPaintables() {
+        return ClassPathExplorer.getPaintablesHavingWidgetAnnotation();
+    }
+
+    private void generateInstantiatorMethod(
+            SourceWriter sourceWriter,
+            Collection<Class<? extends Paintable>> paintablesHavingWidgetAnnotation) {
+        sourceWriter
+                .println("public Paintable instantiate(Class<? extends Paintable> classType) {");
+        sourceWriter.indent();
+
+        sourceWriter
+                .println("Paintable p = super.instantiate(classType); if(p!= null) return p;");
+
+        for (Class<? extends Paintable> class1 : paintablesHavingWidgetAnnotation) {
+            ClientWidget annotation = class1.getAnnotation(ClientWidget.class);
+            Class<? extends com.vaadin.terminal.gwt.client.Paintable> 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<Class<? extends Paintable>> paintablesHavingWidgetAnnotation) {
+        sourceWriter
+                .println("public Class<? extends Paintable> "
+                        + "getImplementationByServerSideClassName(String fullyQualifiedName) {");
+        sourceWriter.indent();
+        sourceWriter
+                .println("fullyQualifiedName = fullyQualifiedName.intern();");
+
+        for (Class<? extends Paintable> class1 : paintablesHavingWidgetAnnotation) {
+            ClientWidget annotation = class1.getAnnotation(ClientWidget.class);
+            Class<? extends com.vaadin.terminal.gwt.client.Paintable> 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 (file)
index 0000000..c2c888c
--- /dev/null
@@ -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("<module>\n\n</module>\n");
+            printStream.close();
+        }
+
+        String content = readFile(widgetsetFile);
+
+        Collection<String> oldInheritedWidgetsets = getCurrentWidgetSets(content);
+
+        Collection<String> 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("<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\t<inherits name=\"" + ws
+                + "\" />" + "\n</module>");
+    }
+
+    private static Collection<String> getCurrentWidgetSets(String content) {
+        HashSet<String> hashSet = new HashSet<String>();
+        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");
+
+    }
+
+}