views:

714

answers:

6

How do i get all classes within a package?

+7  A: 

You can't. Classes can come in via many different class loaders, including remote ones.

ChssPly76
+1  A: 

There is no global way to do that. That being said, if you know where your classes are coming from, you can walk the directory of a jar file or the file system.

Yishai
+2  A: 

There's a snippet from here that does exactly what you want, assuming the classes can be found locally:

private static Class[] getClasses(String packageName)
throws ClassNotFoundException, IOException {
 ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
 assert classLoader != null;
 String path = packageName.replace('.', '/');
 Enumeration<URL> resources = classLoader.getResources(path);
 List<File> dirs = new ArrayList<File>();
 while (resources.hasMoreElements()) {
  URL resource = resources.nextElement();
  dirs.add(new File(resource.getFile()));
 }
 ArrayList<Class> classes = new ArrayList<Class>();
 for (File directory : dirs) {
  classes.addAll(findClasses(directory, packageName));
 }
 return classes.toArray(new Class[classes.size()]);
}

private static List<Class> findClasses(File directory, String packageName) throws ClassNotFoundException {
 List<Class> classes = new ArrayList<Class>();
 if (!directory.exists()) {
  return classes;
 }
 File[] files = directory.listFiles();
 for (File file : files) {
  if (file.isDirectory()) {
   assert !file.getName().contains(".");
   classes.addAll(findClasses(file, packageName + "." + file.getName()));
  } else if (file.getName().endsWith(".class")) {
   classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
  }
 }
 return classes;
}
JG
Does this work with jar'ed classes?
Thorbjørn Ravn Andersen
As far as I can tell this will fail if you have a jar (-1) but is a decent routine for looking for .class files for discovery (+1) so you break even :)
Bill K
The above code not only assumes that classes from the given package are local, but also that they have all been loaded by the same class loader AND they haven't been packaged.
ChssPly76
This is still a useful start for a system like Eclipse where a jar is dropped into a directory and discovered on the next boot. A reasonable solution, but very limited (and since it doesn't currently support Jars, not that useful as-is)
Bill K
A: 

Java doesn't have discovery.

Most products that have the ability to add (discover) new classes either have a text file describing "Program Extensions" or a specific directory where you can place either classes or jars that uses a trick like @JG described. (This is what eclipse does and is recommended for any solution where the user may add the new module by hand)

Bill K
Don't forget the SPI APIs for plugins. These provide a reasonably elegant solution for plugin frameworks.
jsight
+5  A: 

Here's a more complete way to solve this for jars, based on the idea posted by JG.

/**
 * Scans all classloaders for the current thread for loaded jars, and then scans
 * each jar for the package name in question, listing all classes directly under
 * the package name in question. Assumes directory structure in jar file and class
 * package naming follow java conventions (i.e. com.example.test.MyTest would be in
 * /com/example/test/MyTest.class)
 */
public Collection<Class> getClassesForPackage(String packageName) throws Exception {
  String packagePath = packageName.replace(".", "/");
  ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
  Set<URL> jarUrls = new HashSet<URL>();

  while (classLoader != null) {
    if (classLoader instanceof URLClassLoader)
      for (URL url : ((URLClassLoader) classLoader).getURLs())
        if (url.getFile().endsWith(".jar")  // may want better way to detect jar files
          jarUrls.add(url);

    classLoader = classLoader.getParent();
  }

  Set<Class> classes = new HashSet<Class>();

  for (URL url : jarUrls) {
    JarInputStream stream = new JarInputStream(url.openStream()); // may want better way to open url connections
    JarEntry entry = stream.getNextJarEntry();

    while (entry != null) {
      String name = entry.getName();
      int i = name.lastIndexOf("/");

      if (i > 0 && name.endsWith(".class") && name.substring(0, i).equals(packagePath)) 
        classes.add(Class.forName(name.substring(0, name.length() - 6).replace("/", ".")));

      entry = stream.getNextJarEntry();
    }

    stream.close();
  }

  return classes;
}
toluju
-1 no documentation or even hint about what it does, +2 because it looks like it scans the classpath and includes jars! so you net a +1 :)
Bill K
(Well, there was a hint about what it does.. I take it back).
Bill K
Added a javadoc header for a better explanation. :)
toluju
A: 

The above answers outline the required functionality. However, more details on the subject can be found here and here.

01es