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.

ConnectorBundleLoaderFactory.java 46KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278
  1. /*
  2. * Copyright 2000-2014 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.server.widgetsetutils;
  17. import java.io.PrintWriter;
  18. import java.util.ArrayList;
  19. import java.util.Arrays;
  20. import java.util.Collection;
  21. import java.util.Collections;
  22. import java.util.Comparator;
  23. import java.util.HashMap;
  24. import java.util.HashSet;
  25. import java.util.List;
  26. import java.util.Map;
  27. import java.util.Map.Entry;
  28. import java.util.Set;
  29. import com.google.gwt.core.client.GWT;
  30. import com.google.gwt.core.client.RunAsyncCallback;
  31. import com.google.gwt.core.ext.Generator;
  32. import com.google.gwt.core.ext.GeneratorContext;
  33. import com.google.gwt.core.ext.TreeLogger;
  34. import com.google.gwt.core.ext.TreeLogger.Type;
  35. import com.google.gwt.core.ext.UnableToCompleteException;
  36. import com.google.gwt.core.ext.typeinfo.JClassType;
  37. import com.google.gwt.core.ext.typeinfo.JMethod;
  38. import com.google.gwt.core.ext.typeinfo.JParameterizedType;
  39. import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
  40. import com.google.gwt.core.ext.typeinfo.JType;
  41. import com.google.gwt.core.ext.typeinfo.NotFoundException;
  42. import com.google.gwt.core.ext.typeinfo.TypeOracle;
  43. import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
  44. import com.google.gwt.user.rebind.SourceWriter;
  45. import com.vaadin.client.JsArrayObject;
  46. import com.vaadin.client.ServerConnector;
  47. import com.vaadin.client.annotations.OnStateChange;
  48. import com.vaadin.client.metadata.ConnectorBundleLoader;
  49. import com.vaadin.client.metadata.ConnectorBundleLoader.CValUiInfo;
  50. import com.vaadin.client.metadata.InvokationHandler;
  51. import com.vaadin.client.metadata.OnStateChangeMethod;
  52. import com.vaadin.client.metadata.ProxyHandler;
  53. import com.vaadin.client.metadata.TypeData;
  54. import com.vaadin.client.metadata.TypeDataStore;
  55. import com.vaadin.client.ui.UnknownComponentConnector;
  56. import com.vaadin.server.widgetsetutils.metadata.ClientRpcVisitor;
  57. import com.vaadin.server.widgetsetutils.metadata.ConnectorBundle;
  58. import com.vaadin.server.widgetsetutils.metadata.ConnectorInitVisitor;
  59. import com.vaadin.server.widgetsetutils.metadata.GeneratedSerializer;
  60. import com.vaadin.server.widgetsetutils.metadata.OnStateChangeVisitor;
  61. import com.vaadin.server.widgetsetutils.metadata.Property;
  62. import com.vaadin.server.widgetsetutils.metadata.ServerRpcVisitor;
  63. import com.vaadin.server.widgetsetutils.metadata.StateInitVisitor;
  64. import com.vaadin.server.widgetsetutils.metadata.TypeVisitor;
  65. import com.vaadin.server.widgetsetutils.metadata.WidgetInitVisitor;
  66. import com.vaadin.shared.annotations.Delayed;
  67. import com.vaadin.shared.annotations.DelegateToWidget;
  68. import com.vaadin.shared.communication.ClientRpc;
  69. import com.vaadin.shared.communication.ServerRpc;
  70. import com.vaadin.shared.ui.Connect;
  71. import com.vaadin.shared.ui.Connect.LoadStyle;
  72. import com.vaadin.tools.CvalAddonsChecker;
  73. import com.vaadin.tools.CvalChecker;
  74. import com.vaadin.tools.CvalChecker.InvalidCvalException;
  75. public class ConnectorBundleLoaderFactory extends Generator {
  76. /**
  77. * Special SourceWriter that approximates the number of written bytes to
  78. * support splitting long methods into shorter chunks to avoid hitting the
  79. * 65535 byte limit.
  80. */
  81. private class SplittingSourceWriter implements SourceWriter {
  82. private final SourceWriter target;
  83. private final String baseName;
  84. private final int splitSize;
  85. private final List<String> methodNames;
  86. // Seems to be undercounted by about 15%
  87. private int approximateChars = 0;
  88. private int wrapCount = 0;
  89. public SplittingSourceWriter(SourceWriter target, String baseName,
  90. int splitSize) {
  91. this.target = target;
  92. this.baseName = baseName;
  93. this.splitSize = splitSize;
  94. methodNames = new ArrayList<String>();
  95. methodNames.add(baseName);
  96. }
  97. @Override
  98. public void beginJavaDocComment() {
  99. target.beginJavaDocComment();
  100. addChars(10);
  101. }
  102. private void addChars(int i) {
  103. approximateChars += i;
  104. }
  105. private void addChars(String s) {
  106. addChars(s.length());
  107. }
  108. private void addChars(String s, Object[] args) {
  109. addChars(String.format(s, args));
  110. }
  111. @Override
  112. public void commit(TreeLogger logger) {
  113. target.commit(logger);
  114. }
  115. @Override
  116. public void endJavaDocComment() {
  117. target.endJavaDocComment();
  118. addChars(10);
  119. }
  120. @Override
  121. public void indent() {
  122. target.indent();
  123. addChars(10);
  124. }
  125. @Override
  126. public void indentln(String s) {
  127. target.indentln(s);
  128. addChars(s);
  129. }
  130. @Override
  131. public void indentln(String s, Object... args) {
  132. target.indentln(s, args);
  133. addChars(s, args);
  134. }
  135. @Override
  136. public void outdent() {
  137. target.outdent();
  138. }
  139. @Override
  140. public void print(String s) {
  141. target.print(s);
  142. addChars(s);
  143. }
  144. @Override
  145. public void print(String s, Object... args) {
  146. target.print(s, args);
  147. addChars(s, args);
  148. }
  149. @Override
  150. public void println() {
  151. target.println();
  152. addChars(5);
  153. }
  154. @Override
  155. public void println(String s) {
  156. target.println(s);
  157. addChars(s);
  158. }
  159. @Override
  160. public void println(String s, Object... args) {
  161. target.println(s, args);
  162. addChars(s, args);
  163. }
  164. public void splitIfNeeded() {
  165. splitIfNeeded(false, null);
  166. }
  167. public void splitIfNeeded(boolean isNative, String params) {
  168. if (approximateChars > splitSize) {
  169. String newMethod = baseName + wrapCount++;
  170. String args = params == null ? "" : params;
  171. if (isNative) {
  172. outdent();
  173. println("}-*/;");
  174. // To support fields of type long (#13692)
  175. println("@com.google.gwt.core.client.UnsafeNativeLong");
  176. println("private native void %s(%s) /*-{", newMethod, args);
  177. } else {
  178. println("%s();", newMethod);
  179. outdent();
  180. println("}");
  181. println("private void %s(%s) {", newMethod, args);
  182. }
  183. methodNames.add(newMethod);
  184. indent();
  185. approximateChars = 0;
  186. }
  187. }
  188. public List<String> getMethodNames() {
  189. return Collections.unmodifiableList(methodNames);
  190. }
  191. }
  192. private CvalAddonsChecker cvalChecker = new CvalAddonsChecker();
  193. @Override
  194. public String generate(TreeLogger logger, GeneratorContext context,
  195. String typeName) throws UnableToCompleteException {
  196. TypeOracle typeOracle = context.getTypeOracle();
  197. try {
  198. JClassType classType = typeOracle.getType(typeName);
  199. String packageName = classType.getPackage().getName();
  200. String className = classType.getSimpleSourceName() + "Impl";
  201. generateClass(logger, context, packageName, className, typeName);
  202. return packageName + "." + className;
  203. } catch (UnableToCompleteException e) {
  204. // Just rethrow
  205. throw e;
  206. } catch (Exception e) {
  207. logger.log(Type.ERROR, getClass() + " failed", e);
  208. throw new UnableToCompleteException();
  209. }
  210. }
  211. private void generateClass(TreeLogger logger, GeneratorContext context,
  212. String packageName, String className, String requestedType)
  213. throws Exception {
  214. PrintWriter printWriter = context.tryCreate(logger, packageName,
  215. className);
  216. if (printWriter == null) {
  217. return;
  218. }
  219. List<CValUiInfo> cvalInfos = null;
  220. try {
  221. if (cvalChecker != null) {
  222. cvalInfos = cvalChecker.run();
  223. // Don't run twice
  224. cvalChecker = null;
  225. }
  226. } catch (InvalidCvalException e) {
  227. System.err.println("\n\n\n\n" + CvalChecker.LINE);
  228. for (String line : e.getMessage().split("\n")) {
  229. System.err.println(line);
  230. }
  231. System.err.println(CvalChecker.LINE + "\n\n\n\n");
  232. System.exit(1);
  233. throw new UnableToCompleteException();
  234. }
  235. List<ConnectorBundle> bundles = buildBundles(logger,
  236. context.getTypeOracle());
  237. ClassSourceFileComposerFactory composer = new ClassSourceFileComposerFactory(
  238. packageName, className);
  239. composer.setSuperclass(requestedType);
  240. SourceWriter w = composer.createSourceWriter(context, printWriter);
  241. w.println("public void init() {");
  242. w.indent();
  243. for (ConnectorBundle bundle : bundles) {
  244. detectBadProperties(bundle, logger);
  245. String name = bundle.getName();
  246. boolean isEager = name
  247. .equals(ConnectorBundleLoader.EAGER_BUNDLE_NAME);
  248. w.print("addAsyncBlockLoader(new AsyncBundleLoader(\"");
  249. w.print(escape(name));
  250. w.print("\", ");
  251. w.print("new String[] {");
  252. for (Entry<JClassType, Set<String>> entry : bundle.getIdentifiers()
  253. .entrySet()) {
  254. Set<String> identifiers = entry.getValue();
  255. for (String id : identifiers) {
  256. w.print("\"");
  257. w.print(escape(id));
  258. w.print("\",");
  259. }
  260. }
  261. w.println("}) {");
  262. w.indent();
  263. w.print("protected void load(final ");
  264. w.print(TypeDataStore.class.getName());
  265. w.println(" store) {");
  266. w.indent();
  267. if (!isEager) {
  268. w.print(GWT.class.getName());
  269. w.print(".runAsync(");
  270. }
  271. w.println("new %s() {", RunAsyncCallback.class.getName());
  272. w.indent();
  273. w.println("public void onSuccess() {");
  274. w.indent();
  275. w.println("load();");
  276. w.println("%s.get().setLoaded(getName());",
  277. ConnectorBundleLoader.class.getName());
  278. // Close onSuccess method
  279. w.outdent();
  280. w.println("}");
  281. w.println("private void load() {");
  282. w.indent();
  283. String loadNativeJsBundle = "loadJsBundle";
  284. printBundleData(logger, w, bundle, loadNativeJsBundle);
  285. // Close load method
  286. w.outdent();
  287. w.println("}");
  288. // Separate method for loading native JS stuff (e.g. callbacks)
  289. String loadNativeJsMethodName = "loadNativeJs";
  290. // To support fields of type long (#13692)
  291. w.println("@com.google.gwt.core.client.UnsafeNativeLong");
  292. w.println("private native void %s(%s store) /*-{",
  293. loadNativeJsMethodName, TypeDataStore.class.getName());
  294. w.indent();
  295. List<String> jsMethodNames = printJsBundleData(logger, w, bundle,
  296. loadNativeJsMethodName);
  297. w.outdent();
  298. w.println("}-*/;");
  299. // Call all generated native method inside one Java method to avoid
  300. // refercences inside native methods to each other
  301. w.println("private void %s(%s store) {", loadNativeJsBundle,
  302. TypeDataStore.class.getName());
  303. w.indent();
  304. printLoadJsBundleData(w, loadNativeJsBundle, jsMethodNames);
  305. w.outdent();
  306. w.println("}");
  307. // onFailure method declaration starts
  308. w.println("public void onFailure(Throwable reason) {");
  309. w.indent();
  310. w.println("%s.get().setLoadFailure(getName(), reason);",
  311. ConnectorBundleLoader.class.getName());
  312. w.outdent();
  313. w.println("}");
  314. // Close new RunAsyncCallback() {}
  315. w.outdent();
  316. w.print("}");
  317. if (isEager) {
  318. w.println(".onSuccess();");
  319. } else {
  320. w.println(");");
  321. }
  322. // Close load method
  323. w.outdent();
  324. w.println("}");
  325. // Close add(new ...
  326. w.outdent();
  327. w.println("});");
  328. }
  329. if (cvalInfos != null && !cvalInfos.isEmpty()) {
  330. w.println("{");
  331. for (CValUiInfo c : cvalInfos) {
  332. if ("evaluation".equals(c.type)) {
  333. w.println("cvals.add(new CValUiInfo(\"" + c.product
  334. + "\", \"" + c.version + "\", \"" + c.widgetset
  335. + "\", null));");
  336. }
  337. }
  338. w.println("}");
  339. }
  340. w.outdent();
  341. w.println("}");
  342. w.commit(logger);
  343. }
  344. private void printLoadJsBundleData(SourceWriter w, String methodName,
  345. List<String> methods) {
  346. SplittingSourceWriter writer = new SplittingSourceWriter(w, methodName,
  347. 30000);
  348. for (String method : methods) {
  349. writer.println("%s(store);", method);
  350. writer.splitIfNeeded();
  351. }
  352. }
  353. private void detectBadProperties(ConnectorBundle bundle, TreeLogger logger)
  354. throws UnableToCompleteException {
  355. Map<JClassType, Set<String>> definedProperties = new HashMap<JClassType, Set<String>>();
  356. for (Property property : bundle.getNeedsProperty()) {
  357. JClassType beanType = property.getBeanType();
  358. Set<String> usedPropertyNames = definedProperties.get(beanType);
  359. if (usedPropertyNames == null) {
  360. usedPropertyNames = new HashSet<String>();
  361. definedProperties.put(beanType, usedPropertyNames);
  362. }
  363. String name = property.getName();
  364. if (!usedPropertyNames.add(name)) {
  365. logger.log(Type.ERROR, beanType.getQualifiedSourceName()
  366. + " has multiple properties with the name " + name
  367. + ". This can happen if there are multiple "
  368. + "setters with identical names ignoring case.");
  369. throw new UnableToCompleteException();
  370. }
  371. if (!property.hasAccessorMethods()) {
  372. logger.log(Type.ERROR, beanType.getQualifiedSourceName()
  373. + " has the property '" + name
  374. + "' without getter defined.");
  375. throw new UnableToCompleteException();
  376. }
  377. }
  378. }
  379. private List<String> printJsBundleData(TreeLogger logger, SourceWriter w,
  380. ConnectorBundle bundle, String methodName) {
  381. SplittingSourceWriter writer = new SplittingSourceWriter(w, methodName,
  382. 30000);
  383. Set<Property> needsProperty = bundle.getNeedsProperty();
  384. for (Property property : needsProperty) {
  385. writer.println("var data = {");
  386. writer.indent();
  387. writer.println("setter: function(bean, value) {");
  388. writer.indent();
  389. property.writeSetterBody(logger, writer, "bean", "value");
  390. writer.outdent();
  391. writer.println("},");
  392. writer.println("getter: function(bean) {");
  393. writer.indent();
  394. property.writeGetterBody(logger, writer, "bean");
  395. writer.outdent();
  396. writer.println("}");
  397. writer.outdent();
  398. writer.println("};");
  399. // Method declaration
  400. writer.print(
  401. "store.@%s::setPropertyData(Ljava/lang/Class;Ljava/lang/String;Lcom/google/gwt/core/client/JavaScriptObject;)",
  402. TypeDataStore.class.getName());
  403. writer.println("(@%s::class, '%s', data);", property.getBeanType()
  404. .getQualifiedSourceName(), property.getName());
  405. writer.println();
  406. writer.splitIfNeeded(true,
  407. String.format("%s store", TypeDataStore.class.getName()));
  408. }
  409. return writer.getMethodNames();
  410. }
  411. private void printBundleData(TreeLogger logger, SourceWriter sourceWriter,
  412. ConnectorBundle bundle, String loadNativeJsMethodName)
  413. throws UnableToCompleteException {
  414. // Split into new load method when reaching approximately 30000 bytes
  415. SplittingSourceWriter w = new SplittingSourceWriter(sourceWriter,
  416. "load", 30000);
  417. writeSuperClasses(w, bundle);
  418. writeIdentifiers(w, bundle);
  419. writeGwtConstructors(w, bundle);
  420. writeReturnTypes(w, bundle);
  421. writeInvokers(logger, w, bundle);
  422. writeParamTypes(w, bundle);
  423. writeProxys(w, bundle);
  424. writeDelayedInfo(w, bundle);
  425. w.println("%s(store);", loadNativeJsMethodName);
  426. // Must use Java code to generate Type data (because of Type[]), doing
  427. // this after the JS property data has been initialized
  428. writePropertyTypes(logger, w, bundle);
  429. writeSerializers(logger, w, bundle);
  430. writeDelegateToWidget(logger, w, bundle);
  431. writeOnStateChangeHandlers(logger, w, bundle);
  432. }
  433. private void writeOnStateChangeHandlers(TreeLogger logger,
  434. SplittingSourceWriter w, ConnectorBundle bundle)
  435. throws UnableToCompleteException {
  436. Map<JClassType, Set<JMethod>> needsOnStateChangeHandler = bundle
  437. .getNeedsOnStateChangeHandler();
  438. for (Entry<JClassType, Set<JMethod>> entry : needsOnStateChangeHandler
  439. .entrySet()) {
  440. JClassType connector = entry.getKey();
  441. TreeLogger typeLogger = logger.branch(
  442. Type.DEBUG,
  443. "Generating @OnStateChange support for "
  444. + connector.getName());
  445. // Build map to speed up error checking
  446. HashMap<String, Property> stateProperties = new HashMap<String, Property>();
  447. JClassType stateType = ConnectorBundle
  448. .findInheritedMethod(connector, "getState").getReturnType()
  449. .isClassOrInterface();
  450. for (Property property : bundle.getProperties(stateType)) {
  451. stateProperties.put(property.getName(), property);
  452. }
  453. for (JMethod method : entry.getValue()) {
  454. TreeLogger methodLogger = typeLogger.branch(Type.DEBUG,
  455. "Processing method " + method.getName());
  456. if (method.isPublic() || method.isProtected()) {
  457. methodLogger
  458. .log(Type.ERROR,
  459. "@OnStateChange is only supported for methods with private or default visibility.");
  460. throw new UnableToCompleteException();
  461. }
  462. OnStateChange onStateChange = method
  463. .getAnnotation(OnStateChange.class);
  464. String[] properties = onStateChange.value();
  465. if (properties.length == 0) {
  466. methodLogger.log(Type.ERROR,
  467. "There are no properties to listen to");
  468. throw new UnableToCompleteException();
  469. }
  470. // Verify that all properties do exist
  471. for (String propertyName : properties) {
  472. if (!stateProperties.containsKey(propertyName)) {
  473. methodLogger.log(Type.ERROR,
  474. "State class has no property named "
  475. + propertyName);
  476. throw new UnableToCompleteException();
  477. }
  478. }
  479. if (method.getParameters().length != 0) {
  480. methodLogger.log(Type.ERROR,
  481. "Method should accept zero parameters");
  482. throw new UnableToCompleteException();
  483. }
  484. // new OnStateChangeMethod(Class declaringClass, String
  485. // methodName, String[], properties)
  486. w.print("store.addOnStateChangeMethod(%s, new %s(",
  487. getClassLiteralString(connector),
  488. OnStateChangeMethod.class.getName());
  489. if (!connector.equals(method.getEnclosingType())) {
  490. w.print("%s, ",
  491. getClassLiteralString(method.getEnclosingType()));
  492. }
  493. w.print("\"%s\", ", method.getName());
  494. w.print("new String[] {");
  495. for (String propertyName : properties) {
  496. w.print("\"%s\", ", propertyName);
  497. }
  498. w.print("}");
  499. w.println("));");
  500. w.splitIfNeeded();
  501. }
  502. }
  503. }
  504. private void writeSuperClasses(SplittingSourceWriter w,
  505. ConnectorBundle bundle) {
  506. List<JClassType> needsSuperclass = new ArrayList<JClassType>(
  507. bundle.getNeedsSuperclass());
  508. // Emit in hierarchy order to ensure superclass is defined when
  509. // referenced
  510. Collections.sort(needsSuperclass, new Comparator<JClassType>() {
  511. @Override
  512. public int compare(JClassType type1, JClassType type2) {
  513. int depthDiff = getDepth(type1) - getDepth(type2);
  514. if (depthDiff != 0) {
  515. return depthDiff;
  516. } else {
  517. // Just something to get a stable compare
  518. return type1.getName().compareTo(type2.getName());
  519. }
  520. }
  521. private int getDepth(JClassType type) {
  522. int depth = 0;
  523. while (type != null) {
  524. depth++;
  525. type = type.getSuperclass();
  526. }
  527. return depth;
  528. }
  529. });
  530. for (JClassType jClassType : needsSuperclass) {
  531. JClassType superclass = jClassType.getSuperclass();
  532. while (superclass != null && !superclass.isPublic()) {
  533. superclass = superclass.getSuperclass();
  534. }
  535. String classLiteralString;
  536. if (superclass == null) {
  537. classLiteralString = "null";
  538. } else {
  539. classLiteralString = getClassLiteralString(superclass);
  540. }
  541. w.println("store.setSuperClass(%s, %s);",
  542. getClassLiteralString(jClassType), classLiteralString);
  543. }
  544. }
  545. private void writeDelegateToWidget(TreeLogger logger,
  546. SplittingSourceWriter w, ConnectorBundle bundle) {
  547. Set<Property> needsDelegateToWidget = bundle.getNeedsDelegateToWidget();
  548. for (Property property : needsDelegateToWidget) {
  549. w.println("store.setDelegateToWidget(%s, \"%s\", \"%s\");",
  550. getClassLiteralString(property.getBeanType()),
  551. property.getName(),
  552. property.getAnnotation(DelegateToWidget.class).value());
  553. w.splitIfNeeded();
  554. }
  555. }
  556. private void writeSerializers(TreeLogger logger, SplittingSourceWriter w,
  557. ConnectorBundle bundle) throws UnableToCompleteException {
  558. Map<JType, GeneratedSerializer> serializers = bundle.getSerializers();
  559. for (Entry<JType, GeneratedSerializer> entry : serializers.entrySet()) {
  560. JType type = entry.getKey();
  561. GeneratedSerializer serializer = entry.getValue();
  562. w.print("store.setSerializerFactory(");
  563. writeClassLiteral(w, type);
  564. w.print(", ");
  565. w.println("new Invoker() {");
  566. w.indent();
  567. w.println("public Object invoke(Object target, Object[] params) {");
  568. w.indent();
  569. serializer.writeSerializerInstantiator(logger, w);
  570. w.outdent();
  571. w.println("}");
  572. w.outdent();
  573. w.print("}");
  574. w.println(");");
  575. w.splitIfNeeded();
  576. }
  577. }
  578. private void writePropertyTypes(TreeLogger logger, SplittingSourceWriter w,
  579. ConnectorBundle bundle) {
  580. Set<Property> properties = bundle.getNeedsProperty();
  581. for (Property property : properties) {
  582. w.print("store.setPropertyType(");
  583. writeClassLiteral(w, property.getBeanType());
  584. w.print(", \"");
  585. w.print(escape(property.getName()));
  586. w.print("\", ");
  587. writeTypeCreator(w, property.getPropertyType());
  588. w.println(");");
  589. w.splitIfNeeded();
  590. }
  591. }
  592. private void writeDelayedInfo(SplittingSourceWriter w,
  593. ConnectorBundle bundle) {
  594. Map<JClassType, Set<JMethod>> needsDelayedInfo = bundle
  595. .getNeedsDelayedInfo();
  596. Set<Entry<JClassType, Set<JMethod>>> entrySet = needsDelayedInfo
  597. .entrySet();
  598. for (Entry<JClassType, Set<JMethod>> entry : entrySet) {
  599. JClassType type = entry.getKey();
  600. Set<JMethod> methods = entry.getValue();
  601. for (JMethod method : methods) {
  602. Delayed annotation = method.getAnnotation(Delayed.class);
  603. if (annotation != null) {
  604. w.print("store.setDelayed(");
  605. writeClassLiteral(w, type);
  606. w.print(", \"");
  607. w.print(escape(method.getName()));
  608. w.println("\");");
  609. if (annotation.lastOnly()) {
  610. w.print("store.setLastOnly(");
  611. writeClassLiteral(w, type);
  612. w.print(", \"");
  613. w.print(escape(method.getName()));
  614. w.println("\");");
  615. }
  616. w.splitIfNeeded();
  617. }
  618. }
  619. }
  620. }
  621. private void writeProxys(SplittingSourceWriter w, ConnectorBundle bundle) {
  622. Set<JClassType> needsProxySupport = bundle.getNeedsProxySupport();
  623. for (JClassType type : needsProxySupport) {
  624. w.print("store.setProxyHandler(");
  625. writeClassLiteral(w, type);
  626. w.print(", new ");
  627. w.print(ProxyHandler.class.getCanonicalName());
  628. w.println("() {");
  629. w.indent();
  630. w.println("public Object createProxy(final "
  631. + InvokationHandler.class.getName() + " handler) {");
  632. w.indent();
  633. w.print("return new ");
  634. w.print(type.getQualifiedSourceName());
  635. w.println("() {");
  636. w.indent();
  637. JMethod[] methods = type.getOverridableMethods();
  638. for (JMethod method : methods) {
  639. if (method.isAbstract()) {
  640. w.print("public ");
  641. w.print(method.getReturnType().getQualifiedSourceName());
  642. w.print(" ");
  643. w.print(method.getName());
  644. w.print("(");
  645. JType[] types = method.getParameterTypes();
  646. for (int i = 0; i < types.length; i++) {
  647. if (i != 0) {
  648. w.print(", ");
  649. }
  650. w.print(types[i].getQualifiedSourceName());
  651. w.print(" p");
  652. w.print(Integer.toString(i));
  653. }
  654. w.println(") {");
  655. w.indent();
  656. if (!method.getReturnType().getQualifiedSourceName()
  657. .equals("void")) {
  658. w.print("return ");
  659. }
  660. w.print("handler.invoke(this, ");
  661. w.print(TypeData.class.getCanonicalName());
  662. w.print(".getType(");
  663. writeClassLiteral(w, type);
  664. w.print(").getMethod(\"");
  665. w.print(escape(method.getName()));
  666. w.print("\"), new Object [] {");
  667. for (int i = 0; i < types.length; i++) {
  668. w.print("p" + i + ", ");
  669. }
  670. w.println("});");
  671. w.outdent();
  672. w.println("}");
  673. }
  674. }
  675. w.outdent();
  676. w.println("};");
  677. w.outdent();
  678. w.println("}");
  679. w.outdent();
  680. w.println("});");
  681. w.splitIfNeeded();
  682. }
  683. }
  684. private void writeParamTypes(SplittingSourceWriter w, ConnectorBundle bundle) {
  685. Map<JClassType, Set<JMethod>> needsParamTypes = bundle
  686. .getNeedsParamTypes();
  687. for (Entry<JClassType, Set<JMethod>> entry : needsParamTypes.entrySet()) {
  688. JClassType type = entry.getKey();
  689. Set<JMethod> methods = entry.getValue();
  690. for (JMethod method : methods) {
  691. w.print("store.setParamTypes(");
  692. writeClassLiteral(w, type);
  693. w.print(", \"");
  694. w.print(escape(method.getName()));
  695. w.print("\", new Type[] {");
  696. for (JType parameter : method.getParameterTypes()) {
  697. ConnectorBundleLoaderFactory.writeTypeCreator(w, parameter);
  698. w.print(", ");
  699. }
  700. w.println("});");
  701. w.splitIfNeeded();
  702. }
  703. }
  704. }
  705. private void writeInvokers(TreeLogger logger, SplittingSourceWriter w,
  706. ConnectorBundle bundle) throws UnableToCompleteException {
  707. Map<JClassType, Set<JMethod>> needsInvoker = bundle.getNeedsInvoker();
  708. for (Entry<JClassType, Set<JMethod>> entry : needsInvoker.entrySet()) {
  709. JClassType type = entry.getKey();
  710. TreeLogger typeLogger = logger.branch(Type.DEBUG,
  711. "Creating invokers for " + type);
  712. Set<JMethod> methods = entry.getValue();
  713. for (JMethod method : methods) {
  714. w.print("store.setInvoker(");
  715. writeClassLiteral(w, type);
  716. w.print(", \"");
  717. w.print(escape(method.getName()));
  718. w.print("\",");
  719. if (method.isPublic()) {
  720. typeLogger.log(Type.DEBUG, "Invoking " + method.getName()
  721. + " using java");
  722. writeJavaInvoker(w, type, method);
  723. } else {
  724. TreeLogger methodLogger = typeLogger.branch(Type.DEBUG,
  725. "Invoking " + method.getName() + " using jsni");
  726. // Must use JSNI to access non-public methods
  727. writeJsniInvoker(methodLogger, w, type, method);
  728. }
  729. w.println(");");
  730. w.splitIfNeeded();
  731. }
  732. }
  733. }
  734. private void writeJsniInvoker(TreeLogger logger, SplittingSourceWriter w,
  735. JClassType type, JMethod method) throws UnableToCompleteException {
  736. w.println("new JsniInvoker() {");
  737. w.indent();
  738. w.println(
  739. "protected native Object jsniInvoke(Object target, %s<Object> params) /*-{ ",
  740. JsArrayObject.class.getName());
  741. w.indent();
  742. JType returnType = method.getReturnType();
  743. boolean hasReturnType = !"void".equals(returnType
  744. .getQualifiedSourceName());
  745. // Note that void is also a primitive type
  746. boolean hasPrimitiveReturnType = hasReturnType
  747. && returnType.isPrimitive() != null;
  748. if (hasReturnType) {
  749. w.print("return ");
  750. if (hasPrimitiveReturnType) {
  751. // Integer.valueOf(expression);
  752. w.print("@%s::valueOf(%s)(", returnType.isPrimitive()
  753. .getQualifiedBoxedSourceName(), returnType
  754. .getJNISignature());
  755. // Implementation tested briefly, but I don't dare leave it
  756. // enabled since we are not using it in the framework and we
  757. // have not tests for it.
  758. logger.log(Type.ERROR,
  759. "JSNI invocation is not yet supported for methods with "
  760. + "primitive return types. Change your method "
  761. + "to public to be able to use conventional"
  762. + " Java invoking instead.");
  763. throw new UnableToCompleteException();
  764. }
  765. }
  766. JType[] parameterTypes = method.getParameterTypes();
  767. w.print("target.@%s::" + method.getName() + "(*)(", method
  768. .getEnclosingType().getQualifiedSourceName());
  769. for (int i = 0; i < parameterTypes.length; i++) {
  770. if (i != 0) {
  771. w.print(", ");
  772. }
  773. w.print("params[" + i + "]");
  774. JPrimitiveType primitive = parameterTypes[i].isPrimitive();
  775. if (primitive != null) {
  776. // param.intValue();
  777. w.print(".@%s::%sValue()()",
  778. primitive.getQualifiedBoxedSourceName(),
  779. primitive.getQualifiedSourceName());
  780. }
  781. }
  782. if (hasPrimitiveReturnType) {
  783. assert hasReturnType;
  784. w.print(")");
  785. }
  786. w.println(");");
  787. if (!hasReturnType) {
  788. w.println("return null;");
  789. }
  790. w.outdent();
  791. w.println("}-*/;");
  792. w.outdent();
  793. w.print("}");
  794. }
  795. private void writeJavaInvoker(SplittingSourceWriter w, JClassType type,
  796. JMethod method) {
  797. w.println("new Invoker() {");
  798. w.indent();
  799. w.println("public Object invoke(Object target, Object[] params) {");
  800. w.indent();
  801. JType returnType = method.getReturnType();
  802. boolean hasReturnType = !"void".equals(returnType
  803. .getQualifiedSourceName());
  804. if (hasReturnType) {
  805. w.print("return ");
  806. }
  807. JType[] parameterTypes = method.getParameterTypes();
  808. w.print("((" + type.getQualifiedSourceName() + ") target)."
  809. + method.getName() + "(");
  810. for (int i = 0; i < parameterTypes.length; i++) {
  811. JType parameterType = parameterTypes[i];
  812. if (i != 0) {
  813. w.print(", ");
  814. }
  815. String parameterTypeName = getBoxedTypeName(parameterType);
  816. w.print("(" + parameterTypeName + ") params[" + i + "]");
  817. }
  818. w.println(");");
  819. if (!hasReturnType) {
  820. w.println("return null;");
  821. }
  822. w.outdent();
  823. w.println("}");
  824. w.outdent();
  825. w.print("}");
  826. }
  827. private void writeReturnTypes(SplittingSourceWriter w,
  828. ConnectorBundle bundle) {
  829. Map<JClassType, Set<JMethod>> methodReturnTypes = bundle
  830. .getMethodReturnTypes();
  831. for (Entry<JClassType, Set<JMethod>> entry : methodReturnTypes
  832. .entrySet()) {
  833. JClassType type = entry.getKey();
  834. Set<JMethod> methods = entry.getValue();
  835. for (JMethod method : methods) {
  836. // setReturnType(Class<?> type, String methodName, Type
  837. // returnType)
  838. w.print("store.setReturnType(");
  839. writeClassLiteral(w, type);
  840. w.print(", \"");
  841. w.print(escape(method.getName()));
  842. w.print("\", ");
  843. writeTypeCreator(w, method.getReturnType());
  844. w.println(");");
  845. w.splitIfNeeded();
  846. }
  847. }
  848. }
  849. private void writeGwtConstructors(SplittingSourceWriter w,
  850. ConnectorBundle bundle) {
  851. Set<JClassType> constructors = bundle.getGwtConstructors();
  852. for (JClassType type : constructors) {
  853. w.print("store.setConstructor(");
  854. writeClassLiteral(w, type);
  855. w.println(", new Invoker() {");
  856. w.indent();
  857. w.println("public Object invoke(Object target, Object[] params) {");
  858. w.indent();
  859. w.print("return ");
  860. w.print(GWT.class.getName());
  861. w.print(".create(");
  862. writeClassLiteral(w, type);
  863. w.println(");");
  864. w.outdent();
  865. w.println("}");
  866. w.outdent();
  867. w.println("});");
  868. w.splitIfNeeded();
  869. }
  870. }
  871. public static void writeClassLiteral(SourceWriter w, JType type) {
  872. w.print(getClassLiteralString(type));
  873. }
  874. public static String getClassLiteralString(JType type) {
  875. return type.getQualifiedSourceName() + ".class";
  876. }
  877. private void writeIdentifiers(SplittingSourceWriter w,
  878. ConnectorBundle bundle) {
  879. Map<JClassType, Set<String>> identifiers = bundle.getIdentifiers();
  880. for (Entry<JClassType, Set<String>> entry : identifiers.entrySet()) {
  881. Set<String> ids = entry.getValue();
  882. JClassType type = entry.getKey();
  883. for (String id : ids) {
  884. w.print("store.setClass(\"");
  885. w.print(escape(id));
  886. w.print("\", ");
  887. writeClassLiteral(w, type);
  888. w.println(");");
  889. w.splitIfNeeded();
  890. }
  891. }
  892. }
  893. private List<ConnectorBundle> buildBundles(TreeLogger logger,
  894. TypeOracle typeOracle) throws NotFoundException,
  895. UnableToCompleteException {
  896. Map<LoadStyle, Collection<JClassType>> connectorsByLoadStyle = new HashMap<LoadStyle, Collection<JClassType>>();
  897. for (LoadStyle loadStyle : LoadStyle.values()) {
  898. connectorsByLoadStyle.put(loadStyle, new ArrayList<JClassType>());
  899. }
  900. // Find all types with a valid mapping
  901. Collection<JClassType> selectedTypes = getConnectorsForWidgetset(
  902. logger, typeOracle);
  903. // Group by load style
  904. for (JClassType connectorSubtype : selectedTypes) {
  905. LoadStyle loadStyle = getLoadStyle(connectorSubtype);
  906. if (loadStyle != null) {
  907. connectorsByLoadStyle.get(loadStyle).add(connectorSubtype);
  908. }
  909. }
  910. List<ConnectorBundle> bundles = new ArrayList<ConnectorBundle>();
  911. Collection<TypeVisitor> visitors = getVisitors(typeOracle);
  912. ConnectorBundle eagerBundle = new ConnectorBundle(
  913. ConnectorBundleLoader.EAGER_BUNDLE_NAME, visitors, typeOracle);
  914. TreeLogger eagerLogger = logger.branch(Type.TRACE,
  915. "Populating eager bundle");
  916. // Eager connectors and all RPC interfaces are loaded by default
  917. eagerBundle.processTypes(eagerLogger,
  918. connectorsByLoadStyle.get(LoadStyle.EAGER));
  919. eagerBundle.processType(eagerLogger, typeOracle
  920. .findType(UnknownComponentConnector.class.getCanonicalName()));
  921. eagerBundle.processSubTypes(eagerLogger,
  922. typeOracle.getType(ClientRpc.class.getName()));
  923. eagerBundle.processSubTypes(eagerLogger,
  924. typeOracle.getType(ServerRpc.class.getName()));
  925. bundles.add(eagerBundle);
  926. ConnectorBundle deferredBundle = new ConnectorBundle(
  927. ConnectorBundleLoader.DEFERRED_BUNDLE_NAME, eagerBundle);
  928. TreeLogger deferredLogger = logger.branch(Type.TRACE,
  929. "Populating deferred bundle");
  930. deferredBundle.processTypes(deferredLogger,
  931. connectorsByLoadStyle.get(LoadStyle.DEFERRED));
  932. bundles.add(deferredBundle);
  933. Collection<JClassType> lazy = connectorsByLoadStyle.get(LoadStyle.LAZY);
  934. for (JClassType type : lazy) {
  935. ConnectorBundle bundle = new ConnectorBundle(type.getName(),
  936. eagerBundle);
  937. TreeLogger subLogger = logger.branch(Type.TRACE, "Populating "
  938. + type.getName() + " bundle");
  939. bundle.processType(subLogger, type);
  940. bundles.add(bundle);
  941. }
  942. return bundles;
  943. }
  944. /**
  945. * Returns the connector types that should be included in the widgetset.
  946. * This method can be overridden to create a widgetset only containing
  947. * selected connectors.
  948. * <p>
  949. * The default implementation finds all type implementing
  950. * {@link ServerConnector} that have a @{@link Connect} annotation. It also
  951. * checks that multiple connectors aren't connected to the same server-side
  952. * class.
  953. *
  954. * @param logger
  955. * the logger to which information can be logged
  956. * @param typeOracle
  957. * the type oracle that can be used for finding types
  958. * @return a collection of all the connector types that should be included
  959. * in the widgetset
  960. * @throws UnableToCompleteException
  961. * if the operation fails
  962. */
  963. protected Collection<JClassType> getConnectorsForWidgetset(
  964. TreeLogger logger, TypeOracle typeOracle)
  965. throws UnableToCompleteException {
  966. JClassType serverConnectorType;
  967. try {
  968. serverConnectorType = typeOracle.getType(ServerConnector.class
  969. .getName());
  970. } catch (NotFoundException e) {
  971. logger.log(Type.ERROR,
  972. "Can't find " + ServerConnector.class.getName());
  973. throw new UnableToCompleteException();
  974. }
  975. JClassType[] types = serverConnectorType.getSubtypes();
  976. Map<String, JClassType> mappings = new HashMap<String, JClassType>();
  977. // Keep track of what has happened to avoid logging intermediate state
  978. Map<JClassType, List<JClassType>> replaced = new HashMap<JClassType, List<JClassType>>();
  979. for (JClassType type : types) {
  980. Connect connectAnnotation = type.getAnnotation(Connect.class);
  981. if (connectAnnotation == null) {
  982. continue;
  983. }
  984. String identifier = connectAnnotation.value().getCanonicalName();
  985. JClassType previousMapping = mappings.put(identifier, type);
  986. if (previousMapping != null) {
  987. // There are multiple mappings, pick the subclass
  988. JClassType subclass;
  989. JClassType superclass;
  990. if (previousMapping.isAssignableFrom(type)) {
  991. subclass = type;
  992. superclass = previousMapping;
  993. } else if (type.isAssignableFrom(previousMapping)) {
  994. subclass = previousMapping;
  995. superclass = type;
  996. } else {
  997. // Neither inherits from the other - this is a conflict
  998. logger.log(
  999. Type.ERROR,
  1000. "Conflicting @Connect mappings detected for "
  1001. + identifier
  1002. + ": "
  1003. + type.getQualifiedSourceName()
  1004. + " and "
  1005. + previousMapping.getQualifiedSourceName()
  1006. + ". There can only be multiple @Connect mappings for the same server-side type if one is the subclass of the other.");
  1007. throw new UnableToCompleteException();
  1008. }
  1009. mappings.put(identifier, subclass);
  1010. // Inherit any previous replacements
  1011. List<JClassType> previousReplacements = replaced
  1012. .remove(superclass);
  1013. if (previousReplacements == null) {
  1014. previousReplacements = new ArrayList<JClassType>();
  1015. }
  1016. previousReplacements.add(superclass);
  1017. replaced.put(subclass, previousReplacements);
  1018. }
  1019. }
  1020. // Log the final set of replacements
  1021. for (Entry<JClassType, List<JClassType>> entry : replaced.entrySet()) {
  1022. String msg = entry.getKey().getQualifiedSourceName() + " replaces ";
  1023. List<JClassType> list = entry.getValue();
  1024. for (int i = 0; i < list.size(); i++) {
  1025. if (i != 0) {
  1026. msg += ", ";
  1027. }
  1028. msg += list.get(i).getQualifiedSourceName();
  1029. }
  1030. logger.log(Type.INFO, msg);
  1031. }
  1032. // Return the types of the final mapping
  1033. return mappings.values();
  1034. }
  1035. private Collection<TypeVisitor> getVisitors(TypeOracle oracle)
  1036. throws NotFoundException {
  1037. List<TypeVisitor> visitors = Arrays.<TypeVisitor> asList(
  1038. new ConnectorInitVisitor(), new StateInitVisitor(),
  1039. new WidgetInitVisitor(), new ClientRpcVisitor(),
  1040. new ServerRpcVisitor(), new OnStateChangeVisitor());
  1041. for (TypeVisitor typeVisitor : visitors) {
  1042. typeVisitor.init(oracle);
  1043. }
  1044. return visitors;
  1045. }
  1046. protected LoadStyle getLoadStyle(JClassType connectorType) {
  1047. Connect annotation = connectorType.getAnnotation(Connect.class);
  1048. return annotation.loadStyle();
  1049. }
  1050. public static String getBoxedTypeName(JType type) {
  1051. if (type.isPrimitive() != null) {
  1052. // Used boxed types for primitives
  1053. return type.isPrimitive().getQualifiedBoxedSourceName();
  1054. } else {
  1055. return type.getErasedType().getQualifiedSourceName();
  1056. }
  1057. }
  1058. public static void writeTypeCreator(SourceWriter sourceWriter, JType type) {
  1059. String typeName = ConnectorBundleLoaderFactory.getBoxedTypeName(type);
  1060. JParameterizedType parameterized = type.isParameterized();
  1061. if (parameterized != null) {
  1062. sourceWriter.print("new Type(\"" + typeName + "\", ");
  1063. sourceWriter.print("new Type[] {");
  1064. JClassType[] typeArgs = parameterized.getTypeArgs();
  1065. for (JClassType jClassType : typeArgs) {
  1066. writeTypeCreator(sourceWriter, jClassType);
  1067. sourceWriter.print(", ");
  1068. }
  1069. sourceWriter.print("}");
  1070. } else {
  1071. sourceWriter.print("new Type(" + typeName + ".class");
  1072. }
  1073. sourceWriter.print(")");
  1074. }
  1075. }