thvo thvo - 4 months ago 10
Java Question

How can I enumerate all classes in a package and add them to a List?

I need to enumerate all classes in a package and add them to a List. The non-dynamic version for a single class goes like this:

List allClasses = new ArrayList();
allClasses.add(String.class);


How can I do this dynamically to add all classes in a package and all its subpackages?




Update: Having read the early answers, it's absolutely true that I'm trying to solve another secondary problem, so let me state it. And I know this is possible since other tools do it. See new question here.

Update: Reading this again, I can see how it's being misread. I'm looking to enumerate all of MY PROJECT'S classes from the file system after compilation.

Answer

*UPDATE*

OK, I've finally gotten around to cleaning up the code snippet below. I stuck it into it's own github project and even added tests.

https://github.com/ddopson/java-class-enumerator

*Original Post*

Strictly speaking, it isn't possible to list the classes in a package. This is because a package is really nothing more than a namespace (eg com.epicapplications.foo.bar), and any jar-file in the classpath could potentially add classes into a package. Even worse, the classloader will load classes on demand, and part of the classpath might be on the other side of a network connection.

It is possible to solve a more restrictive problem. eg, all classes in a JAR file, or all classes that a JAR file defines within a particular package. This is the more common scenario anyways.

Unfortunately, there isn't any framework code to make this task easy. You have to scan the filesystem in a manner similar to how the ClassLoader would look for class definitions.

There are a lot of samples on the web for class files in plain-old-directories. Most of us these days work with JAR files.

To get things working with JAR files, try this...

private static ArrayList<Class<?>> getClassesForPackage(Package pkg) {
    String pkgname = pkg.getName();
    ArrayList<Class<?>> classes = new ArrayList<Class<?>>();
    // Get a File object for the package
    File directory = null;
    String fullPath;
    String relPath = pkgname.replace('.', '/');
    System.out.println("ClassDiscovery: Package: " + pkgname + " becomes Path:" + relPath);
    URL resource = ClassLoader.getSystemClassLoader().getResource(relPath);
    System.out.println("ClassDiscovery: Resource = " + resource);
    if (resource == null) {
        throw new RuntimeException("No resource for " + relPath);
    }
    fullPath = resource.getFile();
    System.out.println("ClassDiscovery: FullPath = " + resource);

    try {
        directory = new File(resource.toURI());
    } catch (URISyntaxException e) {
        throw new RuntimeException(pkgname + " (" + resource + ") does not appear to be a valid URL / URI.  Strange, since we got it from the system...", e);
    } catch (IllegalArgumentException e) {
        directory = null;
    }
    System.out.println("ClassDiscovery: Directory = " + directory);

    if (directory != null && directory.exists()) {
        // Get the list of the files contained in the package
        String[] files = directory.list();
        for (int i = 0; i < files.length; i++) {
            // we are only interested in .class files
            if (files[i].endsWith(".class")) {
                // removes the .class extension
                String className = pkgname + '.' + files[i].substring(0, files[i].length() - 6);
                System.out.println("ClassDiscovery: className = " + className);
                try {
                    classes.add(Class.forName(className));
                } 
                catch (ClassNotFoundException e) {
                    throw new RuntimeException("ClassNotFoundException loading " + className);
                }
            }
        }
    }
    else {
        try {
            String jarPath = fullPath.replaceFirst("[.]jar[!].*", ".jar").replaceFirst("file:", "");
            JarFile jarFile = new JarFile(jarPath);         
            Enumeration<JarEntry> entries = jarFile.entries();
            while(entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
                String entryName = entry.getName();
                if(entryName.startsWith(relPath) && entryName.length() > (relPath.length() + "/".length())) {
                    System.out.println("ClassDiscovery: JarEntry: " + entryName);
                    String className = entryName.replace('/', '.').replace('\\', '.').replace(".class", "");
                    System.out.println("ClassDiscovery: className = " + className);
                    try {
                        classes.add(Class.forName(className));
                    } 
                    catch (ClassNotFoundException e) {
                        throw new RuntimeException("ClassNotFoundException loading " + className);
                    }
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(pkgname + " (" + directory + ") does not appear to be a valid package", e);
        }
    }
    return classes;
}
Comments