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

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