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

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