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

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