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.

TestClassesSerializable.java 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. package com.vaadin.tests.server;
  2. import java.io.File;
  3. import java.io.IOException;
  4. import java.io.Serializable;
  5. import java.lang.reflect.Method;
  6. import java.util.ArrayList;
  7. import java.util.Collection;
  8. import java.util.Collections;
  9. import java.util.Comparator;
  10. import java.util.Enumeration;
  11. import java.util.Iterator;
  12. import java.util.List;
  13. import java.util.jar.JarEntry;
  14. import java.util.jar.JarFile;
  15. import junit.framework.TestCase;
  16. import org.junit.Test;
  17. public class TestClassesSerializable extends TestCase {
  18. /**
  19. * JARs that will be scanned for classes to test, in addition to classpath
  20. * directories.
  21. */
  22. private static String JAR_PATTERN = ".*vaadin.*\\.jar";
  23. private static String[] BASE_PACKAGES = { "com.vaadin" };
  24. private static String[] EXCLUDED_PATTERNS = {
  25. "com\\.vaadin\\.demo\\..*", //
  26. "com\\.vaadin\\.external\\.org\\.apache\\.commons\\.fileupload\\..*", //
  27. "com\\.vaadin\\.launcher\\..*", //
  28. "com\\.vaadin\\.client\\..*", //
  29. "com\\.vaadin\\.server\\.widgetsetutils\\..*", //
  30. "com\\.vaadin\\.server\\.themeutils\\..*", //
  31. "com\\.vaadin\\.tests\\..*", // exclude automated tests
  32. "com\\.vaadin\\.tools\\..*", //
  33. "com\\.vaadin\\.ui\\.themes\\..*", //
  34. // exact class level filtering
  35. "com\\.vaadin\\.event\\.FieldEvents", //
  36. "com\\.vaadin\\.event\\.LayoutEvents", //
  37. "com\\.vaadin\\.event\\.MouseEvents", //
  38. "com\\.vaadin\\.event\\.UIEvents", //
  39. "com\\.vaadin\\.server\\.VaadinPortlet", //
  40. "com\\.vaadin\\.server\\.MockServletConfig", //
  41. "com\\.vaadin\\.server\\.MockServletContext", //
  42. "com\\.vaadin\\.server\\.Constants", //
  43. "com\\.vaadin\\.server\\.communication\\.FileUploadHandler\\$SimpleMultiPartInputStream", //
  44. "com\\.vaadin\\.server\\.communication\\.PushRequestHandler.*",
  45. "com\\.vaadin\\.server\\.communication\\.PushHandler.*", // PushHandler
  46. "com\\.vaadin\\.server\\.communication\\.DateSerializer", //
  47. "com\\.vaadin\\.server\\.communication\\.JsonSerializer", //
  48. // and its inner classes do not need to be serializable
  49. "com\\.vaadin\\.util\\.SerializerHelper", // fully static
  50. // class level filtering, also affecting nested classes and
  51. // interfaces
  52. "com\\.vaadin\\.server\\.LegacyCommunicationManager.*", //
  53. "com\\.vaadin\\.buildhelpers.*", //
  54. "com\\.vaadin\\.util\\.ReflectTools.*", //
  55. "com\\.vaadin\\.data\\.util\\.ReflectTools.*", //
  56. "com\\.vaadin\\.sass.*", //
  57. "com\\.vaadin\\.testbench.*", //
  58. "com\\.vaadin\\.util\\.CurrentInstance\\$1", //
  59. "com\\.vaadin\\.server\\.AbstractClientConnector\\$1", //
  60. "com\\.vaadin\\.server\\.AbstractClientConnector\\$1\\$1", //
  61. "com\\.vaadin\\.server\\.JsonCodec\\$1", //
  62. "com\\.vaadin\\.server\\.communication\\.PushConnection", //
  63. "com\\.vaadin\\.server\\.communication\\.AtmospherePushConnection", //
  64. "com\\.vaadin\\.util\\.ConnectorHelper", //
  65. "com\\.vaadin\\.server\\.VaadinSession\\$FutureAccess", //
  66. "com\\.vaadin\\.external\\..*", //
  67. "com\\.vaadin\\.util\\.WeakValueMap.*", //
  68. };
  69. /**
  70. * Tests that all the relevant classes and interfaces under
  71. * {@link #BASE_PACKAGES} implement Serializable.
  72. *
  73. * @throws Exception
  74. */
  75. public void testClassesSerializable() throws Exception {
  76. List<String> rawClasspathEntries = getRawClasspathEntries();
  77. List<String> classes = new ArrayList<String>();
  78. for (String location : rawClasspathEntries) {
  79. classes.addAll(findServerClasses(location));
  80. }
  81. ArrayList<Class<?>> nonSerializableClasses = new ArrayList<Class<?>>();
  82. for (String className : classes) {
  83. Class<?> cls = Class.forName(className);
  84. // skip annotations and synthetic classes
  85. if (cls.isAnnotation() || cls.isSynthetic()) {
  86. continue;
  87. }
  88. // Don't add classes that have a @Test annotation on any methods
  89. boolean testPresent = false;
  90. for (Method method : cls.getMethods()) {
  91. if (method.isAnnotationPresent(Test.class)) {
  92. testPresent = true;
  93. break;
  94. }
  95. }
  96. if (testPresent) {
  97. continue;
  98. }
  99. // report non-serializable classes and interfaces
  100. if (!Serializable.class.isAssignableFrom(cls)) {
  101. if (cls.getSuperclass() == Object.class
  102. && cls.getInterfaces().length == 1) {
  103. // Single interface implementors
  104. Class<?> iface = cls.getInterfaces()[0];
  105. if (iface == Runnable.class) {
  106. // Ignore Runnables used with access()
  107. continue;
  108. } else if (iface == Comparator.class) {
  109. // Ignore inline comparators
  110. continue;
  111. }
  112. }
  113. nonSerializableClasses.add(cls);
  114. // TODO easier to read when testing
  115. // System.err.println(cls);
  116. }
  117. }
  118. // useful failure message including all non-serializable classes and
  119. // interfaces
  120. if (!nonSerializableClasses.isEmpty()) {
  121. String nonSerializableString = "";
  122. Iterator<Class<?>> it = nonSerializableClasses.iterator();
  123. while (it.hasNext()) {
  124. Class c = it.next();
  125. nonSerializableString += ", " + c.getName();
  126. if (c.isAnonymousClass()) {
  127. nonSerializableString += "(super: ";
  128. nonSerializableString += c.getSuperclass().getName();
  129. nonSerializableString += ", interfaces: ";
  130. for (Class i : c.getInterfaces()) {
  131. nonSerializableString += i.getName();
  132. nonSerializableString += ",";
  133. }
  134. nonSerializableString += ")";
  135. }
  136. }
  137. fail("Serializable not implemented by the following classes and interfaces: "
  138. + nonSerializableString);
  139. }
  140. }
  141. /**
  142. * Lists all class path entries by splitting the class path string.
  143. *
  144. * Adapted from ClassPathExplorer.getRawClasspathEntries(), but without
  145. * filtering.
  146. *
  147. * @return List of class path segment strings
  148. */
  149. //
  150. private final static List<String> getRawClasspathEntries() {
  151. // try to keep the order of the classpath
  152. List<String> locations = new ArrayList<String>();
  153. String pathSep = System.getProperty("path.separator");
  154. String classpath = System.getProperty("java.class.path");
  155. if (classpath.startsWith("\"")) {
  156. classpath = classpath.substring(1);
  157. }
  158. if (classpath.endsWith("\"")) {
  159. classpath = classpath.substring(0, classpath.length() - 1);
  160. }
  161. String[] split = classpath.split(pathSep);
  162. for (int i = 0; i < split.length; i++) {
  163. String classpathEntry = split[i];
  164. locations.add(classpathEntry);
  165. }
  166. return locations;
  167. }
  168. /**
  169. * Finds the server side classes/interfaces under a class path entry -
  170. * either a directory or a JAR that matches {@link #JAR_PATTERN}.
  171. *
  172. * Only classes under {@link #BASE_PACKAGES} are considered, and those
  173. * matching {@link #EXCLUDED_PATTERNS} are filtered out.
  174. *
  175. * @param classpathEntry
  176. * @return
  177. * @throws IOException
  178. */
  179. private List<String> findServerClasses(String classpathEntry)
  180. throws IOException {
  181. Collection<String> classes = new ArrayList<String>();
  182. File file = new File(classpathEntry);
  183. if (file.isDirectory()) {
  184. classes = findClassesInDirectory(null, file);
  185. } else if (file.getName().matches(JAR_PATTERN)) {
  186. classes = findClassesInJar(file);
  187. } else {
  188. System.out.println("Ignoring " + classpathEntry);
  189. return Collections.emptyList();
  190. }
  191. List<String> filteredClasses = new ArrayList<String>();
  192. for (String className : classes) {
  193. boolean ok = false;
  194. for (String basePackage : BASE_PACKAGES) {
  195. if (className.startsWith(basePackage + ".")) {
  196. ok = true;
  197. break;
  198. }
  199. }
  200. for (String excludedPrefix : EXCLUDED_PATTERNS) {
  201. if (className.matches(excludedPrefix)) {
  202. ok = false;
  203. break;
  204. }
  205. }
  206. // Don't add test classes
  207. if (className.contains("Test")) {
  208. ok = false;
  209. }
  210. if (ok) {
  211. filteredClasses.add(className);
  212. }
  213. }
  214. return filteredClasses;
  215. }
  216. /**
  217. * Lists class names (based on .class files) in a JAR file.
  218. *
  219. * @param file
  220. * a valid JAR file
  221. * @return collection of fully qualified class names in the JAR
  222. * @throws IOException
  223. */
  224. private Collection<String> findClassesInJar(File file) throws IOException {
  225. Collection<String> classes = new ArrayList<String>();
  226. JarFile jar = new JarFile(file);
  227. Enumeration<JarEntry> e = jar.entries();
  228. while (e.hasMoreElements()) {
  229. JarEntry entry = e.nextElement();
  230. if (entry.getName().endsWith(".class")) {
  231. String nameWithoutExtension = entry.getName().replaceAll(
  232. "\\.class", "");
  233. String className = nameWithoutExtension.replace('/', '.');
  234. classes.add(className);
  235. }
  236. }
  237. return classes;
  238. }
  239. /**
  240. * Lists class names (based on .class files) in a directory (a package path
  241. * root).
  242. *
  243. * @param parentPackage
  244. * parent package name or null at root of hierarchy, used by
  245. * recursion
  246. * @param parent
  247. * File representing the directory to scan
  248. * @return collection of fully qualified class names in the directory
  249. */
  250. private final static Collection<String> findClassesInDirectory(
  251. String parentPackage, File parent) {
  252. if (parent.isHidden()
  253. || parent.getPath().contains(File.separator + ".")) {
  254. return Collections.emptyList();
  255. }
  256. if (parentPackage == null) {
  257. parentPackage = "";
  258. } else {
  259. parentPackage += ".";
  260. }
  261. Collection<String> classNames = new ArrayList<String>();
  262. // add all directories recursively
  263. File[] files = parent.listFiles();
  264. for (File child : files) {
  265. if (child.isDirectory()) {
  266. classNames.addAll(findClassesInDirectory(
  267. parentPackage + child.getName(), child));
  268. } else if (child.getName().endsWith(".class")) {
  269. classNames.add(parentPackage.replace(File.separatorChar, '.')
  270. + child.getName().replaceAll("\\.class", ""));
  271. }
  272. }
  273. return classNames;
  274. }
  275. }