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 16KB

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