ClassFinder.java Source code

Java tutorial

Introduction

Here is the source code for ClassFinder.java

Source

//package com.webex.ta.hydra.util;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeSet;
import java.util.zip.ZipFile;

/**
 * Created by Cisco WebEx. User: vegaz Date: 2010-8-30 Time: 14:33:33
 */

public final class ClassFinder {
    private static final String DOT_JAR = ".jar"; // $NON-NLS-1$
    private static final String DOT_CLASS = ".class"; // $NON-NLS-1$
    private static final int DOT_CLASS_LEN = DOT_CLASS.length();

    // static only
    private ClassFinder() {
    }

    /**
     * Filter updates to TreeSet by only storing classes that extend one of the
     * parent classes
     * 
     * 
     */
    private static class FilterTreeSet extends TreeSet {
        private final Class[] parents; // parent classes to check
        private final boolean inner; // are inner classes OK?

        private final transient ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); // Potentially
        // expensive; do it
        // once

        FilterTreeSet(Class[] parents, boolean inner) {
            super();
            this.parents = parents;
            this.inner = inner;
        }

        /**
         * Override the superclass so we only add classnames that meet the
         * criteria.
         * 
         * @param o
         *            - classname (must be a String)
         * @return true if it is a new entry
         * 
         * @see java.util.TreeSet#add(java.lang.Object)
         */
        public boolean add(Object o) {
            if (contains(o))
                return false;// No need to check it again
            String s = (String) o;// we only expect Strings
            if ((s.indexOf("$") == -1) || inner) { // $NON-NLS-1$
                if (isChildOf(parents, s, contextClassLoader)) {
                    return super.add(s);
                }
            }
            return false;
        }
    }

    public static List<String> findClassesThatExtend(String path, Class[] superClasses) throws IOException {
        String[] paths = new String[1];
        paths[0] = path;
        return findClassesThatExtend(paths, superClasses);
    }

    public static List<String> findClassesThatExtend(String[] paths, Class[] superClasses) throws IOException {
        return findClassesThatExtend(paths, superClasses, false);
    }

    // For each directory in the search path, add all the jars found there
    private static String[] addJarsInPath(String[] paths) {
        Set<String> fullList = new HashSet<String>();
        for (final String path : paths) {
            fullList.add(path); // Keep the unexpanded path
            // TODO - allow directories to end with .jar by removing this check?
            if (!path.endsWith(DOT_JAR)) {
                File dir = new File(path);
                if (dir.exists() && dir.isDirectory()) {
                    String[] jars = dir.list(new FilenameFilter() {
                        public boolean accept(File f, String name) {
                            return name.endsWith(DOT_JAR);
                        }
                    });
                    fullList.addAll(Arrays.asList(jars));
                }
            }
        }
        return fullList.toArray(new String[0]);
    }

    public static List<String> findClassesThatExtend(String[] strPathsOrJars, final Class[] superClasses,
            final boolean innerClasses) throws IOException {
        // Find all jars in the search path
        // strPathsOrJars = addJarsInPath(strPathsOrJars);
        for (int k = 0; k < strPathsOrJars.length; k++) {
            strPathsOrJars[k] = fixPathEntry(strPathsOrJars[k]);
        }

        // Now eliminate any classpath entries that do not "match" the search
        // List listPaths = getClasspathMatches(strPathsOrJars);
        List<String> listPaths = new ArrayList<String>();
        for (String path : strPathsOrJars) {
            listPaths.add(path);
        }

        Set<String> listClasses = new FilterTreeSet(superClasses, innerClasses);
        // first get all the classes
        findClassesInPaths(listPaths, listClasses);

        // // Now keep only the required classes
        // Set subClassList = findAllSubclasses(superClasses, listClasses,
        // innerClasses);
        // if (log.isDebugEnabled()) {
        // log.debug("subClassList.size()="+subClassList.size());
        // Iterator tIter = subClassList.iterator();
        // while (tIter.hasNext()) {
        // log.debug("subClassList : " + tIter.next());
        // }
        // }

        return new ArrayList<String>(listClasses);// subClassList);
    }

    /*
     * Returns the classpath entries that match the search list of jars and
     * paths
     */
    private static List getClasspathMatches(String[] strPathsOrJars) {

        StringTokenizer classPaths = new StringTokenizer(System.getProperty("java.class.path"), // $NON-NLS-1$
                System.getProperty("path.separator")); // $NON-NLS-1$

        // find all jar files or paths that end with strPathOrJar
        ArrayList<String> listPaths = new ArrayList<String>();
        String classPath = null;
        while (classPaths.hasMoreTokens()) {
            classPath = fixPathEntry(classPaths.nextToken());
            if (strPathsOrJars == null) {
                listPaths.add(classPath);
            } else {
                boolean found = false;
                for (String strPathsOrJar : strPathsOrJars) {
                    if (classPath.endsWith(strPathsOrJar)) {
                        found = true;
                        listPaths.add(classPath);
                        break;// no need to look further
                    }
                }
                if (!found) {
                    // log.debug("Did not find: " + strPath);
                }
            }
        }
        return listPaths;
    }

    /**
     * Fix a path: - replace "." by current directory - trim any trailing spaces
     * - replace \ by / - replace // by / - remove all trailing /
     * 
     * @param path
     * @return path
     */
    private static String fixPathEntry(String path) {
        if (path == null)
            return null;
        if (path.equals(".")) {
            return System.getProperty("user.dir");
        }
        path = path.trim().replace('\\', '/');
        path = substitute(path, "//", "/");

        while (path.endsWith("/")) { // $NON-NLS-1$
            path = path.substring(0, path.length() - 1);
        }
        return path;
    }

    /*
     * NOTUSED * Determine if the class implements the interface.
     * 
     * @param theClass the class to check
     * 
     * @param theInterface the interface to look for
     * 
     * @return boolean true if it implements
     * 
     * private static boolean classImplementsInterface( Class theClass, Class
     * theInterface) { HashMap mapInterfaces = new HashMap(); String strKey =
     * null; // pass in the map by reference since the method is recursive
     * getAllInterfaces(theClass, mapInterfaces); Iterator iterInterfaces =
     * mapInterfaces.keySet().iterator(); while (iterInterfaces.hasNext()) {
     * strKey = (String) iterInterfaces.next(); if (mapInterfaces.get(strKey) ==
     * theInterface) { return true; } } return false; }
     */

    /*
     * Finds all classes that extend the classes in the listSuperClasses
     * ArrayList, searching in the listAllClasses ArrayList.
     * 
     * @param superClasses the base classes to find subclasses for
     * 
     * @param listAllClasses the collection of classes to search in
     * 
     * @param innerClasses indicate whether to include inner classes in the
     * search
     * 
     * @return ArrayList of the subclasses
     */
    // private static Set findAllSubclasses(Class []superClasses, Set
    // listAllClasses, boolean innerClasses) {
    // Set listSubClasses = new TreeSet();
    // for (int i=0; i< superClasses.length; i++) {
    // findAllSubclassesOneClass(superClasses[i], listAllClasses,
    // listSubClasses, innerClasses);
    // }
    // return listSubClasses;
    // }

    /*
     * Finds all classes that extend the class, searching in the listAllClasses
     * ArrayList.
     * 
     * @param theClass the parent class
     * 
     * @param listAllClasses the collection of classes to search in
     * 
     * @param listSubClasses the collection of discovered subclasses
     * 
     * @param innerClasses indicates whether inners classes should be included
     * in the search
     */
    // private static void findAllSubclassesOneClass(Class theClass, Set
    // listAllClasses, Set listSubClasses,
    // boolean innerClasses) {
    // Iterator iterClasses = listAllClasses.iterator();
    // while (iterClasses.hasNext()) {
    // String strClassName = (String) iterClasses.next();
    // // only check classes if they are not inner classes
    // // or we intend to check for inner classes
    // if ((strClassName.indexOf("$") == -1) || innerClasses) { // $NON-NLS-1$
    // // might throw an exception, assume this is ignorable
    // try {
    // Class c = Class.forName(strClassName, false,
    // Thread.currentThread().getContextClassLoader());
    //
    // if (!c.isInterface() && !Modifier.isAbstract(c.getModifiers())) {
    // if(theClass.isAssignableFrom(c)){
    // listSubClasses.add(strClassName);
    // }
    // }
    // } catch (Throwable ignored) {
    // log.debug(ignored.getLocalizedMessage());
    // }
    // }
    // }
    // }

    /**
     * 
     * @param parentClasses
     *            list of classes to check for
     * @param strClassName
     *            name of class to be checked
     * @param contextClassLoader
     *            the classloader to use
     * @return
     */
    private static boolean isChildOf(Class[] parentClasses, String strClassName, ClassLoader contextClassLoader) {
        // might throw an exception, assume this is ignorable
        try {
            Class c = Class.forName(strClassName, false, contextClassLoader);

            if (!c.isInterface() && !Modifier.isAbstract(c.getModifiers())) {
                for (Class parentClass : parentClasses) {
                    if (parentClass.isAssignableFrom(c)) {
                        return true;
                    }
                }
            }
        } catch (Throwable ignored) {

        }
        return false;
    }

    /*
     * Converts a class file from the text stored in a Jar file to a version
     * that can be used in Class.forName().
     * 
     * @param strClassName the class name from a Jar file
     * 
     * @return String the Java-style dotted version of the name
     */
    private static String fixClassName(String strClassName) {
        strClassName = strClassName.replace('\\', '.'); // $NON-NLS-1$ //
        // $NON-NLS-2$
        strClassName = strClassName.replace('/', '.'); // $NON-NLS-1$ //
        // $NON-NLS-2$
        // remove ".class"
        strClassName = strClassName.substring(0, strClassName.length() - DOT_CLASS_LEN);
        return strClassName;
    }

    private static void findClassesInOnePath(String strPath, Set<String> listClasses) throws IOException {
        File file = new File(strPath);
        if (file.isDirectory()) {
            findClassesInPathsDir(strPath, file, listClasses);
        } else if (file.exists()) {
            ZipFile zipFile = new ZipFile(file);
            Enumeration entries = zipFile.entries();
            while (entries.hasMoreElements()) {
                String strEntry = entries.nextElement().toString();
                if (strEntry.endsWith(DOT_CLASS)) {
                    listClasses.add(fixClassName(strEntry));
                }
            }
            zipFile.close();
        }
    }

    private static void findClassesInPaths(List listPaths, Set listClasses) throws IOException {
        for (Object listPath : listPaths) {
            findClassesInOnePath((String) listPath, listClasses);
        }
    }

    private static void findClassesInPathsDir(String strPathElement, File dir, Set<String> listClasses)
            throws IOException {
        String[] list = dir.list();
        for (int i = 0; i < list.length; i++) {
            File file = new File(dir, list[i]);
            if (file.isDirectory()) {
                // Recursive call
                findClassesInPathsDir(strPathElement, file, listClasses);
            } else if (list[i].endsWith(DOT_CLASS) && file.exists() && (file.length() != 0)) {
                final String path = file.getPath();
                listClasses.add(path.substring(strPathElement.length() + 1, path.lastIndexOf(".")) // $NON-NLS-1$
                        .replace(File.separator.charAt(0), '.')); // $NON-NLS-1$
            } else if (list[i].endsWith(DOT_JAR) && file.exists() && (file.length() != 0)) {
                ZipFile zipFile = new ZipFile(file);
                Enumeration entries = zipFile.entries();
                while (entries.hasMoreElements()) {
                    String strEntry = entries.nextElement().toString();
                    if (strEntry.endsWith(DOT_CLASS)) {
                        listClasses.add(fixClassName(strEntry));
                    }
                }
                zipFile.close();
            }
        }
    }

    public static String substitute(final String input, final String pattern, final String sub) {
        StringBuffer ret = new StringBuffer(input.length());
        int start = 0;
        int index = -1;
        final int length = pattern.length();
        while ((index = input.indexOf(pattern, start)) >= start) {
            ret.append(input.substring(start, index));
            ret.append(sub);
            start = index + length;
        }
        ret.append(input.substring(start));
        return ret.toString();
    }
}