View Javadoc

1   /*
2    * Created on Feb 5, 2005
3    *
4    * TODO To change the template for this generated file go to
5    * Window - Preferences - Java - Code Style - Code Templates
6    */
7   package net.sf.gumshoe.indexer;
8   
9   import java.io.File;
10  import java.io.FilenameFilter;
11  import java.io.IOException;
12  import java.lang.reflect.Modifier;
13  import java.util.ArrayList;
14  import java.util.Enumeration;
15  import java.util.HashSet;
16  import java.util.Iterator;
17  import java.util.List;
18  import java.util.Set;
19  import java.util.StringTokenizer;
20  import java.util.zip.ZipFile;
21  
22  /***
23   * @author Gabor
24   * 
25   * TODO To change the template for this generated type comment go to Window -
26   * Preferences - Java - Code Style - Code Templates
27   */
28  public class FindClasses {
29  
30  	/***
31  	 * 
32  	 */
33  	private FindClasses() {
34  		super();
35  		// TODO Auto-generated constructor stub
36  	}
37  
38  	/***
39  	 * Convenience method that finds classes on the standard java classpath
40  	 * 
41  	 * @param superClass
42  	 * @return a List of classes extending superClass
43  	 * @throws IOException
44  	 * @throws ClassNotFoundException
45  	 */
46  	public static List findClassesThatExtend(Class superClass)
47  			throws IOException, ClassNotFoundException {
48  		String javaClassPath = System.getProperty("java.class.path");
49  		String paths[] = javaClassPath.split("" + File.pathSeparatorChar);
50  		Class superClasses[] = new Class[1];
51  		superClasses[0] = superClass;
52  		return findClassesThatExtend(paths, superClasses);
53  	}
54  
55  	/***
56  	 * Convenience method for <code>findClassesThatExtend(Class[],
57  	 * boolean)</code>
58  	 * with the option to include inner classes in the search set to false.
59  	 * 
60  	 * @return ArrayList containing discovered classes.
61  	 */
62  	public static List findClassesThatExtend(String[] paths,
63  			Class[] superClasses) throws IOException, ClassNotFoundException {
64  		return findClassesThatExtend(paths, superClasses, false);
65  	}
66  
67  	public static List findClassesThatExtend(String[] strPathsOrJars,
68  			Class[] superClasses, boolean innerClasses) throws IOException,
69  			ClassNotFoundException {
70  		List listPaths = null;
71  		ArrayList listClasses = null;
72  		List listSuperClasses = null;
73  		strPathsOrJars = addJarsInPath(strPathsOrJars);
74  		listPaths = getClasspathMatches(strPathsOrJars);
75  		listClasses = new ArrayList();
76  		listSuperClasses = new ArrayList();
77  		for (int i = 0; i < superClasses.length; i++) {
78  			listSuperClasses.add(superClasses[i].getName());
79  		}
80  		// first get all the classes
81  		findClassesInPaths(listPaths, listClasses);
82  		List subClassList = findAllSubclasses(listSuperClasses, listClasses,
83  				innerClasses);
84  		return subClassList;
85  	}
86  
87  	/***
88  	 * Find classes in the provided path(s)/jar(s) that extend the class(es).
89  	 * 
90  	 * @return ArrayList containing discovered classes
91  	 */
92  	private static String[] addJarsInPath(String[] paths) {
93  		Set fullList = new HashSet();
94  		for (int i = 0; i < paths.length; i++) {
95  			fullList.add(paths[i]);
96  			if (!paths[i].endsWith(".jar")) {
97  				File dir = new File(paths[i]);
98  				if (dir.exists() && dir.isDirectory()) {
99  					String[] jars = dir.list(new FilenameFilter() {
100 						public boolean accept(File f, String name) {
101 							if (name.endsWith(".jar")) {
102 								return true;
103 							}
104 							return false;
105 						}
106 					});
107 					for (int x = 0; x < jars.length; x++) {
108 						fullList.add(jars[x]);
109 					}
110 				}
111 			}
112 		}
113 		return (String[]) fullList.toArray(new String[0]);
114 	}
115 
116 	private static List getClasspathMatches(String[] strPathsOrJars) {
117 		ArrayList listPaths = null;
118 		StringTokenizer stPaths = null;
119 		String strPath = null;
120 		int i;
121 		listPaths = new ArrayList();
122 		// log.debug("Classpath = " + System.getProperty("java.class.path"));
123 		stPaths = new StringTokenizer(System.getProperty("java.class.path"),
124 				System.getProperty("path.separator"));
125 		if (strPathsOrJars != null) {
126 			strPathsOrJars = fixDotDirs(strPathsOrJars);
127 			strPathsOrJars = fixSlashes(strPathsOrJars);
128 			strPathsOrJars = fixEndingSlashes(strPathsOrJars);
129 		}
130 		/*
131 		 * if (log.isDebugEnabled()) { for (i = 0; i < strPathsOrJars.length;
132 		 * i++) { log.debug("strPathsOrJars[" + i + "] : " + strPathsOrJars[i]); } }
133 		 */
134 		// find all jar files or paths that end with strPathOrJar
135 		while (stPaths.hasMoreTokens()) {
136 			strPath = fixDotDir((String) stPaths.nextToken());
137 			strPath = fixSlashes(strPath);
138 			strPath = fixEndingSlashes(strPath);
139 			if (strPathsOrJars == null) {
140 				// log.debug("Adding: " + strPath);
141 				listPaths.add(strPath);
142 			} else {
143 				boolean found = false;
144 				for (i = 0; i < strPathsOrJars.length; i++) {
145 					if (strPath.endsWith(strPathsOrJars[i])) {
146 						found = true;
147 						// log.debug("Adding "+strPath+" found at "+i);
148 						listPaths.add(strPath);
149 						break;// no need to look further
150 					}
151 				}
152 				if (!found) {
153 					// log.debug("Did not find: "+strPath);
154 				}
155 			}
156 		}
157 		return listPaths;
158 	}
159 
160 	private static void findClassesInPaths(List listPaths, List listClasses)
161 			throws IOException {
162 		Iterator iterPaths = listPaths.iterator();
163 		while (iterPaths.hasNext()) {
164 			findClassesInOnePath((String) iterPaths.next(), listClasses);
165 		}
166 	}
167 
168 	/***
169 	 * Finds all classes that extend the class, searching in the listAllClasses
170 	 * ArrayList.
171 	 * 
172 	 * @param theClass
173 	 *            the parent class
174 	 * @param listAllClasses
175 	 *            the collection of classes to search in
176 	 * @param listSubClasses
177 	 *            the collection of discovered subclasses
178 	 * @param innerClasses
179 	 *            indicates whether inners classes should be included in the
180 	 *            search
181 	 */
182 	private static void findAllSubclassesOneClass(Class theClass,
183 			List listAllClasses, List listSubClasses, boolean innerClasses) {
184 		Iterator iterClasses = null;
185 		String strClassName = null;
186 		Class c = null;
187 		boolean bIsSubclass = false;
188 		iterClasses = listAllClasses.iterator();
189 		while (iterClasses.hasNext()) {
190 			strClassName = (String) iterClasses.next();
191 			// only check classes if they are not inner classes
192 			// or we intend to check for inner classes
193 			if ((strClassName.indexOf("$") == -1) || innerClasses) {
194 				// might throw an exception, assume this is ignorable
195 				try {
196 					c = Class.forName(strClassName, false, Thread
197 							.currentThread().getContextClassLoader());
198 
199 					if (!c.isInterface()
200 							&& !Modifier.isAbstract(c.getModifiers())) {
201 						bIsSubclass = theClass.isAssignableFrom(c);
202 					} else {
203 						bIsSubclass = false;
204 					}
205 					if (bIsSubclass) {
206 						listSubClasses.add(strClassName);
207 					}
208 				} catch (Throwable ignored) {
209 				}
210 			}
211 		}
212 	}
213 
214     /***
215      * Finds all classes that extend the classes in the listSuperClasses
216      * ArrayList, searching in the listAllClasses ArrayList.
217      *
218      * @param  listSuperClasses  the base classes to find subclasses for
219      * @param  listAllClasses    the collection of classes to search in
220      * @param  innerClasses      indicate whether to include inner classes in
221      *                           the search
222      *@return                    ArrayList of the subclasses
223      */
224     private static ArrayList findAllSubclasses(
225         List listSuperClasses,
226         List listAllClasses,
227         boolean innerClasses)
228     {
229         Iterator iterClasses = null;
230         ArrayList listSubClasses = null;
231         String strClassName = null;
232         Class tempClass = null;
233         listSubClasses = new ArrayList();
234         iterClasses = listSuperClasses.iterator();
235         while (iterClasses.hasNext())
236         {
237             strClassName = (String) iterClasses.next();
238             // only check classes if they are not inner classes
239             // or we intend to check for inner classes
240             if ((strClassName.indexOf("$") == -1) || innerClasses)
241             {
242                 // might throw an exception, assume this is ignorable
243                 try
244                 {
245                     tempClass =
246                         Class.forName(
247                             strClassName,
248                             false,
249                             Thread.currentThread().getContextClassLoader());
250                     findAllSubclassesOneClass(
251                         tempClass,
252                         listAllClasses,
253                         listSubClasses,
254                         innerClasses);
255                     // call by reference - recursive
256                 }
257                 catch (Throwable ignored)
258                 {
259                 }
260             }
261         }
262         return listSubClasses;
263     }
264     
265 	private static void findClassesInOnePath(String strPath, List listClasses)
266 			throws IOException {
267 		File file = null;
268 		ZipFile zipFile = null;
269 		Enumeration entries = null;
270 		String strEntry = null;
271 		file = new File(strPath);
272 		if (file.isDirectory()) {
273 			findClassesInPathsDir(strPath, file, listClasses);
274 		} else if (file.exists()) {
275 			zipFile = new ZipFile(file);
276 			entries = zipFile.entries();
277 			while (entries.hasMoreElements()) {
278 				strEntry = entries.nextElement().toString();
279 				if (strEntry.endsWith(".class")) {
280 					listClasses.add(fixClassName(strEntry));
281 				}
282 			}
283 		}
284 	}
285 
286 	/***
287 	 * Converts a class file from the text stored in a Jar file to a version
288 	 * that can be used in Class.forName().
289 	 * 
290 	 * @param strClassName
291 	 *            the class name from a Jar file
292 	 * @return String the Java-style dotted version of the name
293 	 */
294 	private static String fixClassName(String strClassName) {
295 		strClassName = strClassName.replace('//', '.');
296 		strClassName = strClassName.replace('/', '.');
297 		strClassName = strClassName.substring(0, strClassName.length() - 6);
298 		// remove ".class"
299 		return strClassName;
300 	}
301 
302 	private static void findClassesInPathsDir(String strPathElement, File dir,
303 			List listClasses) throws IOException {
304 		File file = null;
305 		String[] list = dir.list();
306 		for (int i = 0; i < list.length; i++) {
307 			file = new File(dir, list[i]);
308 			if (file.isDirectory()) {
309 				findClassesInPathsDir(strPathElement, file, listClasses);
310 			} else if (file.exists() && (file.length() != 0)
311 					&& list[i].endsWith(".class")) {
312 				listClasses.add(file.getPath().substring(
313 						strPathElement.length() + 1,
314 						file.getPath().lastIndexOf(".")).replace(
315 						File.separator.charAt(0), '.'));
316 			}
317 		}
318 	}
319 
320 	private static String fixDotDir(String path) {
321 		if (path == null)
322 			return null;
323 		if (path.equals(".")) {
324 			return System.getProperty("user.dir");
325 		} else {
326 			return path.trim();
327 		}
328 	}
329 
330     private static String[] fixDotDirs(String[] paths)
331     {
332         for (int i = 0; i < paths.length; i++)
333         {
334             paths[i] = fixDotDir(paths[i]);
335         }
336         return paths;
337     }
338 
339     private static String[] fixEndingSlashes(String[] strings) {
340 		String[] strNew = new String[strings.length];
341 		for (int i = 0; i < strings.length; i++) {
342 			strNew[i] = fixEndingSlashes(strings[i]);
343 		}
344 		return strNew;
345 	}
346 
347 	private static String fixEndingSlashes(String string) {
348 		if (string.endsWith("/") || string.endsWith("//")) {
349 			string = string.substring(0, string.length() - 1);
350 			string = fixEndingSlashes(string);
351 		}
352 		return string;
353 	}
354 
355 	private static String[] fixSlashes(String[] strings) {
356 		String[] strNew = new String[strings.length];
357 		for (int i = 0; i < strings.length; i++) {
358 			strNew[i] = fixSlashes(strings[i]) /* .toLowerCase() */;
359 		}
360 		return strNew;
361 	}
362 
363 	private static String fixSlashes(String str) {
364 		// replace \ with /
365 		str = str.replace('//', '/');
366 		// compress multiples into singles;
367 		// do in 2 steps with dummy string
368 		// to avoid infinte loop
369 		str = replaceString(str, "//", "_____");
370 		str = replaceString(str, "_____", "/");
371 		return str;
372 	}
373 
374 	private static String replaceString(String s, String strToFind,
375 			String strToReplace) {
376 		int index;
377 		int currentPos;
378 		StringBuffer buffer = null;
379 		if (s.indexOf(strToFind) == -1) {
380 			return s;
381 		}
382 		currentPos = 0;
383 		buffer = new StringBuffer();
384 		while (true) {
385 			index = s.indexOf(strToFind, currentPos);
386 			if (index == -1) {
387 				break;
388 			}
389 			buffer.append(s.substring(currentPos, index));
390 			buffer.append(strToReplace);
391 			currentPos = index + strToFind.length();
392 		}
393 		buffer.append(s.substring(currentPos));
394 		return buffer.toString();
395 	}
396 }