All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.kedauis.util.ClasspathPackageScanner Maven / Gradle / Ivy

The newest version!
package com.kedauis.util;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

/**
 * author QiuYu
 * date 2018/1/15
 * time 14:26
 * description 包扫描器
 */
@ConfigurationProperties(prefix = "spring.quartz")
public abstract class ClasspathPackageScanner {
    private Logger logger = LoggerFactory.getLogger(ClasspathPackageScanner.class);

    private String basePackage;
    private ClassLoader cl;

    public ClasspathPackageScanner() {
        this.cl = getClassLoader();
    }

    public abstract ClassLoader getClassLoader();

    public String getBasePackage() {
        return basePackage;
    }

    public void setBasePackage(String basePackage) {
        this.basePackage = basePackage;
    }

    /**
     * Construct an instance with base package and class loader.
     * @param basePackage The base package to scan.
     * @param cl Use this class load to locate the package.
     */
    public ClasspathPackageScanner(String basePackage, ClassLoader cl) {
        this.basePackage = basePackage;
        this.cl = cl;
    }

    /**
     * Get all fully qualified names located in the specified package
     * and its sub-package.
     *
     * @return A list of fully qualified names.
     * @throws IOException can not connect file exception
     */
    public List getFullyQualifiedClassNameList() throws IOException {
        return doScan(basePackage, new ArrayList<>());
    }

    /**
     * Actually perform the scanning procedure.
     *
     * @param basePackage
     * @param nameList A list to contain the result.
     * @return A list of fully qualified names.
     * @throws IOException
     */
    private List doScan(String basePackage, List nameList) throws IOException {

        if(StringUtils.isBlank(basePackage)){
            logger.info("未定义扫描根路径,放弃定时任务扫描!");
            return nameList;
        }
        logger.info("开始扫描包{}下的所有类", basePackage);
        // replace dots with splashes
        String splashPath = dotToSplash(basePackage);

        // get file path
        URL url = cl.getResource(splashPath);
        if(null == url){
            logger.info("路径{}不存在或者路径下不存在任何对象!", basePackage);
            return nameList;
        }
        String filePath = getRootPath(url);

        // Get classes in that package.
        // If the web server unzips the jar file, then the classes will exist in the form of
        // normal file in the directory.
        // If the web server does not unzip the jar file, then classes will exist in jar file.
        List names = null; // contains the name of the class file. e.g., Apple.class will be stored as "Apple"
        if (isJarFile(filePath)) {
            // jar file
            if (logger.isDebugEnabled()) {
                logger.debug("{} 是一个JAR包", filePath);
            }

            names = readFromJarFile(filePath, splashPath);
        } else {
            // directory
            if (logger.isDebugEnabled()) {
                logger.debug("{} 是一个目录", filePath);
            }

            names = readFromDirectory(filePath);
        }

        for (String name : names) {
            if (isClassFile(name)) {
                //nameList.add(basePackage + "." + StringUtil.trimExtension(name));
                nameList.add(toFullyQualifiedName(name, basePackage));
            } else {
                // this is a directory
                // check this directory for more classes
                // do recursive invocation
                doScan(basePackage + "." + name, nameList);
            }
        }

        if (logger.isDebugEnabled()) {
            for (String n : nameList) {
                logger.debug("找到{}", n);
            }
        }

        return nameList;
    }

    /**
     * Convert short class name to fully qualified name.
     * @param basePackage 基础路径
     * @param shortName 短名
     * @return 全名
     */
    private String toFullyQualifiedName(String shortName, String basePackage) {
        StringBuilder sb = new StringBuilder(basePackage);
        sb.append('.');
        sb.append(trimExtension(shortName));

        return sb.toString();
    }

    /**
     * read file from jar file
     * @param jarPath jar's path
     * @param splashedPackageName base package in jar
     * @return file's names
     * @throws IOException jar open exception
     */
    private List readFromJarFile(String jarPath, String splashedPackageName) throws IOException {
        if (logger.isDebugEnabled()) {
            logger.debug("从JAR包中读取类: {}", jarPath);
        }

        JarInputStream jarIn = new JarInputStream(new FileInputStream(jarPath));
        JarEntry entry = jarIn.getNextJarEntry();

        List nameList = new ArrayList<>();
        while (null != entry) {
            String name = entry.getName();
            if (name.startsWith(splashedPackageName) && isClassFile(name)) {
                // find class in jar file, substring it from the index of last '/'
                // set the realClassName in list
                nameList.add(name);
            }

            entry = jarIn.getNextJarEntry();
        }

        return nameList;
    }

    /**
     * read from directory
     * @param path file path
     * @return file's name
     */
    private List readFromDirectory(String path) {
        File file = new File(path);
        String[] names = file.list();

        if (null == names) {
            return null;
        }

        return Arrays.asList(names);
    }

    private boolean isClassFile(String name) {
        return name.endsWith(".class");
    }

    private boolean isJarFile(String name) {
        return name.endsWith(".jar");
    }

    /**
     * "file:/home/whf/cn/fh" to"/home/whf/cn/fh"
     * "jar:file:/home/whf/foo.jar!cn/fh" to "/home/whf/foo.jar"
     * @param url full file path url
     * @return root path
     */
    public static String getRootPath(URL url) {
        String fileUrl = url.getFile();
        int pos = fileUrl.indexOf('!');

        if (-1 == pos) {
            return fileUrl;
        }

        return fileUrl.substring(5, pos);
    }

    /**
     * "cn.fh.lightning" to "cn/fh/lightning"
     * @param name package
     * @return file path
     */
    public static String dotToSplash(String name) {
        return name.replaceAll("\\.", "/");
    }

    /**
     * "Apple.class" to "Apple"
     * "/com/package/Apple.class" to "Apple"
     * @param name className
     * @return class's real name
     */
    public static String trimExtension(String name) {
        int pos = name.indexOf('.');
        if (-1 != pos) {
            String className = name.substring(0, pos);
            int poss = className.lastIndexOf("/");
            if(-1 != poss){
                return className.substring(poss+1);
            }
            return className;
        }

        return name;
    }

    /**
     * /application/home to /home
     * @param uri uri object
     * @return root name
     */
    public static String trimURI(String uri) {
        String trimmed = uri.substring(1);
        int splashIndex = trimmed.indexOf('/');

        return trimmed.substring(splashIndex);
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy