Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting list of fully qualified names from a simple name

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.

like image 946
Barthelemy Avatar asked Oct 02 '10 13:10

Barthelemy


1 Answers

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]
like image 73
Barthelemy Avatar answered Oct 27 '22 00:10

Barthelemy