Java, Get all classes available to a URLClassLoader that implement a specific interface

7.3k views Asked by At

I am working on a command line app that loads user specified text translators at runtime (path to class files/jar provided via command line arg). Basically I am taking that argument and using it to create a URLClassLoader. Then I need to find all classes available to the URLClassloader that implement the Transable interface.

Right now I am only allowing this command line arg to be a directory with class files in it. Making the solution fairly simple (code below). But honestly I don't like the solution as it breaks down for jar files, directory of jar files, etc... Also, this obviously breaks down for any classes with a defined package, as loadClass needs the full name including the package. Anyone have a better method?

    File d = new File(path);
    if(d.isDirectory()) {
        URL url = d.toURI().toURL();
        ClassLoader cl = new URLClassLoader(new URL[]{url});

        FilenameFilter filter = new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return name.endsWith(".class");
            }
        };

        for(File f : d.listFiles(filter)) {
            String name = f.getName().substring(0, f.getName().indexOf("."));
            String key = "";
            if(name.endsWith("Translator")) {
                key = name.substring(0, name.indexOf("Translator"));
            }
            else if(name.endsWith("translator")) {
                key = name.substring(0, name.indexOf("translator"));
            }
            else
                key = name;

            Class c = cl.loadClass(name);
            if(Transable.class.isAssignableFrom(c)) {
                Transable t = (Transable)c.newInstance();
                env.registerTranslator(key, t);
            }
            else {
                System.out.println("[ClassLoader] "+c.getCanonicalName()+" will not be loaded. It is not a translator class");
            }
        }
    }
    else {
        throw new Error("NOT IMPLEMENTED");
    }
5

There are 5 answers

4
rfeak On BEST ANSWER

You may just have to brute force it if you continue down this road. To my knowledge the default class loader will not even load a class into the JVM unless it is referenced somehow. Which means, some of your classes will be basically invisible unless you know their fully qualified class name to load them.

You may want to reconsider your requirements. As it would be far easier to load a set of Translators that you have been given the class names for.

6
whaley On

Instead of searching for implementors explicitly, you should uss Java's Service Provider Interface (SPI) and the ServiceLoader class (introduced in Java 6). SPI is a pretty much a standard way to do what you are describing in Java.

Please see the official Java tutorial on how to create a service provider and how to use it, at runtime, with ServiceLoader.

2
digitaljoel On

This may fit the bill. If not, you ought to be able to look through their source to get an idea of what will work for you. http://code.google.com/p/reflections/

1
C1a On

Would this help?

Since Class c = cl.loadClass(name);

Class method getInterfaces() returns an array of classes

Check each class name for match to translator class name.

3
Paŭlo Ebermann On

In principle this can't work for arbitrary classloaders, as they may use any way imaginable to actually load the classes, and not have any "directory listening" function at all.

A classloader might even generate classes (i.e. the bytecode for the classes) on the fly whenever a loadClass comes, and imagine a TransableClassloader where each such automatically defined class would implement your interface - your program would never end.


That said, for an URLClassloader you can use getURLs(), and for the jar: and file: URL you can use the JarFile or File api to get the list of filenames (and thus Classnames) to try. As you have the root of your package hierarchy given in the URL, finding the right package name is not difficult, too.

(If you need more details, say it.)


Edit: For the package names, they correspond to the directory names inside of your hierarchy. So, when you have a base URL which corresponds to (say) dir/classes, and find a class-file named dir/classes/com/company/gui/SimpleTranslator.class, it corresponds to class com.company.gui.SimpleTranslator.

So, remove the base prefix and replace / by . (and cut of the .class). (In a JarFile you don't have to cut a prefix off.)

Actually, if you use a recursive method to traverse your File hierarchy, you can build up your package-name with the same method, simply by appending Strings (give them as a parameter to the next recursive Invocation):

public void searchClassesInDir(File dir, String packagePrefix) {
    if(dir.isDirectory()) {
        String prefix = packagePrefix + dir.getName() + ".";
        for(File f : dir.listFiles()) {
            searchClasses(f, prefix);
        }
    }
    else {
       String fileName = dir.getName();
       if(! fileName.endsWith(".class"))
           return;
       String className = packagePrefix + fileName.substring(0, fileName.length()-".class".length());
       // now do the rest of your processing
    }
}

searchClasses(new File(url.toURI()), "");