Skip to content

Instantly share code, notes, and snippets.

@SylvanasSun
Created October 14, 2017 12:15
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save SylvanasSun/6ab31dcfd9670f29a46917decdba36d1 to your computer and use it in GitHub Desktop.
Save SylvanasSun/6ab31dcfd9670f29a46917decdba36d1 to your computer and use it in GitHub Desktop.
Package Scanner
/**
* Interface PackageScanner is the basic interface for package scanning.
*
* Created by SylvanasSun on 10/13/2017.
*/
public interface PackageScanner {
/**
* Scanning specified package then return a class list of the after the scan.
*/
List<Class<?>> scan(String packageName);
/**
* Scanning specified package then invoke callback and
* return a class list of the after the scan.
*/
List<Class<?>> scan(String packageName, ScannedClassHandler handler);
}
public class PathUtils {
private static final String FILE_SEPARATOR = System.getProperty("file.separator");
private static final String CLASS_FILE_SUFFIX = ".class";
private static final String JAR_PROTOCOL = "jar";
private static final String FILE_PROTOCOL = "file";
private PathUtils() {
}
public static String trimSuffix(String filename) {
if (filename == null || "".equals(filename))
return filename;
int dotIndex = filename.lastIndexOf(".");
if (-1 == dotIndex)
return filename;
return filename.substring(0, dotIndex);
}
public static String pathToPackage(String path) {
if (path == null || "".equals(path))
return path;
if (path.startsWith(FILE_SEPARATOR))
path = path.substring(1);
return path.replace(FILE_SEPARATOR, ".");
}
public static String packageToPath(String packageName) {
if (packageName == null || "".equals(packageName))
return packageName;
return packageName.replace(".", FILE_SEPARATOR);
}
/**
* By protocol of the url get resource type.
*/
public static ResourceType getResourceType(URL url) {
String protocol = url.getProtocol();
switch (protocol) {
case JAR_PROTOCOL:
return ResourceType.JAR;
case FILE_PROTOCOL:
return ResourceType.FILE;
default:
return ResourceType.INVALID;
}
}
public static boolean isClassFile(String path) {
if (path == null || "".equals(path))
return false;
return path.endsWith(CLASS_FILE_SUFFIX);
}
/**
* Return main path of the url.
* Example:
* "file:/com/example/hello" to "/com/example/hello"
* "jar:file:/com/example/hello.jar!/" to "/com/example/hello.jar"
*/
public static String getUrlMainPath(URL url) throws UnsupportedEncodingException {
if (url == null)
return "";
String filePath = URLDecoder.decode(url.getFile(), "utf-8");
// if file is not the jar
int pos = filePath.indexOf("!");
if (-1 == pos)
return filePath;
return filePath.substring(5, pos);
}
public static String concat(Object... args) {
if (args == null || args.length == 0)
return "";
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < args.length; i++)
stringBuilder.append(args[i]);
return stringBuilder.toString();
}
}
public enum ResourceType {
JAR("jar"),
FILE("file"),
CLASS_FILE("class"),
INVALID("invalid");
private String typeName;
public String getTypeName() {
return this.typeName;
}
private ResourceType(String typeName) {
this.typeName = typeName;
}
}
/**
* Interface ScannedClassHandler is the callback interface function for handle class of the after the scan.
*
* Created by SylvanasSun on 10/13/2017.
*/
public interface ScannedClassHandler {
void execute(Class<?> clazz);
}
/**
* Class SimplePackageScanner is a package scanner which implements interface PackageScanner
* and it offers functionally very simple.
*
* Created by SylvanasSun on 10/13/2017.
*/
public class SimplePackageScanner implements PackageScanner {
protected String packageName;
protected String packagePath;
protected ClassLoader classLoader;
private Logger logger;
public SimplePackageScanner() {
this.classLoader = Thread.currentThread().getContextClassLoader();
this.logger = LoggerFactory.getLogger(SimplePackageScanner.class);
}
@Override
public List<Class<?>> scan(String packageName) {
return this.scan(packageName, null);
}
@Override
public List<Class<?>> scan(String packageName, ScannedClassHandler handler) {
this.initPackageNameAndPath(packageName);
if (logger.isDebugEnabled())
logger.debug("Start scanning package: {} ....", this.packageName);
URL url = this.getResource(this.packagePath);
if (url == null)
return new ArrayList<>();
return this.parseUrlThenScan(url, handler);
}
private void initPackageNameAndPath(String packageName) {
this.packageName = packageName;
this.packagePath = PathUtils.packageToPath(packageName);
}
protected URL getResource(String packagePath) {
URL url = this.classLoader.getResource(packagePath);
if (url != null)
logger.debug("Get resource: {} success!", packagePath);
else
logger.debug("Get resource: {} failed,end of scan.", packagePath);
return url;
}
protected List<Class<?>> parseUrlThenScan(URL url, ScannedClassHandler handler) {
String urlPath = "";
try {
urlPath = PathUtils.getUrlMainPath(url);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
logger.debug("Get url path failed.");
}
// decide file type
ResourceType type = PathUtils.getResourceType(url);
List<Class<?>> classList = new ArrayList<>();
try {
switch (type) {
case FILE:
classList = this.getClassListFromFile(urlPath, this.packageName);
break;
case JAR:
classList = this.getClassListFromJar(urlPath);
break;
default:
logger.debug("Unsupported file type.");
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
logger.debug("Get class list failed.");
}
this.invokeCallback(classList, handler);
logger.debug("End of scan <{}>.", urlPath);
return classList;
}
protected List<Class<?>> getClassListFromFile(String path, String packageName) throws ClassNotFoundException {
File file = new File(path);
List<Class<?>> classList = new ArrayList<>();
File[] listFiles = file.listFiles();
if (listFiles != null) {
for (File f : listFiles) {
if (f.isDirectory()) {
List<Class<?>> list = getClassListFromFile(f.getAbsolutePath(),
PathUtils.concat(packageName, ".", f.getName()));
classList.addAll(list);
} else if (PathUtils.isClassFile(f.getName())) {
// only add class file that not contain "$"
String className = PathUtils.trimSuffix(f.getName());
if (-1 != className.lastIndexOf("$"))
continue;
String finalClassName = PathUtils.concat(packageName, ".", className);
classList.add(Class.forName(finalClassName));
}
}
}
return classList;
}
protected List<Class<?>> getClassListFromJar(String jarPath) throws IOException, ClassNotFoundException {
if (logger.isDebugEnabled())
logger.debug("Start scanning jar: {}", jarPath);
JarInputStream jarInputStream = new JarInputStream(new FileInputStream(jarPath));
JarEntry jarEntry = jarInputStream.getNextJarEntry();
List<Class<?>> classList = new ArrayList<>();
while (jarEntry != null) {
String name = jarEntry.getName();
if (name.startsWith(this.packageName) && PathUtils.isClassFile(name))
classList.add(Class.forName(name));
jarEntry = jarInputStream.getNextJarEntry();
}
return classList;
}
protected void invokeCallback(List<Class<?>> classList, ScannedClassHandler handler) {
if (classList != null && handler != null) {
for (Class<?> clazz : classList) {
handler.execute(clazz);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment