1. package org.tinygame.herostory.util;
    2. import java.io.File;
    3. import java.io.FileInputStream;
    4. import java.net.URL;
    5. import java.util.*;
    6. import java.util.jar.JarEntry;
    7. import java.util.jar.JarInputStream;
    8. /**
    9. * 名称空间实用工具
    10. */
    11. public final class PackageUtil {
    12. /**
    13. * 类默认构造器
    14. */
    15. private PackageUtil() {
    16. }
    17. /**
    18. * 列表指定包中的所有子类
    19. *
    20. * @param packageName 包名称
    21. * @param recursive 是否递归查找
    22. * @param superClazz 父类的类型
    23. * @return 子类集合
    24. */
    25. static public Set<Class<?>> listSubClazz(
    26. String packageName,
    27. boolean recursive,
    28. Class<?> superClazz) {
    29. if (superClazz == null) {
    30. return Collections.emptySet();
    31. } else {
    32. return listClazz(packageName, recursive, superClazz::isAssignableFrom);
    33. }
    34. }
    35. /**
    36. * 列表指定包中的所有类
    37. *
    38. * @param packageName 包名称
    39. * @param recursive 是否递归查找?
    40. * @param filter 过滤器
    41. * @return 符合条件的类集合
    42. */
    43. static public Set<Class<?>> listClazz(
    44. String packageName, boolean recursive, IClazzFilter filter) {
    45. if (packageName == null ||
    46. packageName.isEmpty()) {
    47. return null;
    48. }
    49. // 将点转换成斜杠
    50. final String packagePath = packageName.replace('.', '/');
    51. // 获取类加载器
    52. ClassLoader cl = Thread.currentThread().getContextClassLoader();
    53. // 结果集合
    54. Set<Class<?>> resultSet = new HashSet<>();
    55. try {
    56. // 获取 URL 枚举
    57. Enumeration<URL> urlEnum = cl.getResources(packagePath);
    58. while (urlEnum.hasMoreElements()) {
    59. // 获取当前 URL
    60. URL currUrl = urlEnum.nextElement();
    61. // 获取协议文本
    62. final String protocol = currUrl.getProtocol();
    63. // 定义临时集合
    64. Set<Class<?>> tmpSet = null;
    65. if ("FILE".equalsIgnoreCase(protocol)) {
    66. // 从文件系统中加载类
    67. tmpSet = listClazzFromDir(
    68. new File(currUrl.getFile()), packageName, recursive, filter
    69. );
    70. } else if ("JAR".equalsIgnoreCase(protocol)) {
    71. // 获取文件字符串
    72. String fileStr = currUrl.getFile();
    73. if (fileStr.startsWith("file:")) {
    74. // 如果是以 "file:" 开头的,
    75. // 则去除这个开头
    76. fileStr = fileStr.substring(5);
    77. }
    78. if (fileStr.lastIndexOf('!') > 0) {
    79. // 如果有 '!' 字符,
    80. // 则截断 '!' 字符之后的所有字符
    81. fileStr = fileStr.substring(0, fileStr.lastIndexOf('!'));
    82. }
    83. // 从 JAR 文件中加载类
    84. tmpSet = listClazzFromJar(
    85. new File(fileStr), packageName, recursive, filter
    86. );
    87. }
    88. if (tmpSet != null) {
    89. // 如果类集合不为空,
    90. // 则添加到结果中
    91. resultSet.addAll(tmpSet);
    92. }
    93. }
    94. } catch (Exception ex) {
    95. // 抛出异常!
    96. throw new RuntimeException(ex);
    97. }
    98. return resultSet;
    99. }
    100. /**
    101. * 从目录中获取类列表
    102. *
    103. * @param dirFile 目录
    104. * @param packageName 包名称
    105. * @param recursive 是否递归查询子包
    106. * @param filter 类过滤器
    107. * @return 符合条件的类集合
    108. */
    109. static private Set<Class<?>> listClazzFromDir(
    110. final File dirFile, final String packageName, final boolean recursive, IClazzFilter filter) {
    111. if (!dirFile.exists() ||
    112. !dirFile.isDirectory()) {
    113. // 如果参数对象为空,
    114. // 则直接退出!
    115. return null;
    116. }
    117. // 获取子文件列表
    118. File[] subFileArr = dirFile.listFiles();
    119. if (subFileArr == null ||
    120. subFileArr.length <= 0) {
    121. return null;
    122. }
    123. // 文件队列, 将子文件列表添加到队列
    124. Queue<File> fileQ = new LinkedList<>(Arrays.asList(subFileArr));
    125. // 结果对象
    126. Set<Class<?>> resultSet = new HashSet<>();
    127. while (!fileQ.isEmpty()) {
    128. // 从队列中获取文件
    129. File currFile = fileQ.poll();
    130. if (currFile.isDirectory() &&
    131. recursive) {
    132. // 如果当前文件是目录,
    133. // 并且是执行递归操作时,
    134. // 获取子文件列表
    135. subFileArr = currFile.listFiles();
    136. if (subFileArr != null &&
    137. subFileArr.length > 0) {
    138. // 添加文件到队列
    139. fileQ.addAll(Arrays.asList(subFileArr));
    140. }
    141. continue;
    142. }
    143. if (!currFile.isFile() ||
    144. !currFile.getName().endsWith(".class")) {
    145. // 如果当前文件不是文件,
    146. // 或者文件名不是以 .class 结尾,
    147. // 则直接跳过
    148. continue;
    149. }
    150. // 类名称
    151. String clazzName;
    152. // 设置类名称
    153. clazzName = currFile.getAbsolutePath();
    154. // 清除最后的 .class 结尾
    155. clazzName = clazzName.substring(dirFile.getAbsolutePath().length(), clazzName.lastIndexOf('.'));
    156. // 转换目录斜杠
    157. clazzName = clazzName.replace('\\', '/');
    158. // 清除开头的 /
    159. clazzName = trimLeft(clazzName, "/");
    160. // 将所有的 / 修改为 .
    161. clazzName = join(clazzName.split("/"), ".");
    162. // 包名 + 类名
    163. clazzName = packageName + "." + clazzName;
    164. try {
    165. // 加载类定义
    166. Class<?> clazzObj = Class.forName(clazzName);
    167. if (null != filter &&
    168. !filter.accept(clazzObj)) {
    169. // 如果过滤器不为空,
    170. // 且过滤器不接受当前类,
    171. // 则直接跳过!
    172. continue;
    173. }
    174. // 添加类定义到集合
    175. resultSet.add(clazzObj);
    176. } catch (Exception ex) {
    177. // 抛出异常
    178. throw new RuntimeException(ex);
    179. }
    180. }
    181. return resultSet;
    182. }
    183. /**
    184. * 从 .jar 文件中获取类列表
    185. *
    186. * @param jarFilePath .jar 文件路径
    187. * @param recursive 是否递归查询子包
    188. * @param filter 类过滤器
    189. * @return 符合条件的类集合
    190. */
    191. static private Set<Class<?>> listClazzFromJar(
    192. final File jarFilePath, final String packageName, final boolean recursive, IClazzFilter filter) {
    193. if (jarFilePath == null ||
    194. jarFilePath.isDirectory()) {
    195. // 如果参数对象为空,
    196. // 则直接退出!
    197. return null;
    198. }
    199. // 结果对象
    200. Set<Class<?>> resultSet = new HashSet<>();
    201. try {
    202. // 创建 .jar 文件读入流
    203. JarInputStream jarIn = new JarInputStream(new FileInputStream(jarFilePath));
    204. // 进入点
    205. JarEntry entry;
    206. while ((entry = jarIn.getNextJarEntry()) != null) {
    207. if (entry.isDirectory()) {
    208. continue;
    209. }
    210. // 获取进入点名称
    211. String entryName = entry.getName();
    212. if (!entryName.endsWith(".class")) {
    213. // 如果不是以 .class 结尾,
    214. // 则说明不是 JAVA 类文件, 直接跳过!
    215. continue;
    216. }
    217. if (!recursive) {
    218. //
    219. // 如果没有开启递归模式,
    220. // 那么就需要判断当前 .class 文件是否在指定目录下?
    221. //
    222. // 获取目录名称
    223. String tmpStr = entryName.substring(0, entryName.lastIndexOf('/'));
    224. // 将目录中的 "/" 全部替换成 "."
    225. tmpStr = join(tmpStr.split("/"), ".");
    226. if (!packageName.equals(tmpStr)) {
    227. // 如果包名和目录名不相等,
    228. // 则直接跳过!
    229. continue;
    230. }
    231. }
    232. String clazzName;
    233. // 清除最后的 .class 结尾
    234. clazzName = entryName.substring(0, entryName.lastIndexOf('.'));
    235. // 将所有的 / 修改为 .
    236. clazzName = join(clazzName.split("/"), ".");
    237. // 加载类定义
    238. Class<?> clazzObj = Class.forName(clazzName);
    239. if (null != filter &&
    240. !filter.accept(clazzObj)) {
    241. // 如果过滤器不为空,
    242. // 且过滤器不接受当前类,
    243. // 则直接跳过!
    244. continue;
    245. }
    246. // 添加类定义到集合
    247. resultSet.add(clazzObj);
    248. }
    249. // 关闭 jar 输入流
    250. jarIn.close();
    251. } catch (Exception ex) {
    252. // 抛出异常
    253. throw new RuntimeException(ex);
    254. }
    255. return resultSet;
    256. }
    257. /**
    258. * 类名称过滤器
    259. *
    260. * @author hjj2019
    261. */
    262. @FunctionalInterface
    263. static public interface IClazzFilter {
    264. /**
    265. * 是否接受当前类?
    266. *
    267. * @param clazz 被筛选的类
    268. * @return 是否符合条件
    269. */
    270. boolean accept(Class<?> clazz);
    271. }
    272. /**
    273. * 使用连接符连接字符串数组
    274. *
    275. * @param strArr 字符串数组
    276. * @param conn 连接符
    277. * @return 连接后的字符串
    278. */
    279. static private String join(String[] strArr, String conn) {
    280. if (null == strArr ||
    281. strArr.length <= 0) {
    282. return "";
    283. }
    284. StringBuilder sb = new StringBuilder();
    285. for (int i = 0; i < strArr.length; i++) {
    286. if (i > 0) {
    287. // 添加连接符
    288. sb.append(conn);
    289. }
    290. // 添加字符串
    291. sb.append(strArr[i]);
    292. }
    293. return sb.toString();
    294. }
    295. /**
    296. * 清除源字符串左边的字符串
    297. *
    298. * @param src 原字符串
    299. * @param trimStr 需要被清除的字符串
    300. * @return 清除后的字符串
    301. */
    302. static private String trimLeft(String src, String trimStr) {
    303. if (null == src ||
    304. src.isEmpty()) {
    305. return "";
    306. }
    307. if (null == trimStr ||
    308. trimStr.isEmpty()) {
    309. return src;
    310. }
    311. if (src.equals(trimStr)) {
    312. return "";
    313. }
    314. while (src.startsWith(trimStr)) {
    315. src = src.substring(trimStr.length());
    316. }
    317. return src;
    318. }
    319. }