SylvanasSun
10/14/2017 - 12:15 PM

Package Scanner

Package Scanner

/**
 * 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);
            }
        }
    }

}
/**
 * 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);

}
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;
    }

}
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();
    }
    
}
/**
 * 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);

}