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

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