You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

WidgetMapGenerator.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. /*
  2. @ITMillApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.widgetsetutils;
  5. import java.io.PrintWriter;
  6. import java.util.ArrayList;
  7. import java.util.Collection;
  8. import java.util.Date;
  9. import java.util.Iterator;
  10. import java.util.LinkedList;
  11. import java.util.TreeSet;
  12. import com.google.gwt.core.ext.Generator;
  13. import com.google.gwt.core.ext.GeneratorContext;
  14. import com.google.gwt.core.ext.TreeLogger;
  15. import com.google.gwt.core.ext.TreeLogger.Type;
  16. import com.google.gwt.core.ext.UnableToCompleteException;
  17. import com.google.gwt.core.ext.typeinfo.JClassType;
  18. import com.google.gwt.core.ext.typeinfo.TypeOracle;
  19. import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
  20. import com.google.gwt.user.rebind.SourceWriter;
  21. import com.vaadin.terminal.Paintable;
  22. import com.vaadin.terminal.gwt.client.ui.VView;
  23. import com.vaadin.ui.ClientWidget;
  24. import com.vaadin.ui.ClientWidget.LoadStyle;
  25. /**
  26. * WidgetMapGenerator's are GWT generator to build WidgetMapImpl dynamically
  27. * based on {@link ClientWidget} annotations available in workspace. By
  28. * modifying the generator it is possible to do some fine tuning for the
  29. * generated widgetset (aka client side engine). The components to be included
  30. * in the client side engine can modified be overriding
  31. * {@link #getUsedPaintables()}.
  32. * <p>
  33. * The generator also decides how the client side component implementations are
  34. * loaded to the browser. The default generator is
  35. * {@link EagerWidgetMapGenerator} that builds a monolithic client side engine
  36. * that loads all widget implementation on application initialization. This has
  37. * been the only option until Vaadin 6.4.
  38. * <p>
  39. * This generator uses the loadStyle hints from the {@link ClientWidget}
  40. * annotations. Depending on the {@link LoadStyle} used, the widget may be
  41. * included in the initially loaded JavaScript, loaded when the application has
  42. * started and there is no communication to server or lazy loaded when the
  43. * implementation is absolutely needed.
  44. * <p>
  45. * The GWT module description file of the widgetset (
  46. * <code>...Widgetset.gwt.xml</code>) can be used to define the
  47. * WidgetMapGenarator. An example that defines this generator to be used:
  48. *
  49. * <pre>
  50. * <code>
  51. * &lt;generate-with
  52. * class="com.vaadin.terminal.gwt.widgetsetutils.MyWidgetMapGenerator"&gt;
  53. * &lt;when-type-is class="com.vaadin.terminal.gwt.client.WidgetMap" /&gt;
  54. * &lt;/generate-with&gt;
  55. *
  56. * </code>
  57. * </pre>
  58. *
  59. * <p>
  60. * Vaadin package also includes {@link LazyWidgetMapGenerator}, which is a good
  61. * option if the transferred data should be minimized, and
  62. * {@link CustomWidgetMapGenerator} for easy overriding of loading strategies.
  63. *
  64. */
  65. public class WidgetMapGenerator extends Generator {
  66. private String packageName;
  67. private String className;
  68. @Override
  69. public String generate(TreeLogger logger, GeneratorContext context,
  70. String typeName) throws UnableToCompleteException {
  71. try {
  72. TypeOracle typeOracle = context.getTypeOracle();
  73. // get classType and save instance variables
  74. JClassType classType = typeOracle.getType(typeName);
  75. packageName = classType.getPackage().getName();
  76. className = classType.getSimpleSourceName() + "Impl";
  77. // Generate class source code
  78. generateClass(logger, context);
  79. } catch (Exception e) {
  80. logger.log(TreeLogger.ERROR, "WidgetMap creation failed", e);
  81. }
  82. // return the fully qualifed name of the class generated
  83. return packageName + "." + className;
  84. }
  85. /**
  86. * Generate source code for WidgetMapImpl
  87. *
  88. * @param logger
  89. * Logger object
  90. * @param context
  91. * Generator context
  92. */
  93. private void generateClass(TreeLogger logger, GeneratorContext context) {
  94. // get print writer that receives the source code
  95. PrintWriter printWriter = null;
  96. printWriter = context.tryCreate(logger, packageName, className);
  97. // print writer if null, source code has ALREADY been generated,
  98. // return (WidgetMap is equal to all permutations atm)
  99. if (printWriter == null) {
  100. return;
  101. }
  102. logger.log(Type.INFO,
  103. "Detecting Vaadin components in classpath to generate WidgetMapImpl.java ...");
  104. Date date = new Date();
  105. // init composer, set class properties, create source writer
  106. ClassSourceFileComposerFactory composer = null;
  107. composer = new ClassSourceFileComposerFactory(packageName, className);
  108. composer.addImport("com.google.gwt.core.client.GWT");
  109. composer.addImport("java.util.HashMap");
  110. composer.addImport("com.google.gwt.core.client.RunAsyncCallback");
  111. composer.setSuperclass("com.vaadin.terminal.gwt.client.WidgetMap");
  112. SourceWriter sourceWriter = composer.createSourceWriter(context,
  113. printWriter);
  114. Collection<Class<? extends Paintable>> paintablesHavingWidgetAnnotation = getUsedPaintables();
  115. validatePaintables(logger, context, paintablesHavingWidgetAnnotation);
  116. // generator constructor source code
  117. generateImplementationDetector(sourceWriter,
  118. paintablesHavingWidgetAnnotation);
  119. generateInstantiatorMethod(sourceWriter,
  120. paintablesHavingWidgetAnnotation);
  121. // close generated class
  122. sourceWriter.outdent();
  123. sourceWriter.println("}");
  124. // commit generated class
  125. context.commit(logger, printWriter);
  126. logger.log(Type.INFO,
  127. "Done. (" + (new Date().getTime() - date.getTime()) / 1000
  128. + "seconds)");
  129. }
  130. /**
  131. * Verifies that all client side components are available for client side
  132. * GWT module.
  133. *
  134. * @param logger
  135. * @param context
  136. * @param paintablesHavingWidgetAnnotation
  137. */
  138. private void validatePaintables(
  139. TreeLogger logger,
  140. GeneratorContext context,
  141. Collection<Class<? extends Paintable>> paintablesHavingWidgetAnnotation) {
  142. TypeOracle typeOracle = context.getTypeOracle();
  143. for (Iterator<Class<? extends Paintable>> iterator = paintablesHavingWidgetAnnotation
  144. .iterator(); iterator.hasNext();) {
  145. Class<? extends Paintable> class1 = iterator.next();
  146. ClientWidget annotation = class1.getAnnotation(ClientWidget.class);
  147. if (typeOracle.findType(annotation.value().getName()) == null) {
  148. // GWT widget not inherited
  149. logger.log(Type.WARN, "Widget class "
  150. + annotation.value().getName()
  151. + " was not found. The component " + class1.getName()
  152. + " will not be included in the widgetset.");
  153. iterator.remove();
  154. }
  155. }
  156. logger.log(Type.INFO,
  157. "Widget set will contain implementations for following components: ");
  158. TreeSet<String> classNames = new TreeSet<String>();
  159. for (Class<? extends Paintable> class1 : paintablesHavingWidgetAnnotation) {
  160. classNames.add(class1.getCanonicalName());
  161. }
  162. for (String string : classNames) {
  163. logger.log(Type.INFO, "\t" + string);
  164. }
  165. }
  166. /**
  167. * This method is protected to allow creation of optimized widgetsets. The
  168. * Widgetset will contain only implementation returned by this function. If
  169. * one knows which widgets are needed for the application, returning only
  170. * them here will significantly optimize the size of the produced JS.
  171. *
  172. * @return a collections of Vaadin components that will be added to
  173. * widgetset
  174. */
  175. protected Collection<Class<? extends Paintable>> getUsedPaintables() {
  176. return ClassPathExplorer.getPaintablesHavingWidgetAnnotation();
  177. }
  178. /**
  179. * Returns true if the widget for given component will be lazy loaded by the
  180. * client. The default implementation reads the information from the
  181. * {@link ClientWidget} annotation.
  182. * <p>
  183. * The method can be overridden to optimize the widget loading mechanism. If
  184. * the Widgetset is wanted to be optimized for a network with a high latency
  185. * or for a one with a very fast throughput, it may be good to return false
  186. * for every component.
  187. *
  188. * @param paintableType
  189. * @return true iff the widget for given component should be lazy loaded by
  190. * the client side engine
  191. */
  192. protected LoadStyle getLoadStyle(Class<? extends Paintable> paintableType) {
  193. ClientWidget annotation = paintableType
  194. .getAnnotation(ClientWidget.class);
  195. return annotation.loadStyle();
  196. }
  197. private void generateInstantiatorMethod(
  198. SourceWriter sourceWriter,
  199. Collection<Class<? extends Paintable>> paintablesHavingWidgetAnnotation) {
  200. Collection<Class<?>> deferredWidgets = new LinkedList<Class<?>>();
  201. // TODO detect if it would be noticably faster to instantiate with a
  202. // lookup with index than with the hashmap
  203. sourceWriter
  204. .println("public void ensureInstantiator(Class<? extends Paintable> classType) {");
  205. sourceWriter.println("if(!instmap.containsKey(classType)){");
  206. boolean first = true;
  207. ArrayList<Class<? extends Paintable>> lazyLoadedWidgets = new ArrayList<Class<? extends Paintable>>();
  208. for (Class<? extends Paintable> class1 : paintablesHavingWidgetAnnotation) {
  209. ClientWidget annotation = class1.getAnnotation(ClientWidget.class);
  210. Class<? extends com.vaadin.terminal.gwt.client.Paintable> clientClass = annotation
  211. .value();
  212. if (clientClass == VView.class) {
  213. // VView's are not instantiated by widgetset
  214. continue;
  215. }
  216. if (!first) {
  217. sourceWriter.print(" else ");
  218. } else {
  219. first = false;
  220. }
  221. sourceWriter.print("if( classType == " + clientClass.getName()
  222. + ".class) {");
  223. String instantiator = "new WidgetInstantiator() {\n public Paintable get() {\n return GWT.create("
  224. + clientClass.getName() + ".class );\n}\n}\n";
  225. LoadStyle loadStyle = getLoadStyle(class1);
  226. if (loadStyle != LoadStyle.EAGER) {
  227. sourceWriter
  228. .print("ApplicationConfiguration.startWidgetLoading();\n"
  229. + "GWT.runAsync( \n"
  230. + "new WidgetLoader() { void addInstantiator() {instmap.put("
  231. + clientClass.getName()
  232. + ".class,"
  233. + instantiator + ");}});\n");
  234. lazyLoadedWidgets.add(class1);
  235. if (loadStyle == LoadStyle.DEFERRED) {
  236. deferredWidgets.add(class1);
  237. }
  238. } else {
  239. // widget implementation in initially loaded js script
  240. sourceWriter.print("instmap.put(");
  241. sourceWriter.print(clientClass.getName());
  242. sourceWriter.print(".class, ");
  243. sourceWriter.print(instantiator);
  244. sourceWriter.print(");");
  245. }
  246. sourceWriter.print("}");
  247. }
  248. sourceWriter.println("}");
  249. sourceWriter.println("}");
  250. sourceWriter
  251. .println("public Class<? extends Paintable>[] getDeferredLoadedWidgets() {");
  252. sourceWriter.println("return new Class[] {");
  253. first = true;
  254. for (Class<?> class2 : deferredWidgets) {
  255. if (!first) {
  256. sourceWriter.println(",");
  257. }
  258. first = false;
  259. ClientWidget annotation = class2.getAnnotation(ClientWidget.class);
  260. Class<? extends com.vaadin.terminal.gwt.client.Paintable> value = annotation
  261. .value();
  262. sourceWriter.print(value.getName() + ".class");
  263. }
  264. sourceWriter.println("};");
  265. sourceWriter.println("}");
  266. // in constructor add a "thread" that lazyly loads lazy loaded widgets
  267. // if communication to server idles
  268. // TODO an array of lazy loaded widgets
  269. // TODO an index of last ensured widget in array
  270. sourceWriter
  271. .println("public Paintable instantiate(Class<? extends Paintable> classType) {");
  272. sourceWriter.indent();
  273. sourceWriter
  274. .println("Paintable p = super.instantiate(classType); if(p!= null) return p;");
  275. sourceWriter.println("return instmap.get(classType).get();");
  276. sourceWriter.outdent();
  277. sourceWriter.println("}");
  278. }
  279. /**
  280. *
  281. * @param sourceWriter
  282. * Source writer to output source code
  283. * @param paintablesHavingWidgetAnnotation
  284. */
  285. private void generateImplementationDetector(
  286. SourceWriter sourceWriter,
  287. Collection<Class<? extends Paintable>> paintablesHavingWidgetAnnotation) {
  288. sourceWriter
  289. .println("public Class<? extends Paintable> "
  290. + "getImplementationByServerSideClassName(String fullyQualifiedName) {");
  291. sourceWriter.indent();
  292. sourceWriter
  293. .println("fullyQualifiedName = fullyQualifiedName.intern();");
  294. for (Class<? extends Paintable> class1 : paintablesHavingWidgetAnnotation) {
  295. ClientWidget annotation = class1.getAnnotation(ClientWidget.class);
  296. Class<? extends com.vaadin.terminal.gwt.client.Paintable> clientClass = annotation
  297. .value();
  298. sourceWriter.print("if ( fullyQualifiedName == \"");
  299. sourceWriter.print(class1.getName());
  300. sourceWriter.print("\" ) { ensureInstantiator("
  301. + clientClass.getName() + ".class); return ");
  302. sourceWriter.print(clientClass.getName());
  303. sourceWriter.println(".class;}");
  304. sourceWriter.print("else ");
  305. }
  306. sourceWriter
  307. .println("return com.vaadin.terminal.gwt.client.ui.VUnknownComponent.class;");
  308. sourceWriter.outdent();
  309. sourceWriter.println("}");
  310. }
  311. }