aboutsummaryrefslogtreecommitdiffstats
path: root/client/src/com/vaadin/terminal/gwt/widgetsetutils/WidgetMapGenerator.java
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/com/vaadin/terminal/gwt/widgetsetutils/WidgetMapGenerator.java')
-rw-r--r--client/src/com/vaadin/terminal/gwt/widgetsetutils/WidgetMapGenerator.java398
1 files changed, 398 insertions, 0 deletions
diff --git a/client/src/com/vaadin/terminal/gwt/widgetsetutils/WidgetMapGenerator.java b/client/src/com/vaadin/terminal/gwt/widgetsetutils/WidgetMapGenerator.java
new file mode 100644
index 0000000000..0d062ec4ff
--- /dev/null
+++ b/client/src/com/vaadin/terminal/gwt/widgetsetutils/WidgetMapGenerator.java
@@ -0,0 +1,398 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.terminal.gwt.widgetsetutils;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.TreeSet;
+
+import com.google.gwt.core.ext.Generator;
+import com.google.gwt.core.ext.GeneratorContext;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.TreeLogger.Type;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
+import com.google.gwt.user.rebind.SourceWriter;
+import com.vaadin.shared.Connector;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.shared.ui.Connect.LoadStyle;
+import com.vaadin.terminal.gwt.client.ServerConnector;
+import com.vaadin.terminal.gwt.client.ui.UnknownComponentConnector;
+import com.vaadin.terminal.gwt.client.ui.root.RootConnector;
+import com.vaadin.terminal.gwt.server.ClientConnector;
+
+/**
+ * WidgetMapGenerator's are GWT generator to build WidgetMapImpl dynamically
+ * based on {@link Connect} annotations available in workspace. By modifying the
+ * generator it is possible to do some fine tuning for the generated widgetset
+ * (aka client side engine). The components to be included in the client side
+ * engine can modified be overriding {@link #getUsedConnectors()}.
+ * <p>
+ * The generator also decides how the client side component implementations are
+ * loaded to the browser. The default generator is
+ * {@link EagerWidgetMapGenerator} that builds a monolithic client side engine
+ * that loads all widget implementation on application initialization. This has
+ * been the only option until Vaadin 6.4.
+ * <p>
+ * This generator uses the loadStyle hints from the {@link Connect} annotations.
+ * Depending on the {@link LoadStyle} used, the widget may be included in the
+ * initially loaded JavaScript, loaded when the application has started and
+ * there is no communication to server or lazy loaded when the implementation is
+ * absolutely needed.
+ * <p>
+ * The GWT module description file of the widgetset (
+ * <code>...Widgetset.gwt.xml</code>) can be used to define the
+ * WidgetMapGenarator. An example that defines this generator to be used:
+ *
+ * <pre>
+ * <code>
+ * &lt;generate-with
+ * class="com.vaadin.terminal.gwt.widgetsetutils.MyWidgetMapGenerator"&gt;
+ * &lt;when-type-is class="com.vaadin.terminal.gwt.client.WidgetMap" /&gt;
+ * &lt;/generate-with&gt;
+ *
+ * </code>
+ * </pre>
+ *
+ * <p>
+ * Vaadin package also includes {@link LazyWidgetMapGenerator}, which is a good
+ * option if the transferred data should be minimized, and
+ * {@link CustomWidgetMapGenerator} for easy overriding of loading strategies.
+ *
+ */
+public class WidgetMapGenerator extends Generator {
+
+ private static String serverConnectorClassName = ServerConnector.class
+ .getName();
+
+ private String packageName;
+ private String className;
+
+ @Override
+ public String generate(TreeLogger logger, GeneratorContext context,
+ String typeName) throws UnableToCompleteException {
+
+ try {
+ TypeOracle typeOracle = context.getTypeOracle();
+
+ // get classType and save instance variables
+ JClassType classType = typeOracle.getType(typeName);
+ packageName = classType.getPackage().getName();
+ className = classType.getSimpleSourceName() + "Impl";
+ // Generate class source code
+ generateClass(logger, context);
+ } catch (Exception e) {
+ logger.log(TreeLogger.ERROR, "WidgetMap creation failed", e);
+ }
+ // return the fully qualifed name of the class generated
+ return packageName + "." + className;
+ }
+
+ /**
+ * Generate source code for WidgetMapImpl
+ *
+ * @param logger
+ * Logger object
+ * @param context
+ * Generator context
+ */
+ private void generateClass(TreeLogger logger, GeneratorContext context) {
+ // get print writer that receives the source code
+ PrintWriter printWriter = null;
+ printWriter = context.tryCreate(logger, packageName, className);
+ // print writer if null, source code has ALREADY been generated,
+ // return (WidgetMap is equal to all permutations atm)
+ if (printWriter == null) {
+ return;
+ }
+ logger.log(Type.INFO,
+ "Detecting Vaadin connectors in classpath to generate WidgetMapImpl.java ...");
+ Date date = new Date();
+
+ // init composer, set class properties, create source writer
+ ClassSourceFileComposerFactory composer = null;
+ composer = new ClassSourceFileComposerFactory(packageName, className);
+ composer.addImport("com.google.gwt.core.client.GWT");
+ composer.addImport("java.util.HashMap");
+ composer.addImport("com.google.gwt.core.client.RunAsyncCallback");
+ composer.setSuperclass("com.vaadin.terminal.gwt.client.WidgetMap");
+ SourceWriter sourceWriter = composer.createSourceWriter(context,
+ printWriter);
+
+ Collection<Class<? extends ServerConnector>> connectors = getUsedConnectors(context
+ .getTypeOracle());
+
+ validateConnectors(logger, connectors);
+ logConnectors(logger, context, connectors);
+
+ // generator constructor source code
+ generateImplementationDetector(sourceWriter, connectors);
+ generateInstantiatorMethod(sourceWriter, connectors);
+ // close generated class
+ sourceWriter.outdent();
+ sourceWriter.println("}");
+ // commit generated class
+ context.commit(logger, printWriter);
+ logger.log(Type.INFO,
+ "Done. (" + (new Date().getTime() - date.getTime()) / 1000
+ + "seconds)");
+
+ }
+
+ private void validateConnectors(TreeLogger logger,
+ Collection<Class<? extends ServerConnector>> connectors) {
+
+ Iterator<Class<? extends ServerConnector>> iter = connectors.iterator();
+ while (iter.hasNext()) {
+ Class<? extends ServerConnector> connectorClass = iter.next();
+ Connect annotation = connectorClass.getAnnotation(Connect.class);
+ if (!ClientConnector.class.isAssignableFrom(annotation.value())) {
+ logger.log(
+ Type.WARN,
+ "Connector class "
+ + annotation.value().getName()
+ + " defined in @Connect annotation is not a subclass of "
+ + ClientConnector.class.getName()
+ + ". The component connector "
+ + connectorClass.getName()
+ + " will not be included in the widgetset.");
+ iter.remove();
+ }
+ }
+
+ }
+
+ private void logConnectors(TreeLogger logger, GeneratorContext context,
+ Collection<Class<? extends ServerConnector>> connectors) {
+ logger.log(Type.INFO,
+ "Widget set will contain implementations for following component connectors: ");
+
+ TreeSet<String> classNames = new TreeSet<String>();
+ HashMap<String, String> loadStyle = new HashMap<String, String>();
+ for (Class<? extends ServerConnector> connectorClass : connectors) {
+ String className = connectorClass.getCanonicalName();
+ classNames.add(className);
+ if (getLoadStyle(connectorClass) == LoadStyle.DEFERRED) {
+ loadStyle.put(className, "DEFERRED");
+ } else if (getLoadStyle(connectorClass) == LoadStyle.LAZY) {
+ loadStyle.put(className, "LAZY");
+ }
+
+ }
+ for (String className : classNames) {
+ String msg = className;
+ if (loadStyle.containsKey(className)) {
+ msg += " (load style: " + loadStyle.get(className) + ")";
+ }
+ logger.log(Type.INFO, "\t" + msg);
+ }
+ }
+
+ /**
+ * This method is protected to allow creation of optimized widgetsets. The
+ * Widgetset will contain only implementation returned by this function. If
+ * one knows which widgets are needed for the application, returning only
+ * them here will significantly optimize the size of the produced JS.
+ *
+ * @return a collections of Vaadin components that will be added to
+ * widgetset
+ */
+ @SuppressWarnings("unchecked")
+ private Collection<Class<? extends ServerConnector>> getUsedConnectors(
+ TypeOracle typeOracle) {
+ JClassType connectorType = typeOracle.findType(Connector.class
+ .getName());
+ Collection<Class<? extends ServerConnector>> connectors = new HashSet<Class<? extends ServerConnector>>();
+ for (JClassType jClassType : connectorType.getSubtypes()) {
+ Connect annotation = jClassType.getAnnotation(Connect.class);
+ if (annotation != null) {
+ try {
+ Class<? extends ServerConnector> clazz = (Class<? extends ServerConnector>) Class
+ .forName(jClassType.getQualifiedSourceName());
+ connectors.add(clazz);
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ return connectors;
+ }
+
+ /**
+ * Returns true if the widget for given component will be lazy loaded by the
+ * client. The default implementation reads the information from the
+ * {@link Connect} annotation.
+ * <p>
+ * The method can be overridden to optimize the widget loading mechanism. If
+ * the Widgetset is wanted to be optimized for a network with a high latency
+ * or for a one with a very fast throughput, it may be good to return false
+ * for every component.
+ *
+ * @param connector
+ * @return true iff the widget for given component should be lazy loaded by
+ * the client side engine
+ */
+ protected LoadStyle getLoadStyle(Class<? extends ServerConnector> connector) {
+ Connect annotation = connector.getAnnotation(Connect.class);
+ return annotation.loadStyle();
+ }
+
+ private void generateInstantiatorMethod(
+ SourceWriter sourceWriter,
+ Collection<Class<? extends ServerConnector>> connectorsHavingComponentAnnotation) {
+
+ Collection<Class<?>> deferredWidgets = new LinkedList<Class<?>>();
+
+ // TODO detect if it would be noticably faster to instantiate with a
+ // lookup with index than with the hashmap
+
+ sourceWriter.println("public void ensureInstantiator(Class<? extends "
+ + serverConnectorClassName + "> classType) {");
+ sourceWriter.println("if(!instmap.containsKey(classType)){");
+ boolean first = true;
+
+ ArrayList<Class<? extends ServerConnector>> lazyLoadedConnectors = new ArrayList<Class<? extends ServerConnector>>();
+
+ HashSet<Class<? extends ServerConnector>> connectorsWithInstantiator = new HashSet<Class<? extends ServerConnector>>();
+
+ for (Class<? extends ServerConnector> class1 : connectorsHavingComponentAnnotation) {
+ Class<? extends ServerConnector> clientClass = class1;
+ if (connectorsWithInstantiator.contains(clientClass)) {
+ continue;
+ }
+ if (clientClass == RootConnector.class) {
+ // Roots are not instantiated by widgetset
+ continue;
+ }
+ if (!first) {
+ sourceWriter.print(" else ");
+ } else {
+ first = false;
+ }
+ sourceWriter.print("if( classType == " + clientClass.getName()
+ + ".class) {");
+
+ String instantiator = "new WidgetInstantiator() {\n public "
+ + serverConnectorClassName
+ + " get() {\n return GWT.create(" + clientClass.getName()
+ + ".class );\n}\n}\n";
+
+ LoadStyle loadStyle = getLoadStyle(class1);
+
+ if (loadStyle != LoadStyle.EAGER) {
+ sourceWriter
+ .print("ApplicationConfiguration.startWidgetLoading();\n"
+ + "GWT.runAsync( \n"
+ + "new WidgetLoader() { void addInstantiator() {instmap.put("
+ + clientClass.getName()
+ + ".class,"
+ + instantiator + ");}});\n");
+ lazyLoadedConnectors.add(class1);
+
+ if (loadStyle == LoadStyle.DEFERRED) {
+ deferredWidgets.add(class1);
+ }
+
+ } else {
+ // widget implementation in initially loaded js script
+ sourceWriter.print("instmap.put(");
+ sourceWriter.print(clientClass.getName());
+ sourceWriter.print(".class, ");
+ sourceWriter.print(instantiator);
+ sourceWriter.print(");");
+ }
+ sourceWriter.print("}");
+ connectorsWithInstantiator.add(clientClass);
+ }
+
+ sourceWriter.println("}");
+
+ sourceWriter.println("}");
+
+ sourceWriter.println("public Class<? extends "
+ + serverConnectorClassName
+ + ">[] getDeferredLoadedConnectors() {");
+
+ sourceWriter.println("return new Class[] {");
+ first = true;
+ for (Class<?> class2 : deferredWidgets) {
+ if (!first) {
+ sourceWriter.println(",");
+ }
+ first = false;
+ sourceWriter.print(class2.getName() + ".class");
+ }
+
+ sourceWriter.println("};");
+ sourceWriter.println("}");
+
+ // in constructor add a "thread" that lazyly loads lazy loaded widgets
+ // if communication to server idles
+
+ // TODO an array of lazy loaded widgets
+
+ // TODO an index of last ensured widget in array
+
+ sourceWriter.println("public " + serverConnectorClassName
+ + " instantiate(Class<? extends " + serverConnectorClassName
+ + "> classType) {");
+ sourceWriter.indent();
+ sourceWriter.println(serverConnectorClassName
+ + " p = super.instantiate(classType); if(p!= null) return p;");
+ sourceWriter.println("return instmap.get(classType).get();");
+
+ sourceWriter.outdent();
+ sourceWriter.println("}");
+
+ }
+
+ /**
+ *
+ * @param sourceWriter
+ * Source writer to output source code
+ * @param paintablesHavingWidgetAnnotation
+ */
+ private void generateImplementationDetector(
+ SourceWriter sourceWriter,
+ Collection<Class<? extends ServerConnector>> paintablesHavingWidgetAnnotation) {
+ sourceWriter
+ .println("public Class<? extends "
+ + serverConnectorClassName
+ + "> "
+ + "getConnectorClassForServerSideClassName(String fullyQualifiedName) {");
+ sourceWriter.indent();
+ sourceWriter
+ .println("fullyQualifiedName = fullyQualifiedName.intern();");
+
+ for (Class<? extends ServerConnector> connectorClass : paintablesHavingWidgetAnnotation) {
+ Class<? extends ClientConnector> clientConnectorClass = getClientConnectorClass(connectorClass);
+ sourceWriter.print("if ( fullyQualifiedName == \"");
+ sourceWriter.print(clientConnectorClass.getName());
+ sourceWriter.print("\" ) { ensureInstantiator("
+ + connectorClass.getName() + ".class); return ");
+ sourceWriter.print(connectorClass.getName());
+ sourceWriter.println(".class;}");
+ sourceWriter.print("else ");
+ }
+ sourceWriter.println("return "
+ + UnknownComponentConnector.class.getName() + ".class;");
+ sourceWriter.outdent();
+ sourceWriter.println("}");
+
+ }
+
+ private static Class<? extends ClientConnector> getClientConnectorClass(
+ Class<? extends ServerConnector> connectorClass) {
+ Connect annotation = connectorClass.getAnnotation(Connect.class);
+ return (Class<? extends ClientConnector>) annotation.value();
+ }
+}