views:

105

answers:

3

I would like to get a list of classes that are available at runtime and that match a simple name.

For example:

public List<String> getFQNs(String simpleName) {
    ...
}

// Would return ["java.awt.List","java.util.List"]
List<String> fqns = getFQNs("List")

Is there a library that would do this efficiently, or do I have to manually go through all classes in each classloader? What would be the correct way of doing that?

Thanks!

UPDATE

One responder asked me why I wanted to do this. Essentially, I want to implement a feature that is similar to "organize imports/auto import", but available at runtime. I don't mind if the solution is relatively slow (especially if I can then build a cache so subsequent queries become faster) and if it is a best-effort only. For example, I don't mind if I do not get dynamically generated classes.

UPDATE 2

I had to devise my own solution (see below): it uses some hints provided by the other responders, but I came to realize that it needs to be extensible to handle various environments. It is not possible to automatically traverse all classloaders at runtime so you have to rely on general and domain-specific strategies to get a useful list of classes.

A: 

You probably cannot do this at all. There is no way for the JVM to know whether a class List in an arbitrarily named package a.b.c.d is available without attempting to load a.b.c.d.List first. You would need to test for all possible package names.

Grodriguez
A: 

Without loading them all you could get the classpath property

String class_path = System.getProperty("java.class.path");

And then you could create a function to search the filesystem for classes in those locations. And you'd have to code it to also search inside jar files, and some of the classes may not actually be available due to incompatibilities that would only be revealed when you load them. But if you just want a best-guess of what's available, this might be viable. Maybe you should tell us why you want to do this so you can get some alternative suggestions?

Edit: Ok, sounds like you should check out this thread and the ones linked in it: http://stackoverflow.com/questions/1456930/read-all-classes-from-java-package-in-classpath In particular it appears the Spring framework does something similar, maybe you can look at that code: http://static.springsource.org/spring/docs/2.0.x/api/org/springframework/core/io/support/PathMatchingResourcePatternResolver.html

bemace
A: 

I mixed the the answers from @Grodriguez and @bemace and added my own strategy to come up with a best-effort solution. This solution imitates at runtime the auto-import feature available at compile time.

The full code of my solution is here. Given a simple name, the main steps are:

  1. Get a list of packages accessible from the current classloader.
  2. For each package, try to load the fully qualified name obtained from package + simple name.

Step 2 is easy:

public List<String> getFQNs(String simpleName) {
    if (this.packages == null) {
        this.packages = getPackages();
    }

    List<String> fqns = new ArrayList<String>();
    for (String aPackage : packages) {
        try {
            String fqn = aPackage + "." + simpleName;
            Class.forName(fqn);
            fqns.add(fqn);
        } catch (Exception e) {
            // Ignore
        }
    }
    return fqns;
}

Step 1 is harder and is dependent on your application/environment so I implemented various strategies to get different lists of packages.

Current Classloader (may be useful to detect dynamically generated classes)

public Collection<String> getPackages() {
    Set<String> packages = new HashSet<String>();
    for (Package aPackage : Package.getPackages()) {
        packages.add(aPackage.getName());
    }
    return packages;
}

Classpath (good enough for applications that are entirely loaded from the classpath. Not good for complex applications like Eclipse)

public Collection<String> getPackages() {
    String classpath = System.getProperty("java.class.path");
    return getPackageFromClassPath(classpath);
}

public static Set<String> getPackageFromClassPath(String classpath) {
    Set<String> packages = new HashSet<String>();
    String[] paths = classpath.split(File.pathSeparator);
    for (String path : paths) {
        if (path.trim().length() == 0) {
            continue;
        } else {
            File file = new File(path);
            if (file.exists()) {
                String childPath = file.getAbsolutePath();
                if (childPath.endsWith(".jar")) {
                    packages.addAll(ClasspathPackageProvider
                            .readZipFile(childPath));
                } else {
                    packages.addAll(ClasspathPackageProvider
                            .readDirectory(childPath));
                }
            }
        }

    }
    return packages;
}

Bootstrap classpath (e.g., java.lang)

public Collection<String> getPackages() {
    // Even IBM JDKs seem to use this property...
    String classpath = System.getProperty("sun.boot.class.path");
    return ClasspathPackageProvider.getPackageFromClassPath(classpath);
}

Eclipse bundles (domain-specific package provider)

// Don't forget to add "Eclipse-BuddyPolicy: global" to MANIFEST.MF
public Collection<String> getPackages() {
    Set<String> packages = new HashSet<String>();
    BundleContext context = Activator.getDefault().getBundle()
            .getBundleContext();
    Bundle[] bundles = context.getBundles();
    PackageAdmin pAdmin = getPackageAdmin(context);

    for (Bundle bundle : bundles) {
        ExportedPackage[] ePackages = pAdmin.getExportedPackages(bundle);
        if (ePackages != null) {
            for (ExportedPackage ePackage : ePackages) {
                packages.add(ePackage.getName());
            }
        }
    }

    return packages;
}

public PackageAdmin getPackageAdmin(BundleContext context) {
    ServiceTracker bundleTracker = null;
    bundleTracker = new ServiceTracker(context,
            PackageAdmin.class.getName(), null);
    bundleTracker.open();
    return (PackageAdmin) bundleTracker.getService();
}

Examples of queries and answers in my Eclipse environment:

  1. File: [java.io.File, org.eclipse.core.internal.resources.File]
  2. List: [java.awt.List, org.eclipse.swt.widgets.List, com.sun.xml.internal.bind.v2.schemagen.xmlschema.List, java.util.List, org.hibernate.mapping.List]
  3. IResource: [org.eclipse.core.resources.IResource]
Barthelemy