Created
October 14, 2017 12:15
Package Scanner
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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