001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: package org.apache.jorphan.reflect;
020:
021: import java.io.File;
022: import java.io.FilenameFilter;
023: import java.io.IOException;
024: import java.lang.reflect.Modifier;
025: import java.util.ArrayList;
026: import java.util.Enumeration;
027: import java.util.HashSet;
028: import java.util.Iterator;
029: import java.util.List;
030: import java.util.Set;
031: import java.util.StringTokenizer;
032: import java.util.TreeSet;
033: import java.util.zip.ZipFile;
034:
035: import org.apache.jorphan.logging.LoggingManager;
036: import org.apache.jorphan.util.JOrphanUtils;
037: import org.apache.log.Logger;
038:
039: /**
040: * This class finds classes that extend one of a set of parent classes
041: *
042: * @author Burt Beckwith
043: * @author Michael Stover (mstover1 at apache.org)
044: */
045: public final class ClassFinder {
046: private static final Logger log = LoggingManager
047: .getLoggerForClass();
048:
049: private static final String DOT_JAR = ".jar"; // $NON-NLS-1$
050: private static final String DOT_CLASS = ".class"; // $NON-NLS-1$
051: private static final int DOT_CLASS_LEN = DOT_CLASS.length();
052:
053: // static only
054: private ClassFinder() {
055: }
056:
057: /**
058: * Filter updates to TreeSet by only storing classes
059: * that extend one of the parent classes
060: *
061: *
062: */
063: private static class FilterTreeSet extends TreeSet {
064: private final Class[] parents; // parent classes to check
065: private final boolean inner; // are inner classes OK?
066:
067: private final transient ClassLoader contextClassLoader = Thread
068: .currentThread().getContextClassLoader(); // Potentially expensive; do it once
069:
070: FilterTreeSet(Class[] parents, boolean inner) {
071: super ();
072: this .parents = parents;
073: this .inner = inner;
074: }
075:
076: /**
077: * Override the superclass so we only add classnames that
078: * meet the criteria.
079: *
080: * @param o - classname (must be a String)
081: * @return true if it is a new entry
082: *
083: * @see java.util.TreeSet#add(java.lang.Object)
084: */
085: public boolean add(Object o) {
086: if (contains(o))
087: return false;// No need to check it again
088: String s = (String) o;// we only expect Strings
089: if ((s.indexOf("$") == -1) || inner) { // $NON-NLS-1$
090: if (isChildOf(parents, s, contextClassLoader)) {
091: return super .add(s);
092: }
093: }
094: return false;
095: }
096: }
097:
098: /**
099: * Convenience method for
100: * <code>findClassesThatExtend(Class[], boolean)</code>
101: * with the option to include inner classes in the search set to false.
102: *
103: * @return List containing discovered classes.
104: */
105: public static List findClassesThatExtend(String[] paths,
106: Class[] super Classes) throws IOException {
107: return findClassesThatExtend(paths, super Classes, false);
108: }
109:
110: // For each directory in the search path, add all the jars found there
111: private static String[] addJarsInPath(String[] paths) {
112: Set fullList = new HashSet();
113: for (int i = 0; i < paths.length; i++) {
114: final String path = paths[i];
115: fullList.add(path); // Keep the unexpanded path
116: // TODO - allow directories to end with .jar by removing this check?
117: if (!path.endsWith(DOT_JAR)) {
118: File dir = new File(path);
119: if (dir.exists() && dir.isDirectory()) {
120: String[] jars = dir.list(new FilenameFilter() {
121: public boolean accept(File f, String name) {
122: return name.endsWith(DOT_JAR);
123: }
124: });
125: for (int x = 0; x < jars.length; x++) {
126: fullList.add(jars[x]);
127: }
128: }
129: }
130: }
131: return (String[]) fullList.toArray(new String[0]);
132: }
133:
134: /**
135: * Find classes in the provided path(s)/jar(s) that extend the class(es).
136: * @param strPathsOrJars - pathnames or jarfiles to search for classes
137: * @param superClasses - required parent class(es)
138: * @param innerClasses - should we include inner classes?
139: *
140: * @return List containing discovered classes
141: */
142: public static List findClassesThatExtend(String[] strPathsOrJars,
143: final Class[] super Classes, final boolean innerClasses)
144: throws IOException {
145:
146: if (log.isDebugEnabled()) {
147: for (int i = 0; i < super Classes.length; i++) {
148: log.debug("superclass: " + super Classes[i].getName());
149: }
150: }
151:
152: // Find all jars in the search path
153: strPathsOrJars = addJarsInPath(strPathsOrJars);
154: for (int k = 0; k < strPathsOrJars.length; k++) {
155: strPathsOrJars[k] = fixPathEntry(strPathsOrJars[k]);
156: if (log.isDebugEnabled()) {
157: log.debug("strPathsOrJars : " + strPathsOrJars[k]);
158: }
159: }
160:
161: // Now eliminate any classpath entries that do not "match" the search
162: List listPaths = getClasspathMatches(strPathsOrJars);
163: if (log.isDebugEnabled()) {
164: Iterator tIter = listPaths.iterator();
165: while (tIter.hasNext()) {
166: log.debug("listPaths : " + tIter.next());
167: }
168: }
169:
170: Set listClasses = new FilterTreeSet(super Classes, innerClasses);
171: // first get all the classes
172: findClassesInPaths(listPaths, listClasses);
173: if (log.isDebugEnabled()) {
174: log.debug("listClasses.size()=" + listClasses.size());
175: Iterator tIter = listClasses.iterator();
176: while (tIter.hasNext()) {
177: log.debug("listClasses : " + tIter.next());
178: }
179: }
180:
181: // // Now keep only the required classes
182: // Set subClassList = findAllSubclasses(superClasses, listClasses, innerClasses);
183: // if (log.isDebugEnabled()) {
184: // log.debug("subClassList.size()="+subClassList.size());
185: // Iterator tIter = subClassList.iterator();
186: // while (tIter.hasNext()) {
187: // log.debug("subClassList : " + tIter.next());
188: // }
189: // }
190:
191: return new ArrayList(listClasses);//subClassList);
192: }
193:
194: /*
195: * Returns the classpath entries that match the search list of jars and paths
196: */
197: private static List getClasspathMatches(String[] strPathsOrJars) {
198: log.debug("Classpath = "
199: + System.getProperty("java.class.path")); // $NON-NLS-1$
200: StringTokenizer stPaths = new StringTokenizer(System
201: .getProperty("java.class.path"), // $NON-NLS-1$
202: System.getProperty("path.separator")); // $NON-NLS-1$
203: if (log.isDebugEnabled()) {
204: for (int i = 0; i < strPathsOrJars.length; i++) {
205: log.debug("strPathsOrJars[" + i + "] : "
206: + strPathsOrJars[i]);
207: }
208: }
209:
210: // find all jar files or paths that end with strPathOrJar
211: ArrayList listPaths = new ArrayList();
212: String strPath = null;
213: while (stPaths.hasMoreTokens()) {
214: strPath = fixPathEntry(stPaths.nextToken());
215: if (strPathsOrJars == null) {
216: log.debug("Adding: " + strPath);
217: listPaths.add(strPath);
218: } else {
219: boolean found = false;
220: for (int i = 0; i < strPathsOrJars.length; i++) {
221: if (strPath.endsWith(strPathsOrJars[i])) {
222: found = true;
223: log.debug("Adding " + strPath + " found at "
224: + i);
225: listPaths.add(strPath);
226: break;// no need to look further
227: }
228: }
229: if (!found) {
230: log.debug("Did not find: " + strPath);
231: }
232: }
233: }
234: return listPaths;
235: }
236:
237: /**
238: * Fix a path:
239: * - replace "." by current directory
240: * - trim any trailing spaces
241: * - replace \ by /
242: * - replace // by /
243: * - remove all trailing /
244: */
245: private static String fixPathEntry(String path) {
246: if (path == null)
247: return null;
248: if (path.equals(".")) { // $NON-NLS-1$
249: return System.getProperty("user.dir"); // $NON-NLS-1$
250: }
251: path = path.trim().replace('\\', '/'); // $NON-NLS-1$ // $NON-NLS-2$
252: path = JOrphanUtils.substitute(path, "//", "/"); // $NON-NLS-1$// $NON-NLS-2$
253:
254: while (path.endsWith("/")) { // $NON-NLS-1$
255: path = path.substring(0, path.length() - 1);
256: }
257: return path;
258: }
259:
260: /*
261: * NOTUSED * Determine if the class implements the interface.
262: *
263: * @param theClass
264: * the class to check
265: * @param theInterface
266: * the interface to look for
267: * @return boolean true if it implements
268: *
269: * private static boolean classImplementsInterface( Class theClass, Class
270: * theInterface) { HashMap mapInterfaces = new HashMap(); String strKey =
271: * null; // pass in the map by reference since the method is recursive
272: * getAllInterfaces(theClass, mapInterfaces); Iterator iterInterfaces =
273: * mapInterfaces.keySet().iterator(); while (iterInterfaces.hasNext()) {
274: * strKey = (String) iterInterfaces.next(); if (mapInterfaces.get(strKey) ==
275: * theInterface) { return true; } } return false; }
276: */
277:
278: /*
279: * Finds all classes that extend the classes in the listSuperClasses
280: * ArrayList, searching in the listAllClasses ArrayList.
281: *
282: * @param superClasses
283: * the base classes to find subclasses for
284: * @param listAllClasses
285: * the collection of classes to search in
286: * @param innerClasses
287: * indicate whether to include inner classes in the search
288: * @return ArrayList of the subclasses
289: */
290: // private static Set findAllSubclasses(Class []superClasses, Set listAllClasses, boolean innerClasses) {
291: // Set listSubClasses = new TreeSet();
292: // for (int i=0; i< superClasses.length; i++) {
293: // findAllSubclassesOneClass(superClasses[i], listAllClasses, listSubClasses, innerClasses);
294: // }
295: // return listSubClasses;
296: // }
297: /*
298: * Finds all classes that extend the class, searching in the listAllClasses
299: * ArrayList.
300: *
301: * @param theClass
302: * the parent class
303: * @param listAllClasses
304: * the collection of classes to search in
305: * @param listSubClasses
306: * the collection of discovered subclasses
307: * @param innerClasses
308: * indicates whether inners classes should be included in the
309: * search
310: */
311: // private static void findAllSubclassesOneClass(Class theClass, Set listAllClasses, Set listSubClasses,
312: // boolean innerClasses) {
313: // Iterator iterClasses = listAllClasses.iterator();
314: // while (iterClasses.hasNext()) {
315: // String strClassName = (String) iterClasses.next();
316: // // only check classes if they are not inner classes
317: // // or we intend to check for inner classes
318: // if ((strClassName.indexOf("$") == -1) || innerClasses) { // $NON-NLS-1$
319: // // might throw an exception, assume this is ignorable
320: // try {
321: // Class c = Class.forName(strClassName, false, Thread.currentThread().getContextClassLoader());
322: //
323: // if (!c.isInterface() && !Modifier.isAbstract(c.getModifiers())) {
324: // if(theClass.isAssignableFrom(c)){
325: // listSubClasses.add(strClassName);
326: // }
327: // }
328: // } catch (Throwable ignored) {
329: // log.debug(ignored.getLocalizedMessage());
330: // }
331: // }
332: // }
333: // }
334: /**
335: *
336: * @param parentClasses list of classes to check for
337: * @param strClassName name of class to be checked
338: * @param innerClasses should we allow inner classes?
339: * @param contextClassLoader the classloader to use
340: * @return
341: */
342: private static boolean isChildOf(Class[] parentClasses,
343: String strClassName, ClassLoader contextClassLoader) {
344: // might throw an exception, assume this is ignorable
345: try {
346: Class c = Class.forName(strClassName, false,
347: contextClassLoader);
348:
349: if (!c.isInterface()
350: && !Modifier.isAbstract(c.getModifiers())) {
351: for (int i = 0; i < parentClasses.length; i++) {
352: if (parentClasses[i].isAssignableFrom(c)) {
353: return true;
354: }
355: }
356: }
357: } catch (Throwable ignored) {
358: log.debug(ignored.getLocalizedMessage());
359: }
360: return false;
361: }
362:
363: /*
364: * Converts a class file from the text stored in a Jar file to a version
365: * that can be used in Class.forName().
366: *
367: * @param strClassName
368: * the class name from a Jar file
369: * @return String the Java-style dotted version of the name
370: */
371: private static String fixClassName(String strClassName) {
372: strClassName = strClassName.replace('\\', '.'); // $NON-NLS-1$ // $NON-NLS-2$
373: strClassName = strClassName.replace('/', '.'); // $NON-NLS-1$ // $NON-NLS-2$
374: // remove ".class"
375: strClassName = strClassName.substring(0, strClassName.length()
376: - DOT_CLASS_LEN);
377: return strClassName;
378: }
379:
380: private static void findClassesInOnePath(String strPath,
381: Set listClasses) throws IOException {
382: File file = new File(strPath);
383: if (file.isDirectory()) {
384: findClassesInPathsDir(strPath, file, listClasses);
385: } else if (file.exists()) {
386: ZipFile zipFile = new ZipFile(file);
387: Enumeration entries = zipFile.entries();
388: while (entries.hasMoreElements()) {
389: String strEntry = entries.nextElement().toString();
390: if (strEntry.endsWith(DOT_CLASS)) {
391: listClasses.add(fixClassName(strEntry));
392: }
393: }
394: }
395: }
396:
397: private static void findClassesInPaths(List listPaths,
398: Set listClasses) throws IOException {
399: Iterator iterPaths = listPaths.iterator();
400: while (iterPaths.hasNext()) {
401: findClassesInOnePath((String) iterPaths.next(), listClasses);
402: }
403: }
404:
405: private static void findClassesInPathsDir(String strPathElement,
406: File dir, Set listClasses) throws IOException {
407: String[] list = dir.list();
408: for (int i = 0; i < list.length; i++) {
409: File file = new File(dir, list[i]);
410: if (file.isDirectory()) {
411: // Recursive call
412: findClassesInPathsDir(strPathElement, file, listClasses);
413: } else if (list[i].endsWith(DOT_CLASS) && file.exists()
414: && (file.length() != 0)) {
415: final String path = file.getPath();
416: listClasses.add(path.substring(
417: strPathElement.length() + 1,
418: path.lastIndexOf(".")) // $NON-NLS-1$
419: .replace(File.separator.charAt(0), '.')); // $NON-NLS-1$
420: }
421: }
422: }
423: }
|