package cn.dreampie.common.util.scan;
import cn.dreampie.common.http.Encoding;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import static cn.dreampie.common.util.Checker.checkNotNull;
/**
* Created by ice on 14-12-19.
*/
public abstract class Scaner<S> {
protected ClassLoader classLoader = Scaner.class.getClassLoader();
protected boolean scanInJar;
protected String targetPattern;
protected String targetSuffix;
protected Set<String> includePathOrPackages = new HashSet<String>();
protected boolean checkTarget(Object target) {
return true;
}
protected String packageFilePathSolve(String filePath) {
return filePath;
}
protected String jarFilePathSolve(String filePath) {
return filePath;
}
protected Enumeration<URL> urlSolve(String baseDir) {
try {
if (!baseDir.contains("/") && baseDir.contains(".")) {
baseDir = baseDir.replaceAll("\\.", "/");
}
return classLoader.getResources(baseDir);
} catch (IOException e) {
throw new ScanException(e.getMessage(), e);
}
}
protected S scanInJar(boolean scanInJar) {
this.scanInJar = scanInJar;
return (S) this;
}
protected S targetPattern(String targetPattern) {
this.targetPattern = targetPattern;
if (!targetPattern.contains(".")) {
throw new ScanException("TargetPattern must contains '.'");
}
targetSuffix = targetPattern.substring(targetPattern.indexOf("."));
return (S) this;
}
/**
* scan to Class
*
* @param <T> 返回的lcass类型
* @return 搜索到的class
*/
public <T> Set<Class<? extends T>> scanToClass() {
Set<Class<? extends T>> result = new HashSet<Class<? extends T>>();
Set<String> fileSet = scan();
if (fileSet.size() > 0) {
for (String classFile : fileSet) {
Class<?> classInFile;
try {
classInFile = classLoader.loadClass(classFile);
} catch (ClassNotFoundException e) {
throw new ScanException(e.getMessage(), e);
}
if (checkTarget(classInFile)) {
result.add((Class<? extends T>) classInFile);
}
}
}
return result;
}
/**
* scan to File
*
* @return
*/
public Set<File> scanToFile() {
Set<File> result = new HashSet<File>();
Set<String> fileSet = scan();
if (fileSet.size() > 0) {
File file;
for (String path : fileSet) {
file = new File(path);
if (file.exists()) {
result.add(file);
}
}
}
return result;
}
public Set<String> scan() {
Set<String> fileSet = new HashSet<String>();
if (includePathOrPackages.size() > 0) {
for (String pkg : includePathOrPackages) {
fileSet.addAll(findFiles(pkg, targetPattern));
}
}
return fileSet;
}
/**
* 递归查找文件
*
* @param baseDirName 查找的文件夹路径
* @param targetFileName 需要查找的文件名
*/
protected Set<String> findFiles(String baseDirName, String targetFileName) {
/**
* 算法简述: 从某个给定的需查找的文件夹出发,搜索该文件夹的所有子文件夹及文件, 若为文件,则进行匹配,匹配成功则加入结果集,若为子文件夹,则进队列。 队列不空,重复上述操作,队列为空,程序结束,返回结果。
*/
Set<String> allFiles = new HashSet<String>();
//判断文件路径
Enumeration<URL> baseURLs = urlSolve(baseDirName);
URL baseURL = null;
if (baseURLs != null) {
while (baseURLs.hasMoreElements()) {
baseURL = baseURLs.nextElement();
if (baseURL != null) {
// 得到协议的名称
String protocol = baseURL.getProtocol();
String basePath = baseURL.getFile();
// 如果是以文件的形式保存在服务器上
if (scanInJar && "jar".equals(protocol)) {
String[] paths = basePath.split("!/");
// 获取jar
try {
allFiles.addAll(findJarFiles(URLDecoder.decode(paths[0].replace("file:", ""), Encoding.UTF_8.name()), paths[1]));
} catch (IOException e) {
throw new ScanException(e.getMessage(), e);
}
} else {
allFiles.addAll(findPackageFiles(basePath, targetFileName));
}
}
}
}
return allFiles;
}
/**
* 查找根目录下的文件
*
* @param basePathName 路径
* @param targetFileName 文件匹配
* @return Set
*/
private Set<String> findPackageFiles(String basePathName, String targetFileName) {
Set<String> packageFiles = new HashSet<String>();
String tempName = null;
// 判断目录是否存在
File baseFile = null;
try {
baseFile = new File(URLDecoder.decode(basePathName, Encoding.UTF_8.name()));
} catch (UnsupportedEncodingException e) {
throw new ScanException(e.getMessage(), e);
}
String filePath = null;
if (!baseFile.exists()) {
throw new ScanException("Search error : " + basePathName + " not found.");
} else {
if (baseFile.isDirectory()) {
String[] fileList = baseFile.list();
if (fileList != null && fileList.length > 0) {
for (String file : fileList) {
File readfile = null;
try {
readfile = new File(URLDecoder.decode(basePathName + File.separator + file, Encoding.UTF_8.name()));
} catch (UnsupportedEncodingException e) {
throw new ScanException(e.getMessage(), e);
}
//文件目录
if (readfile.isDirectory()) {
packageFiles.addAll(findPackageFiles(basePathName + File.separator + file, targetFileName));
} else {
tempName = readfile.getName();
//判断是否是目标文件
if (matchTargetPattern(targetFileName, tempName)) {
filePath = readfile.getAbsoluteFile().toString().replaceAll("\\\\", "/");
packageFiles.add(packageFilePathSolve(filePath));
}
}
}
}
} else {
tempName = baseFile.getName();
//判断是否是目标文件
if (matchTargetPattern(targetFileName, tempName)) {
filePath = baseFile.getAbsoluteFile().toString().replaceAll("\\\\", "/");
packageFiles.add(packageFilePathSolve(filePath));
}
}
}
return packageFiles;
}
/**
* find jar file
*
* @param filePath 文件路径
* @param packageName 包名
* @return list
* @throws IOException 文件读取异常
*/
private Set<String> findJarFiles(String filePath, String packageName) throws IOException {
JarFile localJarFile = new JarFile(new File(filePath));
Set<String> inJarFiles = findInJarFiles(localJarFile, packageName);
localJarFile.close();
return inJarFiles;
}
/**
* fin in jar files
*
* @param localJarFile
* @param packageName
* @return
*/
private Set<String> findInJarFiles(JarFile localJarFile, String packageName) {
Set<String> inJarFiles = new HashSet<String>();
Enumeration<JarEntry> entries = localJarFile.entries();
while (entries.hasMoreElements()) {
JarEntry jarEntry = entries.nextElement();
String entryName = jarEntry.getName();
if (!jarEntry.isDirectory() && (packageName == null || entryName.startsWith(packageName)) && entryName.endsWith(targetSuffix)) {
inJarFiles.add(jarFilePathSolve(entryName));
}
}
return inJarFiles;
}
public Scaner include(String... pathOrPackages) {
checkNotNull(pathOrPackages, "File path or packages could not be null.");
Collections.addAll(includePathOrPackages, pathOrPackages);
return this;
}
public Scaner include(Set<String> pathOrPackages) {
checkNotNull(pathOrPackages, "File path or packages could not be null.");
for (String pkg : pathOrPackages) {
this.includePathOrPackages.add(pkg);
}
return this;
}
/**
* 通配符匹配
*
* @param pattern 通配符模式
* @param str 待匹配的字符串
* 匹配成功则返回true,否则返回false
*/
private boolean matchTargetPattern(String pattern, String str) {
int patternLength = pattern.length();
int strLength = str.length();
int strIndex = 0;
char ch;
for (int patternIndex = 0; patternIndex < patternLength; patternIndex++) {
ch = pattern.charAt(patternIndex);
if (ch == '*') {
// 通配符星号*表示可以匹配任意多个字符
while (strIndex < strLength) {
if (matchTargetPattern(pattern.substring(patternIndex + 1), str.substring(strIndex))) {
return true;
}
strIndex++;
}
} else if (ch == '?') {
// 通配符问号?表示匹配任意一个字符
strIndex++;
if (strIndex > strLength) {
// 表示str中已经没有字符匹配?了。
return false;
}
} else {
if ((strIndex >= strLength) || (ch != str.charAt(strIndex))) {
return false;
}
strIndex++;
}
}
return strIndex == strLength;
}
}