1. /*
  2. * Copyright 2001-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of 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,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. *
  16. */
  17. package org.apache.tools.ant.taskdefs.optional.sitraka.bytecode;
  18. import java.io.BufferedInputStream;
  19. import java.io.ByteArrayInputStream;
  20. import java.io.ByteArrayOutputStream;
  21. import java.io.File;
  22. import java.io.FileInputStream;
  23. import java.io.FilenameFilter;
  24. import java.io.IOException;
  25. import java.io.InputStream;
  26. import java.util.Enumeration;
  27. import java.util.Hashtable;
  28. import java.util.NoSuchElementException;
  29. import java.util.StringTokenizer;
  30. import java.util.Vector;
  31. import java.util.zip.ZipEntry;
  32. import java.util.zip.ZipFile;
  33. /**
  34. * Core of the bytecode analyzer. It loads classes from a given classpath.
  35. *
  36. */
  37. public class ClassPathLoader {
  38. public static final FileLoader NULL_LOADER = new NullLoader();
  39. /** the list of files to look for */
  40. private File[] files;
  41. /**
  42. * create a new instance with a given classpath. It must be urls
  43. * separated by the platform specific path separator.
  44. * @param classPath the classpath to load all the classes from.
  45. */
  46. public ClassPathLoader(String classPath) {
  47. StringTokenizer st = new StringTokenizer(classPath, File.pathSeparator);
  48. Vector entries = new Vector();
  49. while (st.hasMoreTokens()) {
  50. File file = new File(st.nextToken());
  51. entries.addElement(file);
  52. }
  53. files = new File[entries.size()];
  54. entries.copyInto(files);
  55. }
  56. /**
  57. * create a new instance with a given set of urls.
  58. * @param entries valid file urls (either .jar, .zip or directory)
  59. */
  60. public ClassPathLoader(String[] entries) {
  61. files = new File[entries.length];
  62. for (int i = 0; i < entries.length; i++) {
  63. files[i] = new File(entries[i]);
  64. }
  65. }
  66. /**
  67. * create a new instance with a given set of urls
  68. * @param entries file urls to look for classes (.jar, .zip or directory)
  69. */
  70. public ClassPathLoader(File[] entries) {
  71. files = entries;
  72. }
  73. /** the interface to implement to look up for specific resources */
  74. public interface FileLoader {
  75. /** the file url that is looked for .class files */
  76. File getFile();
  77. /** return the set of classes found in the file */
  78. ClassFile[] getClasses() throws IOException;
  79. }
  80. /**
  81. * @return the set of <tt>FileLoader</tt> loaders matching the given classpath.
  82. */
  83. public Enumeration loaders() {
  84. return new LoaderEnumeration();
  85. }
  86. /**
  87. * return the whole set of classes in the classpath. Note that this method
  88. * can be very resource demanding since it must load all bytecode from
  89. * all classes in all resources in the classpath at a time.
  90. * To process it in a less resource demanding way, it is maybe better to
  91. * use the <tt>loaders()</tt> that will return loader one by one.
  92. *
  93. * @return the hashtable containing ALL classes that are found in the given
  94. * classpath. Note that the first entry of a given classname will shadow
  95. * classes with the same name (as a classloader does)
  96. */
  97. public Hashtable getClasses() throws IOException {
  98. Hashtable map = new Hashtable();
  99. Enumeration e = loaders();
  100. while (e.hasMoreElements()) {
  101. FileLoader loader = (FileLoader) e.nextElement();
  102. System.out.println("Processing " + loader.getFile());
  103. long t0 = System.currentTimeMillis();
  104. ClassFile[] classes = loader.getClasses();
  105. long dt = System.currentTimeMillis() - t0;
  106. System.out.println("" + classes.length + " classes loaded in " + dt + "ms");
  107. for (int j = 0; j < classes.length; j++) {
  108. String name = classes[j].getFullName();
  109. // do not allow duplicates entries to preserve 'classpath' behavior
  110. // first class in wins
  111. if (!map.containsKey(name)) {
  112. map.put(name, classes[j]);
  113. }
  114. }
  115. }
  116. return map;
  117. }
  118. /** the loader enumeration that will return loaders */
  119. private class LoaderEnumeration implements Enumeration {
  120. private int index = 0;
  121. public boolean hasMoreElements() {
  122. return index < files.length;
  123. }
  124. public Object nextElement() {
  125. if (index >= files.length) {
  126. throw new NoSuchElementException();
  127. }
  128. File file = files[index++];
  129. if (!file.exists()) {
  130. return new NullLoader(file);
  131. }
  132. if (file.isDirectory()) {
  133. // it's a directory
  134. return new DirectoryLoader(file);
  135. } else if (file.getName().endsWith(".zip") || file.getName().endsWith(".jar")) {
  136. // it's a jar/zip file
  137. return new JarLoader(file);
  138. }
  139. return new NullLoader(file);
  140. }
  141. }
  142. /**
  143. * useful methods to read the whole input stream in memory so that
  144. * it can be accessed faster. Processing rt.jar and tools.jar from JDK 1.3.1
  145. * brings time from 50s to 7s.
  146. */
  147. public static InputStream getCachedStream(InputStream is) throws IOException {
  148. final InputStream bis = new BufferedInputStream(is);
  149. final byte[] buffer = new byte[8192];
  150. final ByteArrayOutputStream bos = new ByteArrayOutputStream(2048);
  151. int n;
  152. bos.reset();
  153. while ((n = bis.read(buffer, 0, buffer.length)) != -1) {
  154. bos.write(buffer, 0, n);
  155. }
  156. is.close();
  157. return new ByteArrayInputStream(bos.toByteArray());
  158. }
  159. }
  160. /** a null loader to return when the file is not valid */
  161. final class NullLoader implements ClassPathLoader.FileLoader {
  162. private File file;
  163. NullLoader() {
  164. this(null);
  165. }
  166. NullLoader(File file) {
  167. this.file = file;
  168. }
  169. public File getFile() {
  170. return file;
  171. }
  172. public ClassFile[] getClasses() throws IOException {
  173. return new ClassFile[0];
  174. }
  175. }
  176. /**
  177. * jar loader specified in looking for classes in jar and zip
  178. * @todo read the jar manifest in case there is a Class-Path
  179. * entry.
  180. */
  181. final class JarLoader implements ClassPathLoader.FileLoader {
  182. private File file;
  183. JarLoader(File file) {
  184. this.file = file;
  185. }
  186. public File getFile() {
  187. return file;
  188. }
  189. public ClassFile[] getClasses() throws IOException {
  190. ZipFile zipFile = new ZipFile(file);
  191. Vector v = new Vector();
  192. Enumeration entries = zipFile.entries();
  193. while (entries.hasMoreElements()) {
  194. ZipEntry entry = (ZipEntry) entries.nextElement();
  195. if (entry.getName().endsWith(".class")) {
  196. InputStream is = ClassPathLoader.getCachedStream(zipFile.getInputStream(entry));
  197. ClassFile classFile = new ClassFile(is);
  198. is.close();
  199. v.addElement(classFile);
  200. }
  201. }
  202. ClassFile[] classes = new ClassFile[v.size()];
  203. v.copyInto(classes);
  204. return classes;
  205. }
  206. }
  207. /**
  208. * directory loader that will look all classes recursively
  209. * @todo should discard classes which package name does not
  210. * match the directory ?
  211. */
  212. final class DirectoryLoader implements ClassPathLoader.FileLoader {
  213. private File directory;
  214. private static final FilenameFilter DIRECTORY_FILTER = new DirectoryFilter();
  215. private static final FilenameFilter CLASS_FILTER = new ClassFilter();
  216. DirectoryLoader(File dir) {
  217. directory = dir;
  218. }
  219. public File getFile() {
  220. return directory;
  221. }
  222. public ClassFile[] getClasses() throws IOException {
  223. Vector v = new Vector(127);
  224. Vector files = listFiles(directory, CLASS_FILTER, true);
  225. final int filesCount = files.size();
  226. for (int i = 0; i < filesCount; i++) {
  227. File file = (File) files.elementAt(i);
  228. InputStream is = null;
  229. try {
  230. is = ClassPathLoader.getCachedStream(new FileInputStream(file));
  231. ClassFile classFile = new ClassFile(is);
  232. is.close();
  233. is = null;
  234. v.addElement(classFile);
  235. } finally {
  236. if (is != null) {
  237. try {
  238. is.close();
  239. } catch (IOException ignored) {
  240. }
  241. }
  242. }
  243. }
  244. ClassFile[] classes = new ClassFile[v.size()];
  245. v.copyInto(classes);
  246. return classes;
  247. }
  248. /**
  249. * List files that obeys to a specific filter recursively from a given base
  250. * directory.
  251. * @param directory the directory where to list the files from.
  252. * @param filter the file filter to apply
  253. * @param recurse tells whether or not the listing is recursive.
  254. * @return the list of <tt>File</tt> objects that applies to the given
  255. * filter.
  256. */
  257. public static Vector listFiles(File directory, FilenameFilter filter, boolean recurse) {
  258. if (!directory.isDirectory()) {
  259. throw new IllegalArgumentException(directory + " is not a directory");
  260. }
  261. Vector list = new Vector(512);
  262. listFilesTo(list, directory, filter, recurse);
  263. return list;
  264. }
  265. /**
  266. * List and add files to a given list. As a convenience it sends back the
  267. * instance of the list given as a parameter.
  268. * @param list the list of files where the filtered files should be added
  269. * @param directory the directory where to list the files from.
  270. * @param filter the file filter to apply
  271. * @param recurse tells whether or not the listing is recursive.
  272. * @return the list instance that was passed as the <tt>list</tt> argument.
  273. */
  274. private static Vector listFilesTo(Vector list, File directory,
  275. FilenameFilter filter, boolean recurse) {
  276. String[] files = directory.list(filter);
  277. for (int i = 0; i < files.length; i++) {
  278. list.addElement(new File(directory, files[i]));
  279. }
  280. files = null; // we don't need it anymore
  281. if (recurse) {
  282. String[] subdirs = directory.list(DIRECTORY_FILTER);
  283. for (int i = 0; i < subdirs.length; i++) {
  284. listFilesTo(list, new File(directory, subdirs[i]), filter, recurse);
  285. }
  286. }
  287. return list;
  288. }
  289. }
  290. /** Convenient filter that accepts only directory <tt>File</tt> */
  291. final class DirectoryFilter implements FilenameFilter {
  292. public boolean accept(File directory, String name) {
  293. File pathname = new File(directory, name);
  294. return pathname.isDirectory();
  295. }
  296. }
  297. /** convenient filter to accept only .class files */
  298. final class ClassFilter implements FilenameFilter {
  299. public boolean accept(File dir, String name) {
  300. return name.endsWith(".class");
  301. }
  302. }