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

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