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.

ClassPathExplorer.java 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. /*
  2. @ITMillApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.widgetsetutils;
  5. import java.io.File;
  6. import java.io.FileFilter;
  7. import java.io.IOException;
  8. import java.io.OutputStream;
  9. import java.io.PrintStream;
  10. import java.net.JarURLConnection;
  11. import java.net.MalformedURLException;
  12. import java.net.URL;
  13. import java.net.URLConnection;
  14. import java.util.ArrayList;
  15. import java.util.Collection;
  16. import java.util.Enumeration;
  17. import java.util.HashMap;
  18. import java.util.HashSet;
  19. import java.util.Iterator;
  20. import java.util.LinkedHashMap;
  21. import java.util.List;
  22. import java.util.Map;
  23. import java.util.Set;
  24. import java.util.jar.Attributes;
  25. import java.util.jar.JarEntry;
  26. import java.util.jar.JarFile;
  27. import java.util.jar.Manifest;
  28. import java.util.logging.Level;
  29. import java.util.logging.Logger;
  30. import com.vaadin.event.dd.acceptcriteria.AcceptCriterion;
  31. import com.vaadin.event.dd.acceptcriteria.ClientCriterion;
  32. import com.vaadin.terminal.Paintable;
  33. import com.vaadin.ui.ClientWidget;
  34. /**
  35. * Utility class to collect widgetset related information from classpath.
  36. * Utility will seek all directories from classpaths, and jar files having
  37. * "Vaadin-Widgetsets" key in their manifest file.
  38. * <p>
  39. * Used by WidgetMapGenerator and ide tools to implement some monkey coding for
  40. * you.
  41. * <p>
  42. * Developer notice: If you end up reading this comment, I guess you have faced
  43. * a sluggish performance of widget compilation or unreliable detection of
  44. * components in your classpaths. The thing you might be able to do is to use
  45. * annotation processing tool like apt to generate the needed information. Then
  46. * either use that information in {@link WidgetMapGenerator} or create the
  47. * appropriate monkey code for gwt directly in annotation processor and get rid
  48. * of {@link WidgetMapGenerator}. Using annotation processor might be a good
  49. * idea when dropping Java 1.5 support (integrated to javac in 6).
  50. *
  51. */
  52. public class ClassPathExplorer {
  53. private static Logger logger = Logger
  54. .getLogger("com.vaadin.terminal.gwt.widgetsetutils");
  55. private final static FileFilter DIRECTORIES_ONLY = new FileFilter() {
  56. public boolean accept(File f) {
  57. if (f.exists() && f.isDirectory()) {
  58. return true;
  59. } else {
  60. return false;
  61. }
  62. }
  63. };
  64. private static List<String> rawClasspathEntries = getRawClasspathEntries();
  65. private static Map<URL, String> classpathLocations = getClasspathLocations(rawClasspathEntries);
  66. private ClassPathExplorer() {
  67. }
  68. /**
  69. * Finds server side widgets with {@link ClientWidget} annotation.
  70. */
  71. public static Collection<Class<? extends Paintable>> getPaintablesHavingWidgetAnnotation() {
  72. Collection<Class<? extends Paintable>> paintables = new HashSet<Class<? extends Paintable>>();
  73. Set<URL> keySet = classpathLocations.keySet();
  74. for (URL url : keySet) {
  75. searchForPaintables(url, classpathLocations.get(url), paintables);
  76. }
  77. return paintables;
  78. }
  79. public static Collection<Class<? extends AcceptCriterion>> getCriterion() {
  80. if (acceptCriterion.isEmpty()) {
  81. // accept criterion are searched as a side effect, normally after
  82. // paintable detection
  83. getPaintablesHavingWidgetAnnotation();
  84. }
  85. return acceptCriterion;
  86. }
  87. /**
  88. * Finds available widgetset names.
  89. *
  90. * @return
  91. */
  92. public static Map<String, URL> getAvailableWidgetSets() {
  93. Map<String, URL> widgetsets = new HashMap<String, URL>();
  94. Set<URL> keySet = classpathLocations.keySet();
  95. for (URL url : keySet) {
  96. searchForWidgetSets(url, widgetsets);
  97. }
  98. StringBuilder sb = new StringBuilder();
  99. sb.append("Widgetsets found from classpath:\n");
  100. for (String ws : widgetsets.keySet()) {
  101. sb.append("\t");
  102. sb.append(ws);
  103. sb.append(" in ");
  104. sb.append(widgetsets.get(ws));
  105. sb.append("\n");
  106. }
  107. logger.info(sb.toString());
  108. return widgetsets;
  109. }
  110. private static void searchForWidgetSets(URL location,
  111. Map<String, URL> widgetsets) {
  112. File directory = new File(location.getFile());
  113. if (directory.exists() && !directory.isHidden()) {
  114. // Get the list of the files contained in the directory
  115. String[] files = directory.list();
  116. for (int i = 0; i < files.length; i++) {
  117. // we are only interested in .gwt.xml files
  118. if (files[i].endsWith(".gwt.xml")) {
  119. // remove the extension
  120. String classname = files[i].substring(0,
  121. files[i].length() - 8);
  122. classname = classpathLocations.get(location) + "."
  123. + classname;
  124. if (!widgetsets.containsKey(classname)) {
  125. String packageName = classpathLocations.get(location);
  126. String packagePath = packageName.replaceAll("\\.", "/");
  127. String basePath = location.getFile().replaceAll(
  128. "/" + packagePath + "$", "");
  129. try {
  130. URL url = new URL(location.getProtocol(), location
  131. .getHost(), location.getPort(), basePath);
  132. widgetsets.put(classname, url);
  133. } catch (MalformedURLException e) {
  134. // should never happen as based on an existing URL,
  135. // only changing end of file name/path part
  136. e.printStackTrace();
  137. }
  138. }
  139. }
  140. }
  141. } else {
  142. try {
  143. // check files in jar file, entries will list all directories
  144. // and files in jar
  145. URLConnection openConnection = location.openConnection();
  146. if (openConnection instanceof JarURLConnection) {
  147. JarURLConnection conn = (JarURLConnection) openConnection;
  148. JarFile jarFile = conn.getJarFile();
  149. Manifest manifest = jarFile.getManifest();
  150. if (manifest == null) {
  151. // No manifest so this is not a Vaadin Add-on
  152. return;
  153. }
  154. String value = manifest.getMainAttributes().getValue(
  155. "Vaadin-Widgetsets");
  156. if (value != null) {
  157. String[] widgetsetNames = value.split(",");
  158. for (int i = 0; i < widgetsetNames.length; i++) {
  159. String widgetsetname = widgetsetNames[i].trim()
  160. .intern();
  161. if (!widgetsetname.equals("")) {
  162. widgetsets.put(widgetsetname, location);
  163. }
  164. }
  165. }
  166. }
  167. } catch (IOException e) {
  168. logger.log(Level.WARNING, "Error parsing jar file", e);
  169. }
  170. }
  171. }
  172. /**
  173. * Determine every URL location defined by the current classpath, and it's
  174. * associated package name.
  175. */
  176. private final static List<String> getRawClasspathEntries() {
  177. // try to keep the order of the classpath
  178. List<String> locations = new ArrayList<String>();
  179. String pathSep = System.getProperty("path.separator");
  180. String classpath = System.getProperty("java.class.path");
  181. if (classpath.startsWith("\"")) {
  182. classpath = classpath.substring(1);
  183. }
  184. if (classpath.endsWith("\"")) {
  185. classpath = classpath.substring(0, classpath.length() - 1);
  186. }
  187. logger.fine("Classpath: " + classpath);
  188. String[] split = classpath.split(pathSep);
  189. for (int i = 0; i < split.length; i++) {
  190. String classpathEntry = split[i];
  191. if (acceptClassPathEntry(classpathEntry)) {
  192. locations.add(classpathEntry);
  193. }
  194. }
  195. return locations;
  196. }
  197. /**
  198. * Determine every URL location defined by the current classpath, and it's
  199. * associated package name.
  200. */
  201. private final static Map<URL, String> getClasspathLocations(
  202. List<String> rawClasspathEntries) {
  203. // try to keep the order of the classpath
  204. Map<URL, String> locations = new LinkedHashMap<URL, String>();
  205. for (String classpathEntry : rawClasspathEntries) {
  206. File file = new File(classpathEntry);
  207. include(null, file, locations);
  208. }
  209. return locations;
  210. }
  211. private static boolean acceptClassPathEntry(String classpathEntry) {
  212. if (!classpathEntry.endsWith(".jar")) {
  213. // accept all non jars (practically directories)
  214. return true;
  215. } else {
  216. // accepts jars that comply with vaadin-component packaging
  217. // convention (.vaadin. or vaadin- as distribution packages),
  218. if (classpathEntry.contains("vaadin-")
  219. || classpathEntry.contains(".vaadin.")) {
  220. return true;
  221. } else {
  222. URL url;
  223. try {
  224. url = new URL("file:"
  225. + new File(classpathEntry).getCanonicalPath());
  226. url = new URL("jar:" + url.toExternalForm() + "!/");
  227. JarURLConnection conn = (JarURLConnection) url
  228. .openConnection();
  229. logger.fine(url.toString());
  230. JarFile jarFile = conn.getJarFile();
  231. Manifest manifest = jarFile.getManifest();
  232. if (manifest != null) {
  233. Attributes mainAttributes = manifest
  234. .getMainAttributes();
  235. if (mainAttributes.getValue("Vaadin-Widgetsets") != null) {
  236. return true;
  237. }
  238. }
  239. } catch (MalformedURLException e) {
  240. // TODO Auto-generated catch block
  241. e.printStackTrace();
  242. } catch (IOException e) {
  243. // TODO Auto-generated catch block
  244. e.printStackTrace();
  245. }
  246. return false;
  247. }
  248. }
  249. }
  250. /**
  251. * Recursively add subdirectories and jar files to classpathlocations
  252. *
  253. * @param name
  254. * @param file
  255. * @param locations
  256. */
  257. private final static void include(String name, File file,
  258. Map<URL, String> locations) {
  259. if (!file.exists()) {
  260. return;
  261. }
  262. if (!file.isDirectory()) {
  263. // could be a JAR file
  264. includeJar(file, locations);
  265. return;
  266. }
  267. if (file.isHidden() || file.getPath().contains(File.separator + ".")) {
  268. return;
  269. }
  270. if (name == null) {
  271. name = "";
  272. } else {
  273. name += ".";
  274. }
  275. // add all directories recursively
  276. File[] dirs = file.listFiles(DIRECTORIES_ONLY);
  277. for (int i = 0; i < dirs.length; i++) {
  278. try {
  279. // add the present directory
  280. locations.put(new URL("file://" + dirs[i].getCanonicalPath()),
  281. name + dirs[i].getName());
  282. } catch (Exception ioe) {
  283. return;
  284. }
  285. include(name + dirs[i].getName(), dirs[i], locations);
  286. }
  287. }
  288. private static void includeJar(File file, Map<URL, String> locations) {
  289. try {
  290. URL url = new URL("file:" + file.getCanonicalPath());
  291. url = new URL("jar:" + url.toExternalForm() + "!/");
  292. JarURLConnection conn = (JarURLConnection) url.openConnection();
  293. JarFile jarFile = conn.getJarFile();
  294. if (jarFile != null) {
  295. locations.put(url, "");
  296. }
  297. } catch (Exception e) {
  298. // e.printStackTrace();
  299. return;
  300. }
  301. }
  302. private final static void searchForPaintables(URL location,
  303. String packageName,
  304. Collection<Class<? extends Paintable>> paintables) {
  305. // Get a File object for the package
  306. File directory = new File(location.getFile());
  307. if (directory.exists() && !directory.isHidden()) {
  308. // Get the list of the files contained in the directory
  309. String[] files = directory.list();
  310. for (int i = 0; i < files.length; i++) {
  311. // we are only interested in .class files
  312. if (files[i].endsWith(".class")) {
  313. // remove the .class extension
  314. String classname = files[i].substring(0,
  315. files[i].length() - 6);
  316. classname = packageName + "." + classname;
  317. tryToAdd(classname, paintables);
  318. }
  319. }
  320. } else {
  321. try {
  322. // check files in jar file, entries will list all directories
  323. // and files in jar
  324. URLConnection openConnection = location.openConnection();
  325. if (openConnection instanceof JarURLConnection) {
  326. JarURLConnection conn = (JarURLConnection) openConnection;
  327. JarFile jarFile = conn.getJarFile();
  328. Enumeration<JarEntry> e = jarFile.entries();
  329. while (e.hasMoreElements()) {
  330. JarEntry entry = e.nextElement();
  331. String entryname = entry.getName();
  332. if (!entry.isDirectory()
  333. && entryname.endsWith(".class")
  334. && !entryname.contains("$")) {
  335. String classname = entryname.substring(0, entryname
  336. .length() - 6);
  337. if (classname.startsWith("/")) {
  338. classname = classname.substring(1);
  339. }
  340. classname = classname.replace('/', '.');
  341. tryToAdd(classname, paintables);
  342. }
  343. }
  344. }
  345. } catch (IOException e) {
  346. logger.warning(e.toString());
  347. }
  348. }
  349. }
  350. // Hide possible errors, exceptions from static initializers from
  351. // classes we are inspecting
  352. private static PrintStream devnull = new PrintStream(new OutputStream() {
  353. @Override
  354. public void write(int b) throws IOException {
  355. // NOP
  356. }
  357. });
  358. private static Set<Class<? extends AcceptCriterion>> acceptCriterion = new HashSet<Class<? extends AcceptCriterion>>();
  359. private static void tryToAdd(final String fullclassName,
  360. Collection<Class<? extends Paintable>> paintables) {
  361. try {
  362. PrintStream out = System.out;
  363. PrintStream err = System.err;
  364. System.setErr(devnull);
  365. System.setOut(devnull);
  366. Class<?> c = Class.forName(fullclassName);
  367. System.setErr(err);
  368. System.setOut(out);
  369. if (c.getAnnotation(ClientWidget.class) != null) {
  370. paintables.add((Class<? extends Paintable>) c);
  371. // System.out.println("Found paintable " + fullclassName);
  372. } else if (c.getAnnotation(ClientCriterion.class) != null) {
  373. acceptCriterion.add((Class<? extends AcceptCriterion>) c);
  374. }
  375. } catch (ClassNotFoundException e) {
  376. // e.printStackTrace();
  377. } catch (LinkageError e) {
  378. // NOP
  379. } catch (Exception e) {
  380. e.printStackTrace();
  381. }
  382. }
  383. /**
  384. * Find and return the default source directory where to create new
  385. * widgetsets.
  386. *
  387. * Return the first directory (not a JAR file etc.) on the classpath by
  388. * default.
  389. *
  390. * TODO this could be done better...
  391. *
  392. * @return URL
  393. */
  394. public static URL getDefaultSourceDirectory() {
  395. if (logger.isLoggable(Level.FINE)) {
  396. logger.fine("classpathLocations keys:");
  397. ArrayList<URL> locations = new ArrayList<URL>(classpathLocations
  398. .keySet());
  399. for (URL location : locations) {
  400. logger.fine(location.toString());
  401. }
  402. }
  403. Iterator<String> it = rawClasspathEntries.iterator();
  404. while (it.hasNext()) {
  405. String entry = it.next();
  406. File directory = new File(entry);
  407. if (directory.exists() && !directory.isHidden()
  408. && directory.isDirectory()) {
  409. try {
  410. return new URL("file://" + directory.getCanonicalPath());
  411. } catch (MalformedURLException e) {
  412. e.printStackTrace();
  413. // ignore: continue to the next classpath entry
  414. } catch (IOException e) {
  415. e.printStackTrace();
  416. // ignore: continue to the next classpath entry
  417. }
  418. }
  419. }
  420. return null;
  421. }
  422. /**
  423. * Test method for helper tool
  424. */
  425. public static void main(String[] args) {
  426. Collection<Class<? extends Paintable>> paintables = ClassPathExplorer
  427. .getPaintablesHavingWidgetAnnotation();
  428. logger.info("Found annotated paintables:");
  429. for (Class<? extends Paintable> cls : paintables) {
  430. logger.info(cls.getCanonicalName());
  431. }
  432. logger.info("");
  433. logger.info("Searching available widgetsets...");
  434. Map<String, URL> availableWidgetSets = ClassPathExplorer
  435. .getAvailableWidgetSets();
  436. for (String string : availableWidgetSets.keySet()) {
  437. logger.info(string + " in " + availableWidgetSets.get(string));
  438. }
  439. }
  440. }