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.

SerializerGenerator.java 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  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.HashSet;
  20. import java.util.List;
  21. import com.google.gwt.core.client.GWT;
  22. import com.google.gwt.core.ext.Generator;
  23. import com.google.gwt.core.ext.GeneratorContext;
  24. import com.google.gwt.core.ext.TreeLogger;
  25. import com.google.gwt.core.ext.TreeLogger.Type;
  26. import com.google.gwt.core.ext.UnableToCompleteException;
  27. import com.google.gwt.core.ext.typeinfo.JArrayType;
  28. import com.google.gwt.core.ext.typeinfo.JClassType;
  29. import com.google.gwt.core.ext.typeinfo.JEnumConstant;
  30. import com.google.gwt.core.ext.typeinfo.JEnumType;
  31. import com.google.gwt.core.ext.typeinfo.JMethod;
  32. import com.google.gwt.core.ext.typeinfo.JPackage;
  33. import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
  34. import com.google.gwt.core.ext.typeinfo.JType;
  35. import com.google.gwt.core.ext.typeinfo.TypeOracleException;
  36. import com.google.gwt.json.client.JSONArray;
  37. import com.google.gwt.json.client.JSONObject;
  38. import com.google.gwt.json.client.JSONString;
  39. import com.google.gwt.json.client.JSONValue;
  40. import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
  41. import com.google.gwt.user.rebind.SourceWriter;
  42. import com.vaadin.terminal.gwt.client.ApplicationConnection;
  43. import com.vaadin.terminal.gwt.client.communication.DiffJSONSerializer;
  44. import com.vaadin.terminal.gwt.client.communication.JSONSerializer;
  45. import com.vaadin.terminal.gwt.client.communication.JsonDecoder;
  46. import com.vaadin.terminal.gwt.client.communication.JsonEncoder;
  47. import com.vaadin.terminal.gwt.client.communication.SerializerMap;
  48. /**
  49. * GWT generator for creating serializer classes for custom classes sent from
  50. * server to client.
  51. *
  52. * Only fields with a correspondingly named setter are deserialized.
  53. *
  54. * @since 7.0
  55. */
  56. public class SerializerGenerator extends Generator {
  57. private static final String SUBTYPE_SEPARATOR = "___";
  58. @Override
  59. public String generate(TreeLogger logger, GeneratorContext context,
  60. String typeName) throws UnableToCompleteException {
  61. JClassType type;
  62. try {
  63. type = (JClassType) context.getTypeOracle().parse(typeName);
  64. } catch (TypeOracleException e1) {
  65. logger.log(Type.ERROR, "Could not find type " + typeName, e1);
  66. throw new UnableToCompleteException();
  67. }
  68. String serializerClassName = getSerializerSimpleClassName(type);
  69. try {
  70. // Generate class source code
  71. generateClass(logger, context, type,
  72. getSerializerPackageName(type), serializerClassName);
  73. } catch (Exception e) {
  74. logger.log(TreeLogger.ERROR, "SerializerGenerator failed for "
  75. + type.getQualifiedSourceName(), e);
  76. throw new UnableToCompleteException();
  77. }
  78. // return the fully qualifed name of the class generated
  79. return getFullyQualifiedSerializerClassName(type);
  80. }
  81. /**
  82. * Generate source code for a VaadinSerializer implementation.
  83. *
  84. * @param logger
  85. * Logger object
  86. * @param context
  87. * Generator context
  88. * @param type
  89. * @param beanTypeName
  90. * bean type for which the serializer is to be generated
  91. * @param beanSerializerTypeName
  92. * name of the serializer class to generate
  93. * @throws UnableToCompleteException
  94. */
  95. private void generateClass(TreeLogger logger, GeneratorContext context,
  96. JClassType type, String serializerPackageName,
  97. String serializerClassName) throws UnableToCompleteException {
  98. // get print writer that receives the source code
  99. PrintWriter printWriter = null;
  100. printWriter = context.tryCreate(logger, serializerPackageName,
  101. serializerClassName);
  102. // print writer if null, source code has ALREADY been generated
  103. if (printWriter == null) {
  104. return;
  105. }
  106. boolean isEnum = (type.isEnum() != null);
  107. boolean isArray = (type.isArray() != null);
  108. String qualifiedSourceName = type.getQualifiedSourceName();
  109. logger.log(Type.DEBUG, "Processing serializable type "
  110. + qualifiedSourceName + "...");
  111. // init composer, set class properties, create source writer
  112. ClassSourceFileComposerFactory composer = null;
  113. composer = new ClassSourceFileComposerFactory(serializerPackageName,
  114. serializerClassName);
  115. composer.addImport(GWT.class.getName());
  116. composer.addImport(JSONValue.class.getName());
  117. composer.addImport(com.vaadin.terminal.gwt.client.metadata.Type.class
  118. .getName());
  119. // composer.addImport(JSONObject.class.getName());
  120. // composer.addImport(VPaintableMap.class.getName());
  121. composer.addImport(JsonDecoder.class.getName());
  122. // composer.addImport(VaadinSerializer.class.getName());
  123. if (isEnum || isArray) {
  124. composer.addImplementedInterface(JSONSerializer.class.getName()
  125. + "<" + qualifiedSourceName + ">");
  126. } else {
  127. composer.addImplementedInterface(DiffJSONSerializer.class.getName()
  128. + "<" + qualifiedSourceName + ">");
  129. }
  130. SourceWriter sourceWriter = composer.createSourceWriter(context,
  131. printWriter);
  132. sourceWriter.indent();
  133. // Serializer
  134. // public JSONValue serialize(Object value,
  135. // ApplicationConnection connection) {
  136. sourceWriter.println("public " + JSONValue.class.getName()
  137. + " serialize(" + qualifiedSourceName + " value, "
  138. + ApplicationConnection.class.getName() + " connection) {");
  139. sourceWriter.indent();
  140. // MouseEventDetails castedValue = (MouseEventDetails) value;
  141. sourceWriter.println(qualifiedSourceName + " castedValue = ("
  142. + qualifiedSourceName + ") value;");
  143. if (isEnum) {
  144. writeEnumSerializer(logger, sourceWriter, type);
  145. } else if (isArray) {
  146. writeArraySerializer(logger, sourceWriter, type.isArray());
  147. } else {
  148. writeBeanSerializer(logger, sourceWriter, type);
  149. }
  150. // }
  151. sourceWriter.outdent();
  152. sourceWriter.println("}");
  153. sourceWriter.println();
  154. // Updater
  155. // public void update(T target, Type type, JSONValue jsonValue,
  156. // ApplicationConnection connection);
  157. if (!isEnum && !isArray) {
  158. sourceWriter.println("public void update(" + qualifiedSourceName
  159. + " target, Type type, " + JSONValue.class.getName()
  160. + " jsonValue, " + ApplicationConnection.class.getName()
  161. + " connection) {");
  162. sourceWriter.indent();
  163. writeBeanDeserializer(logger, sourceWriter, type);
  164. sourceWriter.outdent();
  165. sourceWriter.println("}");
  166. }
  167. // Deserializer
  168. // T deserialize(Type type, JSONValue jsonValue, ApplicationConnection
  169. // connection);
  170. sourceWriter.println("public " + qualifiedSourceName
  171. + " deserialize(Type type, " + JSONValue.class.getName()
  172. + " jsonValue, " + ApplicationConnection.class.getName()
  173. + " connection) {");
  174. sourceWriter.indent();
  175. if (isEnum) {
  176. writeEnumDeserializer(logger, sourceWriter, type.isEnum());
  177. } else if (isArray) {
  178. writeArrayDeserializer(logger, sourceWriter, type.isArray());
  179. } else {
  180. sourceWriter.println(qualifiedSourceName + " target = GWT.create("
  181. + qualifiedSourceName + ".class);");
  182. sourceWriter
  183. .println("update(target, type, jsonValue, connection);");
  184. // return target;
  185. sourceWriter.println("return target;");
  186. }
  187. sourceWriter.outdent();
  188. sourceWriter.println("}");
  189. // End of class
  190. sourceWriter.outdent();
  191. sourceWriter.println("}");
  192. // commit generated class
  193. context.commit(logger, printWriter);
  194. logger.log(TreeLogger.INFO, "Generated Serializer class "
  195. + getFullyQualifiedSerializerClassName(type));
  196. }
  197. private void writeEnumDeserializer(TreeLogger logger,
  198. SourceWriter sourceWriter, JEnumType enumType) {
  199. sourceWriter.println("String enumIdentifier = (("
  200. + JSONString.class.getName() + ")jsonValue).stringValue();");
  201. for (JEnumConstant e : enumType.getEnumConstants()) {
  202. sourceWriter.println("if (\"" + e.getName()
  203. + "\".equals(enumIdentifier)) {");
  204. sourceWriter.indent();
  205. sourceWriter.println("return " + enumType.getQualifiedSourceName()
  206. + "." + e.getName() + ";");
  207. sourceWriter.outdent();
  208. sourceWriter.println("}");
  209. }
  210. sourceWriter.println("return null;");
  211. }
  212. private void writeArrayDeserializer(TreeLogger logger,
  213. SourceWriter sourceWriter, JArrayType type) {
  214. JType leafType = type.getLeafType();
  215. int rank = type.getRank();
  216. sourceWriter.println(JSONArray.class.getName()
  217. + " jsonArray = jsonValue.isArray();");
  218. // Type value = new Type[jsonArray.size()][][];
  219. sourceWriter.print(type.getQualifiedSourceName() + " value = new "
  220. + leafType.getQualifiedSourceName() + "[jsonArray.size()]");
  221. for (int i = 1; i < rank; i++) {
  222. sourceWriter.print("[]");
  223. }
  224. sourceWriter.println(";");
  225. sourceWriter.println("for(int i = 0 ; i < value.length; i++) {");
  226. sourceWriter.indent();
  227. JType componentType = type.getComponentType();
  228. sourceWriter.print("value[i] = ("
  229. + GeneratedRpcMethodProviderGenerator
  230. .getBoxedTypeName(componentType) + ") "
  231. + JsonDecoder.class.getName() + ".decodeValue(");
  232. GeneratedRpcMethodProviderGenerator.writeTypeCreator(sourceWriter,
  233. componentType);
  234. sourceWriter.print(", jsonArray.get(i), null, connection)");
  235. sourceWriter.println(";");
  236. sourceWriter.outdent();
  237. sourceWriter.println("}");
  238. sourceWriter.println("return value;");
  239. }
  240. private void writeBeanDeserializer(TreeLogger logger,
  241. SourceWriter sourceWriter, JClassType beanType) {
  242. String beanQualifiedSourceName = beanType.getQualifiedSourceName();
  243. // JSONOBject json = (JSONObject)jsonValue;
  244. sourceWriter.println(JSONObject.class.getName() + " json = ("
  245. + JSONObject.class.getName() + ")jsonValue;");
  246. for (JMethod method : getSetters(beanType)) {
  247. String setterName = method.getName();
  248. String baseName = setterName.substring(3);
  249. String fieldName = getTransportFieldName(baseName); // setZIndex()
  250. // -> zIndex
  251. JType setterParameterType = method.getParameterTypes()[0];
  252. logger.log(Type.DEBUG, "* Processing field " + fieldName + " in "
  253. + beanQualifiedSourceName + " (" + beanType.getName() + ")");
  254. // if (json.containsKey("height")) {
  255. sourceWriter.println("if (json.containsKey(\"" + fieldName
  256. + "\")) {");
  257. sourceWriter.indent();
  258. String jsonFieldName = "json_" + fieldName;
  259. // JSONValue json_Height = json.get("height");
  260. sourceWriter.println("JSONValue " + jsonFieldName
  261. + " = json.get(\"" + fieldName + "\");");
  262. String fieldType;
  263. String getterName = "get" + baseName;
  264. JPrimitiveType primitiveType = setterParameterType.isPrimitive();
  265. if (primitiveType != null) {
  266. // This is a primitive type -> must used the boxed type
  267. fieldType = primitiveType.getQualifiedBoxedSourceName();
  268. if (primitiveType == JPrimitiveType.BOOLEAN) {
  269. getterName = "is" + baseName;
  270. }
  271. } else {
  272. fieldType = setterParameterType.getQualifiedSourceName();
  273. }
  274. // String referenceValue = target.getHeight();
  275. sourceWriter.println(fieldType + " referenceValue = target."
  276. + getterName + "();");
  277. // target.setHeight((String)
  278. // JsonDecoder.decodeValue(jsonFieldValue,referenceValue, idMapper,
  279. // connection));
  280. sourceWriter.print("target." + setterName + "((" + fieldType + ") "
  281. + JsonDecoder.class.getName() + ".decodeValue(");
  282. GeneratedRpcMethodProviderGenerator.writeTypeCreator(sourceWriter,
  283. setterParameterType);
  284. sourceWriter.println(", " + jsonFieldName
  285. + ", referenceValue, connection));");
  286. // } ... end of if contains
  287. sourceWriter.outdent();
  288. sourceWriter.println("}");
  289. }
  290. }
  291. private void writeEnumSerializer(TreeLogger logger,
  292. SourceWriter sourceWriter, JClassType beanType) {
  293. // return new JSONString(castedValue.name());
  294. sourceWriter.println("return new " + JSONString.class.getName()
  295. + "(castedValue.name());");
  296. }
  297. private void writeArraySerializer(TreeLogger logger,
  298. SourceWriter sourceWriter, JArrayType array) {
  299. sourceWriter.println(JSONArray.class.getName() + " values = new "
  300. + JSONArray.class.getName() + "();");
  301. JType componentType = array.getComponentType();
  302. // JPrimitiveType primitive = componentType.isPrimitive();
  303. sourceWriter.println("for (int i = 0; i < castedValue.length; i++) {");
  304. sourceWriter.indent();
  305. sourceWriter.print("values.set(i, ");
  306. sourceWriter.print(JsonEncoder.class.getName()
  307. + ".encode(castedValue[i], false, connection)");
  308. sourceWriter.println(");");
  309. sourceWriter.outdent();
  310. sourceWriter.println("}");
  311. sourceWriter.println("return values;");
  312. }
  313. private void writeBeanSerializer(TreeLogger logger,
  314. SourceWriter sourceWriter, JClassType beanType)
  315. throws UnableToCompleteException {
  316. // JSONObject json = new JSONObject();
  317. sourceWriter.println(JSONObject.class.getName() + " json = new "
  318. + JSONObject.class.getName() + "();");
  319. HashSet<String> usedFieldNames = new HashSet<String>();
  320. for (JMethod setterMethod : getSetters(beanType)) {
  321. String setterName = setterMethod.getName();
  322. String fieldName = getTransportFieldName(setterName.substring(3)); // setZIndex()
  323. // -> zIndex
  324. if (!usedFieldNames.add(fieldName)) {
  325. logger.log(
  326. TreeLogger.ERROR,
  327. "Can't encode "
  328. + beanType.getQualifiedSourceName()
  329. + " as it has multiple fields with the name "
  330. + fieldName.toLowerCase()
  331. + ". This can happen if only casing distinguishes one property name from another.");
  332. throw new UnableToCompleteException();
  333. }
  334. String getterName = findGetter(beanType, setterMethod);
  335. if (getterName == null) {
  336. logger.log(TreeLogger.ERROR, "No getter found for " + fieldName
  337. + ". Serialization will likely fail");
  338. }
  339. // json.put("button",
  340. // JsonEncoder.encode(castedValue.getButton(), false, idMapper,
  341. // connection));
  342. sourceWriter.println("json.put(\"" + fieldName + "\", "
  343. + JsonEncoder.class.getName() + ".encode(castedValue."
  344. + getterName + "(), false, connection));");
  345. }
  346. // return json;
  347. sourceWriter.println("return json;");
  348. }
  349. private static String getTransportFieldName(String baseName) {
  350. return Character.toLowerCase(baseName.charAt(0))
  351. + baseName.substring(1);
  352. }
  353. private String findGetter(JClassType beanType, JMethod setterMethod) {
  354. JType setterParameterType = setterMethod.getParameterTypes()[0];
  355. String fieldName = setterMethod.getName().substring(3);
  356. if (setterParameterType.getQualifiedSourceName().equals(
  357. boolean.class.getName())) {
  358. return "is" + fieldName;
  359. } else {
  360. return "get" + fieldName;
  361. }
  362. }
  363. /**
  364. * Returns a list of all setters found in the beanType or its parent class
  365. *
  366. * @param beanType
  367. * The type to check
  368. * @return A list of setter methods from the class and its parents
  369. */
  370. protected static List<JMethod> getSetters(JClassType beanType) {
  371. List<JMethod> setterMethods = new ArrayList<JMethod>();
  372. while (beanType != null
  373. && !beanType.getQualifiedSourceName().equals(
  374. Object.class.getName())) {
  375. for (JMethod method : beanType.getMethods()) {
  376. // Process all setters that have corresponding fields
  377. if (!method.isPublic() || method.isStatic()
  378. || !method.getName().startsWith("set")
  379. || method.getParameterTypes().length != 1) {
  380. // Not setter, skip to next method
  381. continue;
  382. }
  383. setterMethods.add(method);
  384. }
  385. beanType = beanType.getSuperclass();
  386. }
  387. return setterMethods;
  388. }
  389. private static String getSerializerSimpleClassName(JClassType beanType) {
  390. return getSimpleClassName(beanType) + "_Serializer";
  391. }
  392. private static String getSimpleClassName(JType type) {
  393. JArrayType arrayType = type.isArray();
  394. if (arrayType != null) {
  395. return "Array" + getSimpleClassName(arrayType.getComponentType());
  396. }
  397. JClassType classType = type.isClass();
  398. if (classType != null && classType.isMemberType()) {
  399. // Assumed to be static sub class
  400. String baseName = getSimpleClassName(classType.getEnclosingType());
  401. String name = baseName + SUBTYPE_SEPARATOR
  402. + type.getSimpleSourceName();
  403. return name;
  404. }
  405. return type.getSimpleSourceName();
  406. }
  407. public static String getFullyQualifiedSerializerClassName(JClassType type) {
  408. return getSerializerPackageName(type) + "."
  409. + getSerializerSimpleClassName(type);
  410. }
  411. private static String getSerializerPackageName(JClassType type) {
  412. JPackage typePackage = type.getPackage();
  413. if (typePackage == null) {
  414. return SerializerMap.class.getPackage().getName();
  415. } else {
  416. String packageName = typePackage.getName();
  417. // Dev mode classloader gets unhappy for some java packages
  418. if (packageName.startsWith("java.")) {
  419. packageName = "com.vaadin." + packageName;
  420. }
  421. return packageName;
  422. }
  423. }
  424. }