tags:

views:

3203

answers:

8

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.

+2  A: 

I'm afraid you'll have to manually scan the classpath and the other places where java searches for classes (e.g., the ext directory or the boot classpath). Since java uses lazy loading of classes, it may not even know about additional classes in your packages that haven't been loaded yet. Also check the notion of "sealed" packages.

G B
+8  A: 

It isn't possible to use reflection to query a Package for it's Classes (or even its subpackages). http://forums.sun.com/thread.jspa?threadID=341935&start=0&tstart=0 contains a very good discussion about why this is problematic, as well as a handful of solutions to your problem.

Cyphus
+1  A: 

What are you trying to accomplish? Maybe you can use the Service Provider Interface to solve your problem.

Roel Spilker
+1  A: 

It's funny that this question comes up every once in a while. The problem is that this keyword would have been more appropriately named "namespace". The Java package does not delineate a concrete container that holds all the classes in the package at any one time. It simply defines a token that classes can use to declare that they are a member of that package. You'd have to search through the entire classpath (as another reply indicated) to determine all the classes in a package.

David M. Karr
A: 

You cannot. Why? Because Java classes are loaded dynamically from the class path.

There is no such thing as "the complete set of classes in a package". At any time, you or any other application could create new files in the classpath and then load them.

Adrian
+1  A: 

I figured out how to do this. Here's the procedure:

  1. Start with a class in the root package, and get the folder it's in from the class loader
  2. Recursively enumerate all .class files in this folder
  3. Convert the file names to fully qualified class names
  4. Use Class.forName() to get the classes

There are a few nasty tricks here that make me a bit uneasy, but it works - for example:

  1. Converting path names to package names using string manipulation
  2. Hard-coding the root package name to enable stripping away the path prefix

Too bad that stackoverflow doesn't allow me to accept my own answer...

thvo
A: 
Diastrophism
+1  A: 

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);

    directory = new File(fullPath);
    System.out.println("ClassDiscovery: Directory = " + directory);

    if (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;
}
ddopson