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.

преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
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. }