If you take a peek at the Package class you can easily detect that no getClasses() (or getAllClasses() or whatever) method is available. Quite a shock indeed! Probably if you're dealing with packages in some situation or other you'll need to list the classes inside. Unfortunately the problem is even bigger as that's not possible at all.
The reality is..that information is not available. This is due to the fact that not all classes have to reside in the file system as they can be loaded from a socket or a byte[], for example. That means the number of classes is just indeterminate.
On the other hand, it seems plausible that if you want to get all classes from a package, you are interested in the ones physically available, that is, in your hard disk. Fortunately, this is a problem with solution! Basically it consists in scanning a directory structure. The subtlety resides in the location of the directory itself as in many cases it will be a JAR file packaged inside some other archive (WAR, EAR..) and deployed in an application server.
Let's review step by step how to do it.
Our initial task is to obtain the package location. This is done querying the classloader. I will show one way to obtain the classloader here as well(there are more though). The class loader will return an enumeration of paths (as URLs) that can later be scanned
The reality is..that information is not available. This is due to the fact that not all classes have to reside in the file system as they can be loaded from a socket or a byte[], for example. That means the number of classes is just indeterminate.
On the other hand, it seems plausible that if you want to get all classes from a package, you are interested in the ones physically available, that is, in your hard disk. Fortunately, this is a problem with solution! Basically it consists in scanning a directory structure. The subtlety resides in the location of the directory itself as in many cases it will be a JAR file packaged inside some other archive (WAR, EAR..) and deployed in an application server.
Let's review step by step how to do it.
Our initial task is to obtain the package location. This is done querying the classloader. I will show one way to obtain the classloader here as well(there are more though). The class loader will return an enumeration of paths (as URLs) that can later be scanned
static Set<Class<?>> getClasses(String packageName) throws Exception {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
return getClasses(loader, packageName);
}
static Set<Class<?>> getClasses(ClassLoader loader, String packageName) ...{
Set<Class<?>> classes = new HashSet<Class<?>>();
String path = packageName.replace('.', '/');
Enumeration<URL> resources = loader.getResources(path);
if (resources != null) {
while (resources.hasMoreElements()) {
String filePath = resources.nextElement().getFile();
// WINDOWS HACK
if(filePath.indexOf("%20") > 0)
filePath = filePath.replaceAll("%20", " ");
if (filePath != null) {
if ((filePath.indexOf("!") > 0) & (filePath.indexOf(".jar") > 0)) {
String jarPath = filePath.substring(0, filePath.indexOf("!"))
.substring(filePath.indexOf(":") + 1);
// WINDOWS HACK
if (jarPath.indexOf(":") >= 0) jarPath = jarPath.substring(1);
classes.addAll(getFromJARFile(jarPath, path));
} else {
classes.addAll(
getFromDirectory(new File(filePath), packageName));
}
}
}
}
return classes;
}
ClassLoader loader = Thread.currentThread().getContextClassLoader();
return getClasses(loader, packageName);
}
static Set<Class<?>> getClasses(ClassLoader loader, String packageName) ...{
Set<Class<?>> classes = new HashSet<Class<?>>();
String path = packageName.replace('.', '/');
Enumeration<URL> resources = loader.getResources(path);
if (resources != null) {
while (resources.hasMoreElements()) {
String filePath = resources.nextElement().getFile();
// WINDOWS HACK
if(filePath.indexOf("%20") > 0)
filePath = filePath.replaceAll("%20", " ");
if (filePath != null) {
if ((filePath.indexOf("!") > 0) & (filePath.indexOf(".jar") > 0)) {
String jarPath = filePath.substring(0, filePath.indexOf("!"))
.substring(filePath.indexOf(":") + 1);
// WINDOWS HACK
if (jarPath.indexOf(":") >= 0) jarPath = jarPath.substring(1);
classes.addAll(getFromJARFile(jarPath, path));
} else {
classes.addAll(
getFromDirectory(new File(filePath), packageName));
}
}
}
}
return classes;
}
Reading from a plain directory is quite easy now:
static Set<Class<?>> getFromDirectory(File directory, String packageName) .. {
Set<Class<?>> classes = new HashSet<Class<?>>();
if (directory.exists()) {
for (String file : directory.list()) {
if (file.endsWith(".class")) {
String name = packageName + '.' + stripFilenameExtension(file);
Class<?> clazz = Class.forName(name);
classes.add(clazz);
}
}
}
return classes;
}
Set<Class<?>> classes = new HashSet<Class<?>>();
if (directory.exists()) {
for (String file : directory.list()) {
if (file.endsWith(".class")) {
String name = packageName + '.' + stripFilenameExtension(file);
Class<?> clazz = Class.forName(name);
classes.add(clazz);
}
}
}
return classes;
}
And finally we have to consider compressed files. We can read them using the java.util.jar utilities
static Set<Class<?>> getFromJARFile(String jar, String packageName) ..{
Set<Class<?>> classes = new HashSet<Class<?>>();
JarInputStream jarFile = new JarInputStream(new FileInputStream(jar));
JarEntry jarEntry;
do {
jarEntry = jarFile.getNextJarEntry();
if (jarEntry != null) {
String className = jarEntry.getName();
if (className.endsWith(".class")) {
className = stripFilenameExtension(className);
if (className.startsWith(packageName)) classes.add(Class.forName(className.replace('/', '.')));
}
}
} while (jarEntry != null);
return classes;
}
Set<Class<?>> classes = new HashSet<Class<?>>();
JarInputStream jarFile = new JarInputStream(new FileInputStream(jar));
JarEntry jarEntry;
do {
jarEntry = jarFile.getNextJarEntry();
if (jarEntry != null) {
String className = jarEntry.getName();
if (className.endsWith(".class")) {
className = stripFilenameExtension(className);
if (className.startsWith(packageName)) classes.add(Class.forName(className.replace('/', '.')));
}
}
} while (jarEntry != null);
return classes;
}
This is really all but if you're interested here's a very simple unit test
public void testGetClasses() throws Exception {
Set<Class<?>> list = ClassUtils.getClasses("mock.validation");
assertTrue("Two classes found", list.size() == 2);
assertTrue("Contains Mock class", list.contains(Mock.class));
list.clear();
ClassLoader loader = getClass().getClassLoader();
list = ClassUtils.getClasses(loader, "org.hibernate.annotations");
assertTrue("N classes found", list.size() > 10);
assertTrue("Contains Cache class", list.contains(Cache.class));
}
Set<Class<?>> list = ClassUtils.getClasses("mock.validation");
assertTrue("Two classes found", list.size() == 2);
assertTrue("Contains Mock class", list.contains(Mock.class));
list.clear();
ClassLoader loader = getClass().getClassLoader();
list = ClassUtils.getClasses(loader, "org.hibernate.annotations");
assertTrue("N classes found", list.size() > 10);
assertTrue("Contains Cache class", list.contains(Cache.class));
}
By the way...this does not work
list = ClassUtils.getClasses(loader, "java.lang");
assertTrue("Contains String class", list.contains(String.class));
assertTrue("Contains String class", list.contains(String.class));

No hay comentarios:
Publicar un comentario en la entrada