/*
* Copyright 2005 Joe Walker
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.directwebremoting.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* ClasspathScanner enables you to find the set of classes that match some
* package name. The search can be recursive.
* @author Jose Noheda [jose.noheda at gmail dot com]
* @author Joe Walker [joe at getahead dot ltd dot uk]
*/
public class ClasspathScanner
{
/**
* Attempt to find all classes in the VM
*/
public ClasspathScanner()
{
this(null, true);
}
/**
* Non recursively find classes within the given package
* @param packageName package name specified with dot separators
*/
public ClasspathScanner(String packageName)
{
this(packageName, false);
}
/**
* Find classes within the given package (optionally recursively)
* @param packageName package name specified with dot separator
* @param recursive True to dig into sub-packages
*/
public ClasspathScanner(String packageName, boolean recursive)
{
this.recursive = recursive;
if (packageName == null)
{
packageName = "";
}
packageName = packageName.replace('.', '/');
if (packageName.endsWith("*"))
{
packageName = packageName.substring(0, packageName.length() - 1);
}
if (packageName.endsWith("/"))
{
packageName = packageName.substring(0, packageName.length() - 1);
}
this.packageName = packageName;
}
/**
* Get the list of classes available to the classloader
*/
public Set<String> getClasses()
{
Set<String> classes = new HashSet<String>();
try
{
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
String match = packageName;
if (match == "/")
{
match = "";
}
Enumeration<URL> resources = classLoader.getResources(match + "/");
while (resources.hasMoreElements())
{
String path = sanitizePath(resources.nextElement().getFile());
if ((path == null) || (path.trim().length() <= 0))
{
continue;
}
if (isJARPath(path))
{
classes.addAll(getClassesFromJAR(path));
}
else
{
classes.addAll(getClassesFromDirectory(path));
}
}
}
catch (IOException ex)
{
log.warn("Failed to find any resources from classloader");
}
return classes;
}
/**
* Is this path pointing at a JAR file?
*/
protected boolean isJARPath(String path)
{
return (path.indexOf("!") > 0) & (path.indexOf(".jar") > 0);
}
/**
* Extract the classes from a JAR file
*/
protected Set<String> getClassesFromJAR(String path) throws IOException
{
Set<String> classes = new HashSet<String>();
String jarPath = path.substring(0, path.indexOf("!")).substring(path.indexOf(":") + 1);
JarInputStream jarFile = new JarInputStream(new FileInputStream(jarPath));
while (true)
{
JarEntry jarEntry = jarFile.getNextJarEntry();
if (jarEntry == null)
{
break;
}
addIfMatches(classes, jarEntry.getName());
}
return classes;
}
/**
* Extract the classes from a set of classes in the file system
*/
protected Set<String> getClassesFromDirectory(String path)
{
Set<String> classes = new HashSet<String>();
File directory = new File(path);
if (directory.exists())
{
for (String file : directory.list())
{
File f = new File(directory, file);
if (f.isFile())
{
addIfMatches(classes, path.substring(path.indexOf(packageName)) + file);
}
else if (recursive)
{
classes.addAll(getClassesFromDirectory(path + file + "/"));
}
}
}
return classes;
}
/**
* Check to see that the given file is a class in the right package and add
* it to the given collection
*/
protected void addIfMatches(Set<String> classes, String className)
{
if ((className.startsWith(packageName)) && (className.endsWith(".class")))
{
boolean add = recursive ? true : className.substring(packageName.length() + 1).indexOf("/") < 0;
if (add)
{
classes.add(className.substring(0, className.length() - 6).replace('/', '.'));
}
}
}
/**
* Paths need cleaning up, especially in windows
*/
protected String sanitizePath(String path)
{
String tmp = path;
if (tmp.indexOf("%20") > 0)
{
// TODO: maybe we should do full URL decoding here?
tmp = tmp.replaceAll("%20", " ");
}
if ((tmp.indexOf(":") >= 0) && (tmp.startsWith("/")))
{
// Remove leading / in URLs like /c:/...
tmp = tmp.substring(1);
}
return tmp;
}
/**
* The package name we are finding classes in
*/
private final String packageName;
/**
* Are we digging recursively?
*/
private final boolean recursive;
/**
* The log stream
*/
private static final Log log = LogFactory.getLog(ClasspathScanner.class);
}