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.

SerializerMapGenerator.java 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. /*
  2. @VaadinApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.widgetsetutils;
  5. import java.io.PrintWriter;
  6. import java.io.Serializable;
  7. import java.util.Date;
  8. import java.util.HashSet;
  9. import java.util.List;
  10. import java.util.Map;
  11. import java.util.Set;
  12. import com.google.gwt.core.ext.Generator;
  13. import com.google.gwt.core.ext.GeneratorContext;
  14. import com.google.gwt.core.ext.TreeLogger;
  15. import com.google.gwt.core.ext.TreeLogger.Type;
  16. import com.google.gwt.core.ext.UnableToCompleteException;
  17. import com.google.gwt.core.ext.typeinfo.JArrayType;
  18. import com.google.gwt.core.ext.typeinfo.JClassType;
  19. import com.google.gwt.core.ext.typeinfo.JMethod;
  20. import com.google.gwt.core.ext.typeinfo.JParameterizedType;
  21. import com.google.gwt.core.ext.typeinfo.JType;
  22. import com.google.gwt.core.ext.typeinfo.TypeOracle;
  23. import com.google.gwt.json.client.JSONObject;
  24. import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
  25. import com.google.gwt.user.rebind.SourceWriter;
  26. import com.vaadin.terminal.gwt.client.ApplicationConnection;
  27. import com.vaadin.terminal.gwt.client.ConnectorMap;
  28. import com.vaadin.terminal.gwt.client.communication.ClientRpc;
  29. import com.vaadin.terminal.gwt.client.communication.JSONSerializer;
  30. import com.vaadin.terminal.gwt.client.communication.SerializerMap;
  31. import com.vaadin.terminal.gwt.client.communication.ServerRpc;
  32. import com.vaadin.terminal.gwt.client.communication.SharedState;
  33. /**
  34. * GWT generator that creates a {@link SerializerMap} implementation (mapper
  35. * from type string to serializer instance) and serializer classes for all
  36. * subclasses of {@link SharedState}.
  37. *
  38. * @since 7.0
  39. */
  40. public class SerializerMapGenerator extends Generator {
  41. private String packageName;
  42. private String className;
  43. @Override
  44. public String generate(TreeLogger logger, GeneratorContext context,
  45. String typeName) throws UnableToCompleteException {
  46. try {
  47. TypeOracle typeOracle = context.getTypeOracle();
  48. Set<JClassType> typesNeedingSerializers = findTypesNeedingSerializers(
  49. typeOracle, logger);
  50. warnIfNotJavaSerializable(typesNeedingSerializers, typeOracle,
  51. logger);
  52. Set<JClassType> typesWithExistingSerializers = findTypesWithExistingSerializers(
  53. typeOracle, logger);
  54. Set<JClassType> serializerMappings = new HashSet<JClassType>();
  55. serializerMappings.addAll(typesNeedingSerializers);
  56. serializerMappings.addAll(typesWithExistingSerializers);
  57. // get classType and save instance variables
  58. JClassType classType = typeOracle.getType(typeName);
  59. packageName = classType.getPackage().getName();
  60. className = classType.getSimpleSourceName() + "Impl";
  61. // Generate class source code for SerializerMapImpl
  62. generateSerializerMap(serializerMappings, logger, context);
  63. SerializerGenerator sg = new SerializerGenerator();
  64. for (JClassType type : typesNeedingSerializers) {
  65. sg.generate(logger, context, type.getQualifiedSourceName());
  66. }
  67. } catch (Exception e) {
  68. logger.log(TreeLogger.ERROR,
  69. "SerializerMapGenerator creation failed", e);
  70. throw new UnableToCompleteException();
  71. }
  72. // return the fully qualifed name of the class generated
  73. return packageName + "." + className;
  74. }
  75. /**
  76. * Emits a warning for all classes that are used in communication but do not
  77. * implement java.io.Serializable. Implementing java.io.Serializable is not
  78. * needed for communication but for the server side Application to be
  79. * serializable i.e. work in GAE for instance.
  80. *
  81. * @param typesNeedingSerializers
  82. * @param typeOracle
  83. * @param logger
  84. */
  85. private void warnIfNotJavaSerializable(
  86. Set<JClassType> typesNeedingSerializers, TypeOracle typeOracle,
  87. TreeLogger logger) {
  88. JClassType javaSerializable = typeOracle.findType(Serializable.class
  89. .getName());
  90. for (JClassType type : typesNeedingSerializers) {
  91. if (type.isArray() != null) {
  92. // Don't check for arrays
  93. continue;
  94. }
  95. boolean serializable = type.isAssignableTo(javaSerializable);
  96. if (!serializable) {
  97. logger.log(
  98. Type.ERROR,
  99. type
  100. + " is used in RPC or shared state but does not implement "
  101. + Serializable.class.getName()
  102. + ". Communication will work but the Application on server side cannot be serialized if it refers to objects of this type.");
  103. }
  104. }
  105. }
  106. private Set<JClassType> findTypesWithExistingSerializers(
  107. TypeOracle typeOracle, TreeLogger logger) {
  108. JClassType serializerInterface = typeOracle
  109. .findType(JSONSerializer.class.getName());
  110. Set<JClassType> types = new HashSet<JClassType>();
  111. for (JClassType serializer : serializerInterface.getSubtypes()) {
  112. JType[] deserializeParamTypes = new JType[] {
  113. typeOracle.findType(JSONObject.class.getName()),
  114. typeOracle.findType(ConnectorMap.class.getName()),
  115. typeOracle.findType(ApplicationConnection.class.getName()) };
  116. JMethod deserializeMethod = serializer.findMethod("deserialize",
  117. deserializeParamTypes);
  118. if (deserializeMethod == null) {
  119. continue;
  120. }
  121. types.add(deserializeMethod.getReturnType().isClass());
  122. }
  123. return types;
  124. }
  125. /**
  126. * Generate source code for SerializerMapImpl
  127. *
  128. * @param typesNeedingSerializers
  129. *
  130. * @param logger
  131. * Logger object
  132. * @param context
  133. * Generator context
  134. */
  135. private void generateSerializerMap(Set<JClassType> typesNeedingSerializers,
  136. TreeLogger logger, GeneratorContext context) {
  137. // get print writer that receives the source code
  138. PrintWriter printWriter = null;
  139. printWriter = context.tryCreate(logger, packageName, className);
  140. // print writer if null, source code has ALREADY been generated
  141. if (printWriter == null) {
  142. return;
  143. }
  144. Date date = new Date();
  145. TypeOracle typeOracle = context.getTypeOracle();
  146. // init composer, set class properties, create source writer
  147. ClassSourceFileComposerFactory composer = null;
  148. composer = new ClassSourceFileComposerFactory(packageName, className);
  149. composer.addImport("com.google.gwt.core.client.GWT");
  150. composer.addImplementedInterface(SerializerMap.class.getName());
  151. SourceWriter sourceWriter = composer.createSourceWriter(context,
  152. printWriter);
  153. sourceWriter.indent();
  154. sourceWriter.println("public " + JSONSerializer.class.getName()
  155. + " getSerializer(String type) {");
  156. sourceWriter.indent();
  157. // TODO cache serializer instances in a map
  158. for (JClassType type : typesNeedingSerializers) {
  159. sourceWriter.print("if (type.equals(\""
  160. + type.getQualifiedSourceName() + "\")");
  161. if (type instanceof JArrayType) {
  162. // Also add binary name to support encoding based on
  163. // object.getClass().getName()
  164. sourceWriter.print("||type.equals(\"" + type.getJNISignature()
  165. + "\")");
  166. }
  167. sourceWriter.println(") {");
  168. sourceWriter.indent();
  169. String serializerName = SerializerGenerator
  170. .getFullyQualifiedSerializerClassName(type);
  171. sourceWriter.println("return GWT.create(" + serializerName
  172. + ".class);");
  173. sourceWriter.outdent();
  174. sourceWriter.println("}");
  175. logger.log(Type.INFO, "Configured serializer (" + serializerName
  176. + ") for " + type.getName());
  177. }
  178. sourceWriter
  179. .println("throw new RuntimeException(\"No serializer found for class \"+type);");
  180. sourceWriter.outdent();
  181. sourceWriter.println("}");
  182. // close generated class
  183. sourceWriter.outdent();
  184. sourceWriter.println("}");
  185. // commit generated class
  186. context.commit(logger, printWriter);
  187. logger.log(Type.INFO,
  188. "Done. (" + (new Date().getTime() - date.getTime()) / 1000
  189. + "seconds)");
  190. }
  191. public Set<JClassType> findTypesNeedingSerializers(TypeOracle typeOracle,
  192. TreeLogger logger) {
  193. logger.log(Type.DEBUG, "Detecting serializable data types...");
  194. HashSet<JClassType> types = new HashSet<JClassType>();
  195. // Generate serializer classes for each subclass of SharedState
  196. JClassType serializerType = typeOracle.findType(SharedState.class
  197. .getName());
  198. types.add(serializerType);
  199. JClassType[] serializerSubtypes = serializerType.getSubtypes();
  200. for (JClassType type : serializerSubtypes) {
  201. types.add(type);
  202. }
  203. // Serializer classes might also be needed for RPC methods
  204. for (Class<?> cls : new Class[] { ServerRpc.class, ClientRpc.class }) {
  205. JClassType rpcType = typeOracle.findType(cls.getName());
  206. JClassType[] serverRpcSubtypes = rpcType.getSubtypes();
  207. for (JClassType type : serverRpcSubtypes) {
  208. addMethodParameterTypes(type, types, logger);
  209. }
  210. }
  211. // Add all types used from/in the types
  212. for (Object t : types.toArray()) {
  213. findSubTypesNeedingSerializers((JClassType) t, types);
  214. }
  215. logger.log(Type.DEBUG, "Serializable data types: " + types.toString());
  216. return types;
  217. }
  218. private void addMethodParameterTypes(JClassType classContainingMethods,
  219. Set<JClassType> types, TreeLogger logger) {
  220. for (JMethod method : classContainingMethods.getMethods()) {
  221. if (method.getName().equals("initRpc")) {
  222. continue;
  223. }
  224. for (JType type : method.getParameterTypes()) {
  225. addTypeIfNeeded(types, type);
  226. }
  227. }
  228. }
  229. public void findSubTypesNeedingSerializers(JClassType type,
  230. Set<JClassType> serializableTypes) {
  231. // Find all setters and look at their parameter type to determine if a
  232. // new serializer is needed
  233. for (JMethod setterMethod : SerializerGenerator.getSetters(type)) {
  234. // The one and only parameter for the setter
  235. JType setterType = setterMethod.getParameterTypes()[0];
  236. addTypeIfNeeded(serializableTypes, setterType);
  237. }
  238. }
  239. private void addTypeIfNeeded(Set<JClassType> serializableTypes, JType type) {
  240. if (serializableTypes.contains(type)) {
  241. return;
  242. }
  243. JParameterizedType parametrized = type.isParameterized();
  244. if (parametrized != null) {
  245. for (JClassType parameterType : parametrized.getTypeArgs()) {
  246. addTypeIfNeeded(serializableTypes, parameterType);
  247. }
  248. }
  249. if (serializationHandledByFramework(type)) {
  250. return;
  251. }
  252. if (serializableTypes.contains(type)) {
  253. return;
  254. }
  255. JClassType typeClass = type.isClass();
  256. if (typeClass != null) {
  257. // setterTypeClass is null at least for List<String>. It is
  258. // possible that we need to handle the cases somehow, for
  259. // instance for List<MyObject>.
  260. serializableTypes.add(typeClass);
  261. findSubTypesNeedingSerializers(typeClass, serializableTypes);
  262. }
  263. // Generate (n-1)-dimensional array serializer for n-dimensional array
  264. JArrayType arrayType = type.isArray();
  265. if (arrayType != null) {
  266. serializableTypes.add(arrayType);
  267. addTypeIfNeeded(serializableTypes, arrayType.getComponentType());
  268. }
  269. }
  270. Set<Class<?>> frameworkHandledTypes = new HashSet<Class<?>>();
  271. {
  272. frameworkHandledTypes.add(String.class);
  273. frameworkHandledTypes.add(Boolean.class);
  274. frameworkHandledTypes.add(Integer.class);
  275. frameworkHandledTypes.add(Float.class);
  276. frameworkHandledTypes.add(Double.class);
  277. frameworkHandledTypes.add(Long.class);
  278. frameworkHandledTypes.add(Enum.class);
  279. frameworkHandledTypes.add(String[].class);
  280. frameworkHandledTypes.add(Object[].class);
  281. frameworkHandledTypes.add(Map.class);
  282. frameworkHandledTypes.add(List.class);
  283. frameworkHandledTypes.add(Set.class);
  284. frameworkHandledTypes.add(Byte.class);
  285. frameworkHandledTypes.add(Character.class);
  286. }
  287. private boolean serializationHandledByFramework(JType setterType) {
  288. // Some types are handled by the framework at the moment. See #8449
  289. // This method should be removed at some point.
  290. if (setterType.isPrimitive() != null) {
  291. return true;
  292. }
  293. String qualifiedName = setterType.getQualifiedSourceName();
  294. for (Class<?> cls : frameworkHandledTypes) {
  295. if (qualifiedName.equals(cls.getName())) {
  296. return true;
  297. }
  298. }
  299. return false;
  300. }
  301. }