From b2ee5fbfec75d157617aaed1574fad7c08dcd579 Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 29 Nov 2020 15:18:00 +0800 Subject: [PATCH 1/4] add compiler --- CHANGELOG.md | 2 + .../core/compiler/CompilerException.java | 34 +++ .../cn/hutool/core/compiler/CompilerUtil.java | 56 +++++ .../hutool/core/compiler/DiagnosticUtil.java | 26 ++ .../core/compiler/JavaClassFileManager.java | 43 +--- .../core/compiler/JavaFileObjectUtil.java | 75 ++++++ .../core/compiler/JavaSourceCompiler.java | 106 ++------ .../core/compiler/JavaSourceFileObject.java | 25 +- .../main/java/cn/hutool/core/io/FileUtil.java | 36 ++- .../main/java/cn/hutool/core/io/IoUtil.java | 18 +- .../cn/hutool/core/io/file/FileNameUtil.java | 25 ++ .../cn/hutool/core/io/file/FileReader.java | 27 ++- .../cn/hutool/core/io/file/FileWriter.java | 38 ++- .../java/cn/hutool/core/io/file/PathUtil.java | 12 + .../core/io/resource/BytesResource.java | 7 - .../io/resource/CharSequenceResource.java | 90 +++++++ .../core/io/resource/FileObjectResource.java | 73 ++++++ .../core/io/resource/InputStreamResource.java | 33 --- .../cn/hutool/core/io/resource/Resource.java | 12 +- .../core/io/resource/StringResource.java | 53 +--- .../hutool/core/io/resource/UrlResource.java | 38 --- .../cn/hutool/core/lang/JarClassLoader.java | 2 +- .../hutool/core/lang/ResourceClassLoader.java | 52 ++++ .../java/cn/hutool/core/util/ZipUtil.java | 226 +++++++++++------- .../core/compiler/JavaSourceCompilerTest.java | 1 + .../core/io/resource/ResourceUtilTest.java | 9 + 26 files changed, 743 insertions(+), 376 deletions(-) create mode 100644 hutool-core/src/main/java/cn/hutool/core/compiler/CompilerException.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/compiler/CompilerUtil.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/compiler/DiagnosticUtil.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/compiler/JavaFileObjectUtil.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/io/resource/CharSequenceResource.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/io/resource/FileObjectResource.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/lang/ResourceClassLoader.java diff --git a/CHANGELOG.md b/CHANGELOG.md index c44ecbfa6..78aa0974d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,8 @@ * 【cache 】 增加CacheListener(issue#1257@Github) * 【core 】 TimeInterval支持分组(issue#1238@Github) * 【core 】 增加compile包(pr#1243@Github) +* 【core 】 增加ResourceClassLoader、CharSequenceResource、FileObjectResource +* 【core 】 修改IoUtil.read(Reader)逻辑默认关闭Reader ### Bug修复 * 【cron 】 修复CronTimer可能死循环的问题(issue#1224@Github) diff --git a/hutool-core/src/main/java/cn/hutool/core/compiler/CompilerException.java b/hutool-core/src/main/java/cn/hutool/core/compiler/CompilerException.java new file mode 100644 index 000000000..8cf07e8be --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/compiler/CompilerException.java @@ -0,0 +1,34 @@ +package cn.hutool.core.compiler; + +import cn.hutool.core.exceptions.ExceptionUtil; +import cn.hutool.core.util.StrUtil; + +/** + * 编译异常 + * + * @author looly + * @since 5.5.2 + */ +public class CompilerException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public CompilerException(Throwable e) { + super(ExceptionUtil.getMessage(e), e); + } + + public CompilerException(String message) { + super(message); + } + + public CompilerException(String messageTemplate, Object... params) { + super(StrUtil.format(messageTemplate, params)); + } + + public CompilerException(String message, Throwable throwable) { + super(message, throwable); + } + + public CompilerException(Throwable throwable, String messageTemplate, Object... params) { + super(StrUtil.format(messageTemplate, params), throwable); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/compiler/CompilerUtil.java b/hutool-core/src/main/java/cn/hutool/core/compiler/CompilerUtil.java new file mode 100644 index 000000000..707983a14 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/compiler/CompilerUtil.java @@ -0,0 +1,56 @@ +package cn.hutool.core.compiler; + +import javax.tools.DiagnosticListener; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileObject; +import javax.tools.ToolProvider; + +/** + * 源码编译工具类,主要封装{@link JavaCompiler} 相关功能 + * + * @author looly + * @since 5.5.2 + */ +public class CompilerUtil { + /** + * java 编译器 + */ + public static final JavaCompiler SYSTEM_COMPILER = ToolProvider.getSystemJavaCompiler(); + + /** + * 编译指定的源码文件 + * + * @param sourceFiles 源码文件路径 + * @return 0表示成功,否则其他 + */ + public static boolean compile(String... sourceFiles) { + return 0 == SYSTEM_COMPILER.run(null, null, null, sourceFiles); + } + + /** + * 获取{@link JavaFileManager} + * + * @return {@link JavaFileManager} + */ + public static JavaFileManager getFileManager() { + return SYSTEM_COMPILER.getStandardFileManager(null, null, null); + } + + /** + * 新建编译任务 + * + * @param fileManager {@link JavaFileManager},用于管理已经编译好的文件 + * @param diagnosticListener 诊断监听 + * @param options 选项,例如 -cpXXX等 + * @param compilationUnits 编译单元,即需要编译的对象 + * @return {@link JavaCompiler.CompilationTask} + */ + public static JavaCompiler.CompilationTask getTask( + JavaFileManager fileManager, + DiagnosticListener diagnosticListener, + Iterable options, + Iterable compilationUnits) { + return SYSTEM_COMPILER.getTask(null, fileManager, diagnosticListener, options, null, compilationUnits); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/compiler/DiagnosticUtil.java b/hutool-core/src/main/java/cn/hutool/core/compiler/DiagnosticUtil.java new file mode 100644 index 000000000..fe04dba5f --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/compiler/DiagnosticUtil.java @@ -0,0 +1,26 @@ +package cn.hutool.core.compiler; + +import javax.tools.DiagnosticCollector; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 诊断工具类 + * + * @author looly + * @since 5.5.2 + */ +public class DiagnosticUtil { + + /** + * 获取{@link DiagnosticCollector}收集到的诊断信息,以文本返回 + * + * @param collector {@link DiagnosticCollector} + * @return 诊断消息 + */ + public static String getMessages(DiagnosticCollector collector) { + final List diagnostics = collector.getDiagnostics(); + return diagnostics.stream().map(String::valueOf) + .collect(Collectors.joining(System.lineSeparator())); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/compiler/JavaClassFileManager.java b/hutool-core/src/main/java/cn/hutool/core/compiler/JavaClassFileManager.java index 0de53681c..9960a7320 100644 --- a/hutool-core/src/main/java/cn/hutool/core/compiler/JavaClassFileManager.java +++ b/hutool-core/src/main/java/cn/hutool/core/compiler/JavaClassFileManager.java @@ -1,7 +1,7 @@ package cn.hutool.core.compiler; -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.resource.FileObjectResource; +import cn.hutool.core.lang.ResourceClassLoader; import cn.hutool.core.util.ClassLoaderUtil; import cn.hutool.core.util.ObjectUtil; @@ -10,16 +10,16 @@ import javax.tools.ForwardingJavaFileManager; import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; import javax.tools.JavaFileObject.Kind; -import java.io.IOException; -import java.io.InputStream; -import java.security.SecureClassLoader; import java.util.HashMap; import java.util.Map; /** - * Java 字节码文件对象 + * Java 字节码文件对象管理器 + * + *

* 正常我们使用javac命令编译源码时会将class文件写入到磁盘中,但在运行时动态编译类不适合保存在磁盘中 - * 我们采取此对象来管理运行时动态编译类生成的字节码 + * 我们采取此对象来管理运行时动态编译类生成的字节码。 + *

* * @author lzpeng * @since 5.5.2 @@ -29,7 +29,7 @@ class JavaClassFileManager extends ForwardingJavaFileManager { /** * 存储java字节码文件对象映射 */ - private final Map javaFileObjectMap = new HashMap<>(); + private final Map classFileObjectMap = new HashMap<>(); /** * 加载动态编译生成类的父类加载器 @@ -55,28 +55,7 @@ class JavaClassFileManager extends ForwardingJavaFileManager { */ @Override public ClassLoader getClassLoader(final Location location) { - return new SecureClassLoader(parent) { - - /** - * 查找类 - * @param name 类名 - * @return 类的class对象 - * @throws ClassNotFoundException 未找到类异常 - */ - @Override - protected Class findClass(final String name) throws ClassNotFoundException { - final JavaFileObject javaFileObject = javaFileObjectMap.get(name); - if (null != javaFileObject) { - try(final InputStream inputStream = javaFileObject.openInputStream()){ - final byte[] bytes = IoUtil.readBytes(inputStream); - return defineClass(name, bytes, 0, bytes.length); - } catch (IOException e) { - throw new IORuntimeException(e); - } - } - throw new ClassNotFoundException(name); - } - }; + return new ResourceClassLoader<>(this.parent, this.classFileObjectMap); } /** @@ -86,13 +65,13 @@ class JavaClassFileManager extends ForwardingJavaFileManager { * @param location 源码位置 * @param className 类名 * @param kind 文件类型 - * @param sibling 将Java源码对象 + * @param sibling Java源码对象 * @return Java字节码文件对象 */ @Override public JavaFileObject getJavaFileForOutput(final Location location, final String className, final Kind kind, final FileObject sibling) { final JavaFileObject javaFileObject = new JavaClassFileObject(className, kind); - javaFileObjectMap.put(className, javaFileObject); + this.classFileObjectMap.put(className, new FileObjectResource(javaFileObject)); return javaFileObject; } diff --git a/hutool-core/src/main/java/cn/hutool/core/compiler/JavaFileObjectUtil.java b/hutool-core/src/main/java/cn/hutool/core/compiler/JavaFileObjectUtil.java new file mode 100644 index 000000000..9da8ac4e3 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/compiler/JavaFileObjectUtil.java @@ -0,0 +1,75 @@ +package cn.hutool.core.compiler; + +import cn.hutool.core.io.file.FileNameUtil; +import cn.hutool.core.util.ZipUtil; + +import javax.tools.JavaFileObject; +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.zip.ZipFile; + +/** + * {@link JavaFileObject} 相关工具类封装 + * + * @author lzpeng, looly + * @since 5.5.2 + */ +public class JavaFileObjectUtil { + + /** + * 获取指定文件下的所有待编译的java文件,并以{@link JavaFileObject}形式返回 + * + * @param file 文件或目录,文件支持.java、.jar和.zip文件 + * @return 所有待编译的 {@link JavaFileObject} + */ + public static List getJavaFileObjects(File file) { + final List result = new ArrayList<>(); + final String fileName = file.getName(); + + if (isJavaFile(fileName)) { + result.add(new JavaSourceFileObject(file.toURI())); + } else if (isJarOrZipFile(fileName)) { + result.addAll(getJavaFileObjectByZipOrJarFile(file)); + } + return result; + } + + /** + * 是否是jar 或 zip 文件 + * + * @param fileName 文件名 + * @return 是否是jar 或 zip 文件 + */ + public static boolean isJarOrZipFile(String fileName) { + return FileNameUtil.isType(fileName, "jar", "zip"); + } + + /** + * 是否是java文件 + * + * @param fileName 文件名 + * @return 是否是.java文件 + */ + public static boolean isJavaFile(String fileName) { + return FileNameUtil.isType(fileName, "java"); + } + + /** + * 通过zip包或jar包创建Java文件对象 + * + * @param file 压缩文件 + * @return Java文件对象 + */ + private static List getJavaFileObjectByZipOrJarFile(File file) { + final List collection = new ArrayList<>(); + final ZipFile zipFile = ZipUtil.toZipFile(file, null); + ZipUtil.read(zipFile, (zipEntry) -> { + final String name = zipEntry.getName(); + if (isJavaFile(name)) { + collection.add(new JavaSourceFileObject(name, ZipUtil.getStream(zipFile, zipEntry))); + } + }); + return collection; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceCompiler.java b/hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceCompiler.java index 9f2650c8e..076f7fc60 100644 --- a/hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceCompiler.java +++ b/hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceCompiler.java @@ -9,28 +9,21 @@ import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.URLUtil; import javax.tools.DiagnosticCollector; -import javax.tools.JavaCompiler; import javax.tools.JavaCompiler.CompilationTask; import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; import javax.tools.StandardLocation; -import javax.tools.ToolProvider; import java.io.File; -import java.io.IOException; -import java.io.InputStream; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.Enumeration; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; /** * Java 源码编译器 @@ -39,11 +32,6 @@ import java.util.zip.ZipFile; */ public class JavaSourceCompiler { - /** - * java 编译器 - */ - private static final JavaCompiler JAVA_COMPILER = ToolProvider.getSystemJavaCompiler(); - /** * 待编译的文件 可以是 .java文件 压缩文件 文件夹 递归搜索文件夹内的zip包和jar包 */ @@ -68,10 +56,10 @@ public class JavaSourceCompiler { /** * 构造 * - * @param parent 父类加载器 + * @param parent 父类加载器,null则使用默认类加载器 */ private JavaSourceCompiler(ClassLoader parent) { - this.parentClassLoader = parent; + this.parentClassLoader = ObjectUtil.defaultIfNull(parent, ClassLoaderUtil.getClassLoader()); } @@ -145,22 +133,19 @@ public class JavaSourceCompiler { * @return 类加载器 */ public ClassLoader compile() { - final ClassLoader parent = ObjectUtil.defaultIfNull(this.parentClassLoader, ClassLoaderUtil.getClassLoader()); - // 获得classPath final List classPath = getClassPath(); final URL[] urLs = URLUtil.getURLs(classPath.toArray(new File[0])); - final URLClassLoader ucl = URLClassLoader.newInstance(urLs, parent); + final URLClassLoader ucl = URLClassLoader.newInstance(urLs, this.parentClassLoader); if (sourceCodeMap.isEmpty() && sourceFileList.isEmpty()) { // 没有需要编译的源码 return ucl; } + // 没有需要编译的源码文件返回加载zip或jar包的类加载器 - final Iterable javaFileObjectList = getJavaFileObject(); // 创建编译器 - final JavaFileManager standardJavaFileManager = JAVA_COMPILER.getStandardFileManager(null, null, null); - final JavaFileManager javaFileManager = new JavaClassFileManager(ucl, standardJavaFileManager); + final JavaFileManager javaFileManager = new JavaClassFileManager(ucl, CompilerUtil.getFileManager()); // classpath final List options = new ArrayList<>(); @@ -172,17 +157,14 @@ public class JavaSourceCompiler { // 编译文件 final DiagnosticCollector diagnosticCollector = new DiagnosticCollector<>(); - final CompilationTask task = JAVA_COMPILER.getTask(null, javaFileManager, diagnosticCollector, - options, null, javaFileObjectList); + final List javaFileObjectList = getJavaFileObject(); + final CompilationTask task = CompilerUtil.getTask(javaFileManager, diagnosticCollector, options, javaFileObjectList); if (task.call()) { + // 加载编译后的类 return javaFileManager.getClassLoader(StandardLocation.CLASS_OUTPUT); } else { // 编译失败,收集错误信息 - final List diagnostics = diagnosticCollector.getDiagnostics(); - final String errorMsg = diagnostics.stream().map(String::valueOf) - .collect(Collectors.joining(System.lineSeparator())); - // CompileException - throw new RuntimeException(errorMsg); + throw new CompilerException(DiagnosticUtil.getMessages(diagnosticCollector)); } } @@ -194,7 +176,7 @@ public class JavaSourceCompiler { private List getClassPath() { List classPathFileList = new ArrayList<>(); for (File file : libraryFileList) { - List jarOrZipFile = FileUtil.loopFiles(file, this::isJarOrZipFile); + List jarOrZipFile = FileUtil.loopFiles(file, (subFile)-> JavaFileObjectUtil.isJarOrZipFile(subFile.getName())); classPathFileList.addAll(jarOrZipFile); if (file.isDirectory()) { classPathFileList.add(file); @@ -208,20 +190,14 @@ public class JavaSourceCompiler { * * @return 待编译的Java文件对象 */ - private Iterable getJavaFileObject() { - final Collection collection = new ArrayList<>(); + private List getJavaFileObject() { + final List collection = new ArrayList<>(); + + // 源码文件 for (File file : sourceFileList) { - // .java 文件 - final List javaFileList = FileUtil.loopFiles(file, this::isJavaFile); - for (File javaFile : javaFileList) { - collection.add(getJavaFileObjectByJavaFile(javaFile)); - } - // 压缩包 - final List jarOrZipFileList = FileUtil.loopFiles(file, this::isJarOrZipFile); - for (File jarOrZipFile : jarOrZipFileList) { - collection.addAll(getJavaFileObjectByZipOrJarFile(jarOrZipFile)); - } + FileUtil.walkFiles(file, (subFile)-> collection.addAll(JavaFileObjectUtil.getJavaFileObjects(file))); } + // 源码Map collection.addAll(getJavaFileObjectByMap(this.sourceCodeMap)); return collection; @@ -252,54 +228,4 @@ public class JavaSourceCompiler { return new JavaSourceFileObject(file.toURI()); } - /** - * 通过zip包或jar包创建Java文件对象 - * - * @param file 压缩文件 - * @return Java文件对象 - */ - private Collection getJavaFileObjectByZipOrJarFile(final File file) { - final Collection collection = new ArrayList<>(); - try { - final ZipFile zipFile = new ZipFile(file); - final Enumeration entries = zipFile.entries(); - while (entries.hasMoreElements()) { - final ZipEntry zipEntry = entries.nextElement(); - final String name = zipEntry.getName(); - if (name.endsWith(".java")) { - final InputStream inputStream = zipFile.getInputStream(zipEntry); - final JavaSourceFileObject fileObject = new JavaSourceFileObject(name, inputStream); - collection.add(fileObject); - } - } - return collection; - } catch (IOException e) { - e.printStackTrace(); - } - return Collections.emptyList(); - } - - - /** - * 是否是jar 或 zip 文件 - * - * @param file 文件 - * @return 是否是jar 或 zip 文件 - */ - private boolean isJarOrZipFile(final File file) { - final String fileName = file.getName(); - return fileName.endsWith(".jar") || fileName.endsWith(".zip"); - } - - /** - * 是否是.java文件 - * - * @param file 文件 - * @return 是否是.java文件 - */ - private boolean isJavaFile(final File file) { - final String fileName = file.getName(); - return fileName.endsWith(".java"); - } - } diff --git a/hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceFileObject.java b/hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceFileObject.java index e8cae9da8..cf8a4e707 100644 --- a/hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceFileObject.java +++ b/hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceFileObject.java @@ -10,7 +10,11 @@ import java.net.URI; import java.nio.charset.Charset; /** - * Java 源码文件对象 + * Java 源码文件对象,支持:
+ *
    + *
  1. 源文件
  2. + *
  3. 代码内容
  4. + *
* * @author lzpeng * @since 5.5.2 @@ -34,23 +38,22 @@ class JavaSourceFileObject extends SimpleJavaFileObject { /** * 构造 * - * @param name 需要编译的文件名 - * @param inputStream 输入流 + * @param className 需要编译的类名 + * @param code 需要编译的类源码 */ - protected JavaSourceFileObject(String name, InputStream inputStream) { - this(URI.create("string:///" + name)); - this.inputStream = inputStream; + protected JavaSourceFileObject(String className, String code, Charset charset) { + this(className, IoUtil.toStream(code, charset)); } /** * 构造 * - * @param className 需要编译的类名 - * @param code 需要编译的类源码 + * @param name 需要编译的文件名 + * @param inputStream 输入流 */ - protected JavaSourceFileObject(String className, String code, Charset charset) { - this(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension)); - this.inputStream = IoUtil.toStream(code, charset); + protected JavaSourceFileObject(String name, InputStream inputStream) { + this(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension)); + this.inputStream = inputStream; } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java index 6583e98d2..6780b93d4 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java @@ -1,6 +1,7 @@ package cn.hutool.core.io; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.ListUtil; import cn.hutool.core.io.file.FileCopier; import cn.hutool.core.io.file.FileMode; import cn.hutool.core.io.file.FileNameUtil; @@ -50,6 +51,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.function.Consumer; import java.util.jar.JarFile; import java.util.zip.CRC32; import java.util.zip.Checksum; @@ -64,11 +66,11 @@ public class FileUtil extends PathUtil { /** * Class文件扩展名 */ - public static final String CLASS_EXT = ".class"; + public static final String CLASS_EXT = FileNameUtil.EXT_CLASS; /** * Jar文件扩展名 */ - public static final String JAR_FILE_EXT = ".jar"; + public static final String JAR_FILE_EXT = FileNameUtil.EXT_JAR; /** * 在Jar中的路径jar的扩展名形式 */ @@ -171,25 +173,33 @@ public class FileUtil extends PathUtil { * @return 文件列表 */ public static List loopFiles(File file, FileFilter fileFilter) { - final List fileList = new ArrayList<>(); if (null == file || false == file.exists()) { - return fileList; + return ListUtil.empty(); } + final List fileList = new ArrayList<>(); + walkFiles(file, fileList::add); + return fileList; + } + + /** + * 递归遍历目录并处理目录下的文件 + * + * @param file 文件或目录,文件直接处理 + * @param consumer 文件处理器,只会处理文件 + * @since 5.5.2 + */ + public static void walkFiles(File file, Consumer consumer) { if (file.isDirectory()) { final File[] subFiles = file.listFiles(); if (ArrayUtil.isNotEmpty(subFiles)) { for (File tmp : subFiles) { - fileList.addAll(loopFiles(tmp, fileFilter)); + walkFiles(tmp, consumer); } } } else { - if (null == fileFilter || fileFilter.accept(file)) { - fileList.add(file); - } + consumer.accept(file); } - - return fileList; } /** @@ -968,7 +978,7 @@ public class FileUtil extends PathUtil { * 移动文件或者目录 * * @param src 源文件或者目录 - * @param target 目标文件或者目录 + * @param target 目标文件或者目录 * @param isOverride 是否覆盖目标,只有目标为文件才覆盖 * @throws IORuntimeException IO异常 * @see PathUtil#move(Path, Path, boolean) @@ -2900,6 +2910,7 @@ public class FileUtil extends PathUtil { /** * 将流的内容写入文件
+ * 此方法会自动关闭输入流 * * @param dest 目标文件 * @param in 输入流 @@ -2912,6 +2923,7 @@ public class FileUtil extends PathUtil { /** * 将流的内容写入文件
+ * 此方法会自动关闭输入流 * * @param in 输入流 * @param fullFilePath 文件绝对路径 @@ -2923,7 +2935,7 @@ public class FileUtil extends PathUtil { } /** - * 将文件写入流中 + * 将文件写入流中,此方法不会概念比输出流 * * @param file 文件 * @param out 流 diff --git a/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java index 134f377fa..82c941f72 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java @@ -500,13 +500,25 @@ public class IoUtil { } /** - * 从Reader中读取String,读取完毕后并不关闭Reader + * 从Reader中读取String,读取完毕后关闭Reader * * @param reader Reader * @return String * @throws IORuntimeException IO异常 */ public static String read(Reader reader) throws IORuntimeException { + return read(reader, true); + } + + /** + * 从{@link Reader}中读取String + * + * @param reader {@link Reader} + * @param isClose 是否关闭{@link Reader} + * @return String + * @throws IORuntimeException IO异常 + */ + public static String read(Reader reader, boolean isClose) throws IORuntimeException { final StringBuilder builder = StrUtil.builder(); final CharBuffer buffer = CharBuffer.allocate(DEFAULT_BUFFER_SIZE); try { @@ -515,6 +527,10 @@ public class IoUtil { } } catch (IOException e) { throw new IORuntimeException(e); + } finally{ + if(isClose){ + IoUtil.close(reader); + } } return builder.toString(); } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/FileNameUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/file/FileNameUtil.java index 406c066b9..cc874827d 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/file/FileNameUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/file/FileNameUtil.java @@ -15,6 +15,19 @@ import java.util.regex.Pattern; */ public class FileNameUtil { + /** + * .java文件扩展名 + */ + public static final String EXT_JAVA = ".java"; + /** + * .class文件扩展名 + */ + public static final String EXT_CLASS = ".class"; + /** + * .jar文件扩展名 + */ + public static final String EXT_JAR = ".jar"; + /** * 类Unix路径分隔符 */ @@ -232,5 +245,17 @@ public class FileNameUtil { public static boolean containsInvalid(String fileName) { return (false == StrUtil.isBlank(fileName)) && ReUtil.contains(FILE_NAME_INVALID_PATTERN_WIN, fileName); } + + /** + * 根据文件名检查文件类型,忽略大小写 + * + * @param fileName 文件名,例如hutool.png + * @param extNames 被检查的扩展名数组,同一文件类型可能有多种扩展名,扩展名不带“.” + * @return 是否是指定扩展名的类型 + * @since 5.5.2 + */ + public static boolean isType(String fileName, String... extNames) { + return StrUtil.equalsAnyIgnoreCase(extName(fileName), extNames); + } // -------------------------------------------------------------------------------------------- name end } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/FileReader.java b/hutool-core/src/main/java/cn/hutool/core/io/file/FileReader.java index 93808ce89..9c481787c 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/file/FileReader.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/file/FileReader.java @@ -31,7 +31,7 @@ public class FileReader extends FileWrapper { * 创建 FileReader * @param file 文件 * @param charset 编码,使用 {@link CharsetUtil} - * @return {@link FileReader} + * @return FileReader */ public static FileReader create(File file, Charset charset){ return new FileReader(file, charset); @@ -40,7 +40,7 @@ public class FileReader extends FileWrapper { /** * 创建 FileReader, 编码:{@link FileWrapper#DEFAULT_CHARSET} * @param file 文件 - * @return {@link FileReader} + * @return FileReader */ public static FileReader create(File file){ return new FileReader(file); @@ -244,19 +244,36 @@ public class FileReader extends FileWrapper { throw new IORuntimeException(e); } } - + /** - * 将文件写入流中 - * + * 将文件写入流中,此方法不会关闭比输出流 + * * @param out 流 * @return 写出的流byte数 * @throws IORuntimeException IO异常 */ public long writeToStream(OutputStream out) throws IORuntimeException { + return writeToStream(out, false); + } + + /** + * 将文件写入流中 + * + * @param out 流 + * @param isCloseOut 是否关闭输出流 + * @return 写出的流byte数 + * @throws IORuntimeException IO异常 + * @since 5.5.2 + */ + public long writeToStream(OutputStream out, boolean isCloseOut) throws IORuntimeException { try (FileInputStream in = new FileInputStream(this.file)){ return IoUtil.copy(in, out); }catch (IOException e) { throw new IORuntimeException(e); + } finally{ + if(isCloseOut){ + IoUtil.close(out); + } } } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/FileWriter.java b/hutool-core/src/main/java/cn/hutool/core/io/file/FileWriter.java index 12438f713..a6aeb40fe 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/file/FileWriter.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/file/FileWriter.java @@ -1,5 +1,12 @@ package cn.hutool.core.io.file; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; + import java.io.BufferedOutputStream; import java.io.BufferedWriter; import java.io.File; @@ -13,13 +20,6 @@ import java.util.Collection; import java.util.Map; import java.util.Map.Entry; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.StrUtil; - /** * 文件写入器 * @author Looly @@ -32,7 +32,7 @@ public class FileWriter extends FileWrapper{ * 创建 FileWriter * @param file 文件 * @param charset 编码,使用 {@link CharsetUtil} - * @return {@link FileWriter} + * @return FileWriter */ public static FileWriter create(File file, Charset charset){ return new FileWriter(file, charset); @@ -41,7 +41,7 @@ public class FileWriter extends FileWrapper{ /** * 创建 FileWriter, 编码:{@link FileWrapper#DEFAULT_CHARSET} * @param file 文件 - * @return {@link FileWriter} + * @return FileWriter */ public static FileWriter create(File file){ return new FileWriter(file); @@ -302,13 +302,26 @@ public class FileWriter extends FileWrapper{ /** * 将流的内容写入文件
- * 此方法不会关闭输入流 - * + * 此方法会自动关闭输入流 + * * @param in 输入流,不关闭 * @return dest * @throws IORuntimeException IO异常 */ public File writeFromStream(InputStream in) throws IORuntimeException { + return writeFromStream(in, true); + } + + /** + * 将流的内容写入文件 + * + * @param in 输入流,不关闭 + * @param isCloseIn 是否关闭输入流 + * @return dest + * @throws IORuntimeException IO异常 + * @since 5.5.2 + */ + public File writeFromStream(InputStream in, boolean isCloseIn) throws IORuntimeException { FileOutputStream out = null; try { out = new FileOutputStream(FileUtil.touch(file)); @@ -317,6 +330,9 @@ public class FileWriter extends FileWrapper{ throw new IORuntimeException(e); } finally { IoUtil.close(out); + if(isCloseIn){ + IoUtil.close(in); + } } return file; } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/PathUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/file/PathUtil.java index 50ee10ca9..5b7baaa07 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/file/PathUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/file/PathUtil.java @@ -91,6 +91,18 @@ public class PathUtil { return fileList; } + /** + * 遍历指定path下的文件并做处理 + * + * @param start 起始路径,必须为目录 + * @param visitor {@link FileVisitor} 接口,用于自定义在访问文件时,访问目录前后等节点做的操作 + * @see Files#walkFileTree(Path, java.util.Set, int, FileVisitor) + * @since 5.5.2 + */ + public static void walkFiles(Path start, FileVisitor visitor) { + walkFiles(start, -1, visitor); + } + /** * 遍历指定path下的文件并做处理 * diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/BytesResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/BytesResource.java index f3607ba4b..457681d59 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/BytesResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/BytesResource.java @@ -3,11 +3,9 @@ package cn.hutool.core.io.resource; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.util.StrUtil; -import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.Serializable; -import java.io.StringReader; import java.net.URL; import java.nio.charset.Charset; @@ -59,11 +57,6 @@ public class BytesResource implements Resource, Serializable { return new ByteArrayInputStream(this.bytes); } - @Override - public BufferedReader getReader(Charset charset) { - return new BufferedReader(new StringReader(readStr(charset))); - } - @Override public String readStr(Charset charset) throws IORuntimeException { return StrUtil.str(this.bytes, charset); diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/CharSequenceResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/CharSequenceResource.java new file mode 100644 index 000000000..1b4fcfd59 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/CharSequenceResource.java @@ -0,0 +1,90 @@ +package cn.hutool.core.io.resource; + +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.CharsetUtil; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.Serializable; +import java.io.StringReader; +import java.net.URL; +import java.nio.charset.Charset; + +/** + * {@link CharSequence}资源,字符串做为资源 + * + * @author looly + * @since 5.5.2 + */ +public class CharSequenceResource implements Resource, Serializable { + private static final long serialVersionUID = 1L; + + private final CharSequence data; + private final CharSequence name; + private final Charset charset; + + /** + * 构造,使用UTF8编码 + * + * @param data 资源数据 + */ + public CharSequenceResource(CharSequence data) { + this(data, null); + } + + /** + * 构造,使用UTF8编码 + * + * @param data 资源数据 + * @param name 资源名称 + */ + public CharSequenceResource(CharSequence data, String name) { + this(data, name, CharsetUtil.CHARSET_UTF_8); + } + + /** + * 构造 + * + * @param data 资源数据 + * @param name 资源名称 + * @param charset 编码 + */ + public CharSequenceResource(CharSequence data, CharSequence name, Charset charset) { + this.data = data; + this.name = name; + this.charset = charset; + } + + @Override + public String getName() { + return this.name.toString(); + } + + @Override + public URL getUrl() { + return null; + } + + @Override + public InputStream getStream() { + return new ByteArrayInputStream(readBytes()); + } + + @Override + public BufferedReader getReader(Charset charset) { + return IoUtil.getReader(new StringReader(this.data.toString())); + } + + @Override + public String readStr(Charset charset) throws IORuntimeException { + return this.data.toString(); + } + + @Override + public byte[] readBytes() throws IORuntimeException { + return this.data.toString().getBytes(this.charset); + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/FileObjectResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileObjectResource.java new file mode 100644 index 000000000..0e435c06b --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileObjectResource.java @@ -0,0 +1,73 @@ +package cn.hutool.core.io.resource; + +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; + +import javax.tools.FileObject; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.charset.Charset; + +/** + * {@link FileObject} 资源包装 + * + * @author looly + * @since 5.5.2 + */ +public class FileObjectResource implements Resource { + + private final FileObject fileObject; + + /** + * 构造 + * + * @param fileObject {@link FileObject} + */ + public FileObjectResource(FileObject fileObject) { + this.fileObject = fileObject; + } + + /** + * 获取原始的{@link FileObject} + * + * @return {@link FileObject} + */ + public FileObject getFileObject() { + return this.fileObject; + } + + @Override + public String getName() { + return this.fileObject.getName(); + } + + @Override + public URL getUrl() { + try { + return this.fileObject.toUri().toURL(); + } catch (MalformedURLException e) { + return null; + } + } + + @Override + public InputStream getStream() { + try { + return this.fileObject.openInputStream(); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + + @Override + public BufferedReader getReader(Charset charset) { + try { + return IoUtil.getReader(this.fileObject.openReader(false)); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/InputStreamResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/InputStreamResource.java index e861329f2..0a4e1bf81 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/InputStreamResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/InputStreamResource.java @@ -1,13 +1,8 @@ package cn.hutool.core.io.resource; -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.io.IoUtil; - -import java.io.BufferedReader; import java.io.InputStream; import java.io.Serializable; import java.net.URL; -import java.nio.charset.Charset; /** * 基于{@link InputStream}的资源获取器
@@ -56,32 +51,4 @@ public class InputStreamResource implements Resource, Serializable { public InputStream getStream() { return this.in; } - - @Override - public BufferedReader getReader(Charset charset) { - return IoUtil.getReader(this.in, charset); - } - - @Override - public String readStr(Charset charset) throws IORuntimeException { - BufferedReader reader = null; - try { - reader = getReader(charset); - return IoUtil.read(reader); - } finally { - IoUtil.close(reader); - } - } - - @Override - public byte[] readBytes() throws IORuntimeException { - InputStream in = null; - try { - in = getStream(); - return IoUtil.readBytes(in); - } finally { - IoUtil.close(in); - } - } - } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/Resource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/Resource.java index 98989a29e..a174c9dce 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/Resource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/Resource.java @@ -58,7 +58,9 @@ public interface Resource { * @param charset 编码 * @return {@link BufferedReader} */ - BufferedReader getReader(Charset charset); + default BufferedReader getReader(Charset charset){ + return IoUtil.getReader(getStream(), charset); + } /** * 读取资源内容,读取完毕后会关闭流
@@ -68,7 +70,9 @@ public interface Resource { * @return 读取资源内容 * @throws IORuntimeException 包装{@link IOException} */ - String readStr(Charset charset) throws IORuntimeException; + default String readStr(Charset charset) throws IORuntimeException{ + return IoUtil.read(getReader(charset)); + } /** * 读取资源内容,读取完毕后会关闭流
@@ -88,5 +92,7 @@ public interface Resource { * @return 读取资源内容 * @throws IORuntimeException 包装IOException */ - byte[] readBytes() throws IORuntimeException; + default byte[] readBytes() throws IORuntimeException{ + return IoUtil.readBytes(getStream()); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/StringResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/StringResource.java index 0f45a4972..16158d1a0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/StringResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/StringResource.java @@ -1,15 +1,7 @@ package cn.hutool.core.io.resource; -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.CharsetUtil; -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.io.Serializable; -import java.io.StringReader; -import java.net.URL; import java.nio.charset.Charset; /** @@ -17,13 +9,11 @@ import java.nio.charset.Charset; * * @author looly * @since 4.1.0 + * @see CharSequenceResource */ -public class StringResource implements Resource, Serializable { +public class StringResource extends CharSequenceResource { private static final long serialVersionUID = 1L; - private final String data; - private final String name; - private final Charset charset; /** * 构造,使用UTF8编码 @@ -31,7 +21,7 @@ public class StringResource implements Resource, Serializable { * @param data 资源数据 */ public StringResource(String data) { - this(data, null); + super(data, null); } /** @@ -41,7 +31,7 @@ public class StringResource implements Resource, Serializable { * @param name 资源名称 */ public StringResource(String data, String name) { - this(data, name, CharsetUtil.CHARSET_UTF_8); + super(data, name, CharsetUtil.CHARSET_UTF_8); } /** @@ -52,39 +42,6 @@ public class StringResource implements Resource, Serializable { * @param charset 编码 */ public StringResource(String data, String name, Charset charset) { - this.data = data; - this.name = name; - this.charset = charset; + super(data, name, charset); } - - @Override - public String getName() { - return this.name; - } - - @Override - public URL getUrl() { - return null; - } - - @Override - public InputStream getStream() { - return new ByteArrayInputStream(readBytes()); - } - - @Override - public BufferedReader getReader(Charset charset) { - return IoUtil.getReader(new StringReader(this.data)); - } - - @Override - public String readStr(Charset charset) throws IORuntimeException { - return this.data; - } - - @Override - public byte[] readBytes() throws IORuntimeException { - return this.data.getBytes(this.charset); - } - } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java index 995a5923f..96d0293f5 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java @@ -1,17 +1,13 @@ package cn.hutool.core.io.resource; import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.URLUtil; -import java.io.BufferedReader; import java.io.File; import java.io.InputStream; import java.io.Serializable; import java.net.URL; -import java.nio.charset.Charset; /** * URL资源访问类 @@ -72,40 +68,6 @@ public class UrlResource implements Resource, Serializable{ return URLUtil.getStream(url); } - /** - * 获得Reader - * @param charset 编码 - * @return {@link BufferedReader} - * @since 3.0.1 - */ - @Override - public BufferedReader getReader(Charset charset){ - return URLUtil.getReader(this.url, charset); - } - - //------------------------------------------------------------------------------- read - @Override - public String readStr(Charset charset) throws IORuntimeException{ - BufferedReader reader = null; - try { - reader = getReader(charset); - return IoUtil.read(reader); - } finally { - IoUtil.close(reader); - } - } - - @Override - public byte[] readBytes() throws IORuntimeException{ - InputStream in = null; - try { - in = getStream(); - return IoUtil.readBytes(in); - } finally { - IoUtil.close(in); - } - } - /** * 获得File * @return {@link File} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/JarClassLoader.java b/hutool-core/src/main/java/cn/hutool/core/lang/JarClassLoader.java index 0ae55f932..58d5cb26d 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/JarClassLoader.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/JarClassLoader.java @@ -59,7 +59,7 @@ public class JarClassLoader extends URLClassLoader { method.setAccessible(true); final List jars = loopJar(jarFile); for (File jar : jars) { - ReflectUtil.invoke(loader, method, new Object[]{jar.toURI().toURL()}); + ReflectUtil.invoke(loader, method, jar.toURI().toURL()); } } } catch (IOException e) { diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/ResourceClassLoader.java b/hutool-core/src/main/java/cn/hutool/core/lang/ResourceClassLoader.java new file mode 100644 index 000000000..76135cf56 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/ResourceClassLoader.java @@ -0,0 +1,52 @@ +package cn.hutool.core.lang; + +import cn.hutool.core.io.resource.Resource; +import cn.hutool.core.util.ClassLoaderUtil; +import cn.hutool.core.util.ObjectUtil; + +import java.security.SecureClassLoader; +import java.util.HashMap; +import java.util.Map; + +/** + * 资源类加载器,可以加载任意类型的资源类 + * + * @param {@link Resource}接口实现类 + * @author looly + * @since 5.5.2 + */ +public class ResourceClassLoader extends SecureClassLoader { + + private final Map resourceMap; + + /** + * 构造 + * + * @param parentClassLoader 父类加载器,null表示默认当前上下文加载器 + * @param resourceMap 资源map + */ + public ResourceClassLoader(ClassLoader parentClassLoader, Map resourceMap) { + super(ObjectUtil.defaultIfNull(parentClassLoader, ClassLoaderUtil.getClassLoader())); + this.resourceMap = ObjectUtil.defaultIfNull(resourceMap, new HashMap<>()); + } + + /** + * 增加需要加载的类资源 + * @param resource 资源,可以是文件、流或者字符串 + * @return this + */ + public ResourceClassLoader addResource(T resource){ + this.resourceMap.put(resource.getName(), resource); + return this; + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + final Resource resource = resourceMap.get(name); + if (null != resource) { + final byte[] bytes = resource.readBytes(); + return defineClass(name, bytes, 0, bytes.length); + } + return super.findClass(name); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java index ccafd1506..0e63d35e1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java @@ -5,6 +5,7 @@ import cn.hutool.core.io.FastByteArrayOutputStream; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.resource.Resource; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; @@ -19,6 +20,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.List; +import java.util.function.Consumer; import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; import java.util.zip.GZIPInputStream; @@ -44,6 +46,37 @@ public class ZipUtil { */ private static final Charset DEFAULT_CHARSET = CharsetUtil.defaultCharset(); + /** + * 将Zip文件转换为{@link ZipFile} + * + * @param file zip文件 + * @param charset 解析zip文件的编码,null表示{@link CharsetUtil#CHARSET_UTF_8} + * @return {@link ZipFile} + */ + public static ZipFile toZipFile(File file, Charset charset) { + try { + return new ZipFile(file, ObjectUtil.defaultIfNull(charset, CharsetUtil.CHARSET_UTF_8)); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + + /** + * 获取指定{@link ZipEntry}的流,用于读取这个entry的内容 + * + * @param zipFile {@link ZipFile} + * @param zipEntry {@link ZipEntry} + * @return 流 + * @since 5.5.2 + */ + public static InputStream getStream(ZipFile zipFile, ZipEntry zipEntry) { + try { + return zipFile.getInputStream(zipEntry); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + /** * 打包到当前目录,使用默认编码UTF-8 * @@ -190,7 +223,7 @@ public class ZipUtil { /** * 对文件或文件目录进行压缩 * - * @param out 生成的Zip到的目标流,包括文件名。注意:zipPath不能是srcPath路径下的子文件夹 + * @param out 生成的Zip到的目标流,包括文件名。注意:zipPath不能是srcPath路径下的子文件夹 * @param charset 编码 * @param withSrcDir 是否包含被打包目录,只针对压缩目录有效。若为false,则只压缩目录下的文件或目录,为true则将本目录也压缩 * @param filter 文件过滤器,通过实现此接口,自定义要过滤的文件(过滤掉哪些文件或文件夹不加入压缩) @@ -205,16 +238,16 @@ public class ZipUtil { /** * 对文件或文件目录进行压缩 * - * @param zipOutputStream 生成的Zip到的目标流,不关闭此流 - * @param withSrcDir 是否包含被打包目录,只针对压缩目录有效。若为false,则只压缩目录下的文件或目录,为true则将本目录也压缩 - * @param filter 文件过滤器,通过实现此接口,自定义要过滤的文件(过滤掉哪些文件或文件夹不加入压缩) - * @param srcFiles 要压缩的源文件或目录。如果压缩一个文件,则为该文件的全路径;如果压缩一个目录,则为该目录的顶层目录路径 + * @param zipOutputStream 生成的Zip到的目标流,不关闭此流 + * @param withSrcDir 是否包含被打包目录,只针对压缩目录有效。若为false,则只压缩目录下的文件或目录,为true则将本目录也压缩 + * @param filter 文件过滤器,通过实现此接口,自定义要过滤的文件(过滤掉哪些文件或文件夹不加入压缩) + * @param srcFiles 要压缩的源文件或目录。如果压缩一个文件,则为该文件的全路径;如果压缩一个目录,则为该目录的顶层目录路径 * @throws IORuntimeException IO异常 * @since 5.1.1 */ public static void zip(ZipOutputStream zipOutputStream, boolean withSrcDir, FileFilter filter, File... srcFiles) throws IORuntimeException { String srcRootDir; - try{ + try { for (File srcFile : srcFiles) { if (null == srcFile) { continue; @@ -300,7 +333,7 @@ public class ZipUtil { * * @param zipFile 生成的Zip文件,包括文件名。注意:zipPath不能是srcPath路径下的子文件夹 * @param paths 流数据在压缩文件中的路径或文件名 - * @param ins 要压缩的源 + * @param ins 要压缩的源,添加完成后自动关闭流 * @return 压缩文件 * @throws UtilException IO异常 * @since 3.0.9 @@ -333,7 +366,31 @@ public class ZipUtil { try { out = getZipOutputStream(zipFile, charset); for (int i = 0; i < paths.length; i++) { - addFile(ins[i], paths[i], out); + add(ins[i], paths[i], out); + } + } finally { + IoUtil.close(out); + } + return zipFile; + } + + /** + * 对流中的数据加入到压缩文件
+ * 路径列表和流列表长度必须一致 + * + * @param zipFile 生成的Zip文件,包括文件名。注意:zipPath不能是srcPath路径下的子文件夹 + * @param charset 编码 + * @param resources 需要压缩的资源,资源的路径为{@link Resource#getName()} + * @return 压缩文件 + * @throws UtilException IO异常 + * @since 5.5.2 + */ + public static File zip(File zipFile, Charset charset, Resource... resources) throws UtilException { + ZipOutputStream out = null; + try { + out = getZipOutputStream(zipFile, charset); + for (Resource resource : resources) { + add(resource.getStream(), resource.getName(), out); } } finally { IoUtil.close(out); @@ -437,17 +494,10 @@ public class ZipUtil { * @param outFile 解压到的目录 * @param charset 编码 * @return 解压的目录 - * @throws UtilException IO异常 * @since 3.2.2 */ - public static File unzip(File zipFile, File outFile, Charset charset) throws UtilException { - ZipFile zip; - try { - zip = new ZipFile(zipFile, charset); - } catch (IOException e) { - throw new IORuntimeException(e); - } - return unzip(zip, outFile); + public static File unzip(File zipFile, File outFile, Charset charset) { + return unzip(toZipFile(zipFile, charset), outFile); } /** @@ -460,66 +510,71 @@ public class ZipUtil { * @since 4.5.8 */ public static File unzip(ZipFile zipFile, File outFile) throws IORuntimeException { - if(outFile.exists() && outFile.isFile()){ + if (outFile.exists() && outFile.isFile()) { throw new UtilException("Target path [{}] exist!", outFile.getAbsolutePath()); } - try { - final Enumeration em = zipFile.entries(); - ZipEntry zipEntry; - File outItemFile; - while (em.hasMoreElements()) { - zipEntry = em.nextElement(); - // FileUtil.file会检查slip漏洞,漏洞说明见http://blog.nsfocus.net/zip-slip-2/ - outItemFile = FileUtil.file(outFile, zipEntry.getName()); - if (zipEntry.isDirectory()) { - // 创建对应目录 - //noinspection ResultOfMethodCallIgnored - outItemFile.mkdirs(); - } else { - // 写出文件 - write(zipFile, zipEntry, outItemFile); - } + read(zipFile, (zipEntry) -> { + // FileUtil.file会检查slip漏洞,漏洞说明见http://blog.nsfocus.net/zip-slip-2/ + File outItemFile = FileUtil.file(outFile, zipEntry.getName()); + if (zipEntry.isDirectory()) { + // 创建对应目录 + //noinspection ResultOfMethodCallIgnored + outItemFile.mkdirs(); + } else { + // 写出文件 + write(zipFile, zipEntry, outItemFile); } - } finally { - IoUtil.close(zipFile); - } + }); + return outFile; } /** * 获取压缩包中的指定文件流 + * * @param zipFile 压缩文件 - * @param path 需要提取文件的文件名或路径 + * @param path 需要提取文件的文件名或路径 * @return 压缩文件流,如果未找到返回{@code null} * @since 5.5.2 */ - public static InputStream get(File zipFile, Charset charset, String path){ - try { - return get(new ZipFile(zipFile, charset), path); - } catch (IOException e) { - throw new IORuntimeException(e); - } + public static InputStream get(File zipFile, Charset charset, String path) { + return get(toZipFile(zipFile, charset), path); } /** * 获取压缩包中的指定文件流 + * * @param zipFile 压缩文件 - * @param path 需要提取文件的文件名或路径 + * @param path 需要提取文件的文件名或路径 * @return 压缩文件流,如果未找到返回{@code null} * @since 5.5.2 */ - public static InputStream get(ZipFile zipFile, String path){ + public static InputStream get(ZipFile zipFile, String path) { final ZipEntry entry = zipFile.getEntry(path); - if(null != entry){ - try { - return zipFile.getInputStream(entry); - } catch (IOException e) { - throw new IORuntimeException(e); - } + if (null != entry) { + return getStream(zipFile, entry); } return null; } + /** + * 读取并处理Zip文件中的每一个{@link ZipEntry} + * + * @param zipFile Zip文件 + * @param consumer {@link ZipEntry}处理器 + * @since 5.5.2 + */ + public static void read(ZipFile zipFile, Consumer consumer) { + try { + final Enumeration em = zipFile.entries(); + while (em.hasMoreElements()) { + consumer.accept(em.nextElement()); + } + } finally { + IoUtil.close(zipFile); + } + } + /** * 解压
* ZIP条目不使用高速缓冲。 @@ -549,27 +604,40 @@ public class ZipUtil { * @since 4.5.8 */ public static File unzip(ZipInputStream zipStream, File outFile) throws UtilException { + read(zipStream, (zipEntry) -> { + // FileUtil.file会检查slip漏洞,漏洞说明见http://blog.nsfocus.net/zip-slip-2/ + File outItemFile = FileUtil.file(outFile, zipEntry.getName()); + if (zipEntry.isDirectory()) { + // 目录 + //noinspection ResultOfMethodCallIgnored + outItemFile.mkdirs(); + } else { + // 文件 + FileUtil.writeFromStream(zipStream, outItemFile); + } + }); + return outFile; + } + + /** + * 读取并处理Zip流中的每一个{@link ZipEntry} + * + * @param zipStream zip文件流,包含编码信息 + * @param consumer {@link ZipEntry}处理器 + * @since 5.5.2 + */ + public static void read(ZipInputStream zipStream, Consumer consumer) { try { ZipEntry zipEntry; File outItemFile; while (null != (zipEntry = zipStream.getNextEntry())) { - // FileUtil.file会检查slip漏洞,漏洞说明见http://blog.nsfocus.net/zip-slip-2/ - outItemFile = FileUtil.file(outFile, zipEntry.getName()); - if (zipEntry.isDirectory()) { - // 目录 - //noinspection ResultOfMethodCallIgnored - outItemFile.mkdirs(); - } else { - // 文件 - FileUtil.writeFromStream(zipStream, outItemFile); - } + consumer.accept(zipEntry); } } catch (IOException e) { throw new UtilException(e); } finally { IoUtil.close(zipStream); } - return outFile; } /** @@ -622,17 +690,15 @@ public class ZipUtil { public static byte[] unzipFileBytes(File zipFile, Charset charset, String name) { ZipFile zipFileObj = null; try { - zipFileObj = new ZipFile(zipFile, charset); + zipFileObj = toZipFile(zipFile, charset); final Enumeration em = (Enumeration) zipFileObj.entries(); ZipEntry zipEntry; while (em.hasMoreElements()) { zipEntry = em.nextElement(); if ((false == zipEntry.isDirectory()) && name.equals(zipEntry.getName())) { - return IoUtil.readBytes(zipFileObj.getInputStream(zipEntry)); + return IoUtil.readBytes(getStream(zipFileObj, zipEntry)); } } - } catch (IOException e) { - throw new UtilException(e); } finally { IoUtil.close(zipFileObj); } @@ -946,8 +1012,8 @@ public class ZipUtil { * @return {@link ZipOutputStream} */ private static ZipOutputStream getZipOutputStream(OutputStream out, Charset charset) { - if(out instanceof ZipOutputStream) { - return (ZipOutputStream)out; + if (out instanceof ZipOutputStream) { + return (ZipOutputStream) out; } return new ZipOutputStream(out, ObjectUtil.defaultIfNull(charset, DEFAULT_CHARSET)); } @@ -980,7 +1046,7 @@ public class ZipUtil { zip(childFile, srcRootDir, out, filter); } } else {// 如果是文件或其它符号,则直接压缩该文件 - addFile(file, subPath, out); + add(file, subPath, out); } } @@ -993,19 +1059,19 @@ public class ZipUtil { * @throws UtilException IO异常 * @since 4.0.5 */ - private static void addFile(File file, String path, ZipOutputStream out) throws UtilException { - addFile(FileUtil.getInputStream(file), path, out); + private static void add(File file, String path, ZipOutputStream out) throws UtilException { + add(FileUtil.getInputStream(file), path, out); } /** * 添加文件流到压缩包,添加后关闭流 * - * @param in 需要压缩的输入流 + * @param in 需要压缩的输入流,使用完后自动关闭 * @param path 压缩的路径 * @param out 压缩文件存储对象 * @throws UtilException IO异常 */ - private static void addFile(InputStream in, String path, ZipOutputStream out) throws UtilException { + private static void add(InputStream in, String path, ZipOutputStream out) throws UtilException { if (null == in) { return; } @@ -1058,7 +1124,7 @@ public class ZipUtil { } // 压缩文件不能位于被压缩的目录内 - if(srcFile.isDirectory() && FileUtil.isSub(srcFile, zipFile.getParentFile())){ + if (srcFile.isDirectory() && FileUtil.isSub(srcFile, zipFile.getParentFile())) { throw new UtilException("Zip file path [{}] must not be the child directory of [{}] !", zipFile.getPath(), srcFile.getPath()); } } @@ -1086,15 +1152,7 @@ public class ZipUtil { * @throws IORuntimeException IO异常 */ private static void write(ZipFile zipFile, ZipEntry zipEntry, File outItemFile) throws IORuntimeException { - InputStream in = null; - try { - in = zipFile.getInputStream(zipEntry); - FileUtil.writeFromStream(in, outItemFile); - } catch (IOException e) { - throw new IORuntimeException(e); - } finally { - IoUtil.close(in); - } + FileUtil.writeFromStream(getStream(zipFile, zipEntry), outItemFile); } /** diff --git a/hutool-core/src/test/java/cn/hutool/core/compiler/JavaSourceCompilerTest.java b/hutool-core/src/test/java/cn/hutool/core/compiler/JavaSourceCompilerTest.java index 3e24514d7..5131e7bdb 100644 --- a/hutool-core/src/test/java/cn/hutool/core/compiler/JavaSourceCompilerTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/compiler/JavaSourceCompilerTest.java @@ -21,6 +21,7 @@ public class JavaSourceCompilerTest { */ @Test public void testCompile() throws ClassNotFoundException { + // 依赖A,编译B和C final File libFile = ZipUtil.zip(FileUtil.file("lib.jar"), new String[]{"a/A.class", "a/A$1.class", "a/A$InnerClass.class"}, new InputStream[]{ diff --git a/hutool-core/src/test/java/cn/hutool/core/io/resource/ResourceUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/resource/ResourceUtilTest.java index 36b2d72bc..eab36a616 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/resource/ResourceUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/resource/ResourceUtilTest.java @@ -1,5 +1,6 @@ package cn.hutool.core.io.resource; +import cn.hutool.core.io.IoUtil; import org.junit.Assert; import org.junit.Test; @@ -10,4 +11,12 @@ public class ResourceUtilTest { final String str = ResourceUtil.readUtf8Str("test.xml"); Assert.assertNotNull(str); } + + @Test + public void stringResourceTest(){ + final StringResource stringResource = new StringResource("testData", "test"); + Assert.assertEquals("test", stringResource.getName()); + Assert.assertArrayEquals("testData".getBytes(), stringResource.readBytes()); + Assert.assertArrayEquals("testData".getBytes(), IoUtil.readBytes(stringResource.getStream())); + } } From 2f7cd188957a6244189ccaf814bee2809dfd0f2d Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 29 Nov 2020 15:56:07 +0800 Subject: [PATCH 2/4] fix code --- .../hutool/core/compiler/JavaSourceCompiler.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceCompiler.java b/hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceCompiler.java index 076f7fc60..2f9275cdc 100644 --- a/hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceCompiler.java +++ b/hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceCompiler.java @@ -1,6 +1,7 @@ package cn.hutool.core.compiler; import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.CharsetUtil; @@ -159,13 +160,16 @@ public class JavaSourceCompiler { final DiagnosticCollector diagnosticCollector = new DiagnosticCollector<>(); final List javaFileObjectList = getJavaFileObject(); final CompilationTask task = CompilerUtil.getTask(javaFileManager, diagnosticCollector, options, javaFileObjectList); - if (task.call()) { - // 加载编译后的类 - return javaFileManager.getClassLoader(StandardLocation.CLASS_OUTPUT); - } else { - // 编译失败,收集错误信息 - throw new CompilerException(DiagnosticUtil.getMessages(diagnosticCollector)); + try{ + if (task.call()) { + // 加载编译后的类 + return javaFileManager.getClassLoader(StandardLocation.CLASS_OUTPUT); + } + } finally { + IoUtil.close(javaFileManager); } + //编译失败,收集错误信息 + throw new CompilerException(DiagnosticUtil.getMessages(diagnosticCollector)); } /** From cdfae52eb943b4a00401b84ab35ca0b5c5c82ee3 Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 30 Nov 2020 02:08:14 +0800 Subject: [PATCH 3/4] fix code --- CHANGELOG.md | 1 + .../cn/hutool/core/compiler/CompilerUtil.java | 25 ++- .../core/compiler/JavaClassFileManager.java | 2 +- .../core/compiler/JavaClassFileObject.java | 17 +-- .../core/compiler/JavaSourceCompiler.java | 143 +++++++++++------- .../core/compiler/JavaSourceFileObject.java | 14 +- .../main/java/cn/hutool/core/io/FileUtil.java | 6 +- .../hutool/core/io/resource/FileResource.java | 51 ++++++- .../cn/hutool/core/io/resource/Resource.java | 52 ++++--- .../java/cn/hutool/core/util/URLUtil.java | 12 ++ .../java/cn/hutool/core/util/ZipUtil.java | 60 ++++++-- .../core/io/resource/ResourceUtilTest.java | 14 ++ .../java/cn/hutool/setting/SettingLoader.java | 12 +- .../java/cn/hutool/setting/SettingUtil.java | 23 +-- .../java/cn/hutool/setting/dialect/Props.java | 27 ++-- .../cn/hutool/setting/dialect/PropsUtil.java | 32 ++-- 16 files changed, 325 insertions(+), 166 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78aa0974d..e6dca943a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ * 【core 】 增加compile包(pr#1243@Github) * 【core 】 增加ResourceClassLoader、CharSequenceResource、FileObjectResource * 【core 】 修改IoUtil.read(Reader)逻辑默认关闭Reader +* 【core 】 ZipUtil增加Zip方法(pr#222@Gitee) ### Bug修复 * 【cron 】 修复CronTimer可能死循环的问题(issue#1224@Github) diff --git a/hutool-core/src/main/java/cn/hutool/core/compiler/CompilerUtil.java b/hutool-core/src/main/java/cn/hutool/core/compiler/CompilerUtil.java index 707983a14..148252b03 100644 --- a/hutool-core/src/main/java/cn/hutool/core/compiler/CompilerUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/compiler/CompilerUtil.java @@ -4,6 +4,7 @@ import javax.tools.DiagnosticListener; import javax.tools.JavaCompiler; import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; /** @@ -13,6 +14,7 @@ import javax.tools.ToolProvider; * @since 5.5.2 */ public class CompilerUtil { + /** * java 编译器 */ @@ -29,21 +31,21 @@ public class CompilerUtil { } /** - * 获取{@link JavaFileManager} + * 获取{@link StandardJavaFileManager} * - * @return {@link JavaFileManager} + * @return {@link StandardJavaFileManager} */ - public static JavaFileManager getFileManager() { + public static StandardJavaFileManager getFileManager() { return SYSTEM_COMPILER.getStandardFileManager(null, null, null); } /** * 新建编译任务 * - * @param fileManager {@link JavaFileManager},用于管理已经编译好的文件 + * @param fileManager {@link JavaFileManager},用于管理已经编译好的文件 * @param diagnosticListener 诊断监听 - * @param options 选项,例如 -cpXXX等 - * @param compilationUnits 编译单元,即需要编译的对象 + * @param options 选项,例如 -cpXXX等 + * @param compilationUnits 编译单元,即需要编译的对象 * @return {@link JavaCompiler.CompilationTask} */ public static JavaCompiler.CompilationTask getTask( @@ -53,4 +55,15 @@ public class CompilerUtil { Iterable compilationUnits) { return SYSTEM_COMPILER.getTask(null, fileManager, diagnosticListener, options, null, compilationUnits); } + + /** + * 获取{@link JavaSourceCompiler} + * + * @param parent 父{@link ClassLoader} + * @return {@link JavaSourceCompiler} + * @see JavaSourceCompiler#create(ClassLoader) + */ + public static JavaSourceCompiler getCompiler(ClassLoader parent) { + return JavaSourceCompiler.create(parent); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/compiler/JavaClassFileManager.java b/hutool-core/src/main/java/cn/hutool/core/compiler/JavaClassFileManager.java index 9960a7320..1150f9e5c 100644 --- a/hutool-core/src/main/java/cn/hutool/core/compiler/JavaClassFileManager.java +++ b/hutool-core/src/main/java/cn/hutool/core/compiler/JavaClassFileManager.java @@ -70,7 +70,7 @@ class JavaClassFileManager extends ForwardingJavaFileManager { */ @Override public JavaFileObject getJavaFileForOutput(final Location location, final String className, final Kind kind, final FileObject sibling) { - final JavaFileObject javaFileObject = new JavaClassFileObject(className, kind); + final JavaFileObject javaFileObject = new JavaClassFileObject(className); this.classFileObjectMap.put(className, new FileObjectResource(javaFileObject)); return javaFileObject; } diff --git a/hutool-core/src/main/java/cn/hutool/core/compiler/JavaClassFileObject.java b/hutool-core/src/main/java/cn/hutool/core/compiler/JavaClassFileObject.java index d707a1c18..b1d6e8792 100644 --- a/hutool-core/src/main/java/cn/hutool/core/compiler/JavaClassFileObject.java +++ b/hutool-core/src/main/java/cn/hutool/core/compiler/JavaClassFileObject.java @@ -1,22 +1,22 @@ package cn.hutool.core.compiler; +import cn.hutool.core.util.CharUtil; +import cn.hutool.core.util.URLUtil; + import javax.tools.SimpleJavaFileObject; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.OutputStream; -import java.net.URI; /** - * Java 字节码文件对象 + * Java 字节码文件对象,用于在内存中暂存class字节码,从而可以在ClassLoader中动态加载。 * * @author lzpeng - * @see JavaClassFileManager#getClassLoader(javax.tools.JavaFileManager.Location - * @see JavaClassFileManager#getJavaFileForOutput(javax.tools.JavaFileManager.Location, java.lang.String, javax.tools.JavaFileObject.Kind, javax.tools.FileObject) * @since 5.5.2 */ -final class JavaClassFileObject extends SimpleJavaFileObject { +class JavaClassFileObject extends SimpleJavaFileObject { /** * 字节码输出流 @@ -26,12 +26,11 @@ final class JavaClassFileObject extends SimpleJavaFileObject { /** * 构造 * - * @param className 需要编译的类名 - * @param kind 需要编译的文件类型 + * @param className 编译后的class文件的类名 * @see JavaClassFileManager#getJavaFileForOutput(javax.tools.JavaFileManager.Location, java.lang.String, javax.tools.JavaFileObject.Kind, javax.tools.FileObject) */ - protected JavaClassFileObject(final String className, final Kind kind) { - super(URI.create("string:///" + className.replaceAll("\\.", "/") + kind.extension), kind); + protected JavaClassFileObject(String className) { + super(URLUtil.getStringURI(className.replace(CharUtil.DOT, CharUtil.SLASH) + Kind.CLASS.extension), Kind.CLASS); this.byteArrayOutputStream = new ByteArrayOutputStream(); } diff --git a/hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceCompiler.java b/hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceCompiler.java index 2f9275cdc..609bf0423 100644 --- a/hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceCompiler.java +++ b/hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceCompiler.java @@ -2,6 +2,9 @@ package cn.hutool.core.compiler; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.resource.FileResource; +import cn.hutool.core.io.resource.Resource; +import cn.hutool.core.io.resource.StringResource; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.CharsetUtil; @@ -11,7 +14,6 @@ import cn.hutool.core.util.URLUtil; import javax.tools.DiagnosticCollector; import javax.tools.JavaCompiler.CompilationTask; -import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; import javax.tools.StandardLocation; import java.io.File; @@ -21,49 +23,55 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** * Java 源码编译器 + *

通过此类可以动态编译java源码,并加载到ClassLoader,从而动态获取加载的类。

+ *

JavaSourceCompiler支持加载的源码类型包括:

+ *
    + *
  • 源码文件
  • + *
  • 源码文件源码字符串
  • + *
+ * + *

使用方法如下:

+ *
+ *     ClassLoader classLoader = JavaSourceCompiler.create(null)
+ *         .addSource(FileUtil.file("test-compile/b/B.java"))
+ *         .addSource("c.C", FileUtil.readUtf8String("test-compile/c/C.java"))
+ *         // 增加编译依赖的类库
+ *         .addLibrary(libFile)
+ *         .compile();
+ *     Class<?> clazz = classLoader.loadClass("c.C");
+ * 
* * @author lzpeng */ public class JavaSourceCompiler { /** - * 待编译的文件 可以是 .java文件 压缩文件 文件夹 递归搜索文件夹内的zip包和jar包 + * 待编译的资源,支持: + * + *
    + *
  • 源码字符串,使用{@link StringResource}
  • + *
  • 源码文件、源码jar包或源码zip包,亦或者文件夹,使用{@link FileResource}
  • + *
+ * 可以是 .java文件 压缩文件 文件夹 递归搜索文件夹内的zip包和jar包 */ - private final List sourceFileList = new ArrayList<>(); + private final List sourceList = new ArrayList<>(); /** * 编译时需要加入classpath中的文件 可以是 压缩文件 文件夹递归搜索文件夹内的zip包和jar包 */ private final List libraryFileList = new ArrayList<>(); - /** - * 源码映射 key: 类名 value: 类源码 - */ - private final Map sourceCodeMap = new LinkedHashMap<>(); - /** * 编译类时使用的父类加载器 */ private final ClassLoader parentClassLoader; - - /** - * 构造 - * - * @param parent 父类加载器,null则使用默认类加载器 - */ - private JavaSourceCompiler(ClassLoader parent) { - this.parentClassLoader = ObjectUtil.defaultIfNull(parent, ClassLoaderUtil.getClassLoader()); - } - - /** * 创建Java源码编译器 * @@ -74,16 +82,41 @@ public class JavaSourceCompiler { return new JavaSourceCompiler(parent); } + /** + * 构造 + * + * @param parent 父类加载器,null则使用默认类加载器 + */ + private JavaSourceCompiler(ClassLoader parent) { + this.parentClassLoader = ObjectUtil.defaultIfNull(parent, ClassLoaderUtil.getClassLoader()); + } /** - * 向编译器中加入待编译的文件 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包 + * 向编译器中加入待编译的资源
+ * 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包 + * + * @param resources 待编译的资源,支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包 + * @return Java源码编译器 + */ + public JavaSourceCompiler addSource(Resource... resources) { + if (ArrayUtil.isNotEmpty(resources)) { + this.sourceList.addAll(Arrays.asList(resources)); + } + return this; + } + + /** + * 向编译器中加入待编译的文件
+ * 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包 * * @param files 待编译的文件 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包 * @return Java源码编译器 */ - public JavaSourceCompiler addSource(final File... files) { + public JavaSourceCompiler addSource(File... files) { if (ArrayUtil.isNotEmpty(files)) { - this.sourceFileList.addAll(Arrays.asList(files)); + for (File file : files) { + this.sourceList.add(new FileResource(file)); + } } return this; } @@ -94,36 +127,36 @@ public class JavaSourceCompiler { * @param sourceCodeMap 源码Map key: 类名 value 源码 * @return Java源码编译器 */ - public JavaSourceCompiler addSource(final Map sourceCodeMap) { + public JavaSourceCompiler addSource(Map sourceCodeMap) { if (MapUtil.isNotEmpty(sourceCodeMap)) { - this.sourceCodeMap.putAll(sourceCodeMap); + sourceCodeMap.forEach(this::addSource); } return this; } /** - * 加入编译Java源码时所需要的jar包 - * - * @param files 编译Java源码时所需要的jar包 - * @return Java源码编译器 - */ - public JavaSourceCompiler addLibrary(final File... files) { - if (ArrayUtil.isNotEmpty(files)) { - this.libraryFileList.addAll(Arrays.asList(files)); - } - return this; - } - - /** - * 向编译器中加入待编译的源码Map + * 向编译器中加入待编译的源码 * * @param className 类名 * @param sourceCode 源码 * @return Java文件编译器 */ - public JavaSourceCompiler addSource(final String className, final String sourceCode) { + public JavaSourceCompiler addSource(String className, String sourceCode) { if (className != null && sourceCode != null) { - this.sourceCodeMap.put(className, sourceCode); + this.sourceList.add(new StringResource(sourceCode, className)); + } + return this; + } + + /** + * 加入编译Java源码时所需要的jar包,jar包中必须为字节码 + * + * @param files 编译Java源码时所需要的jar包 + * @return Java源码编译器 + */ + public JavaSourceCompiler addLibrary(File... files) { + if (ArrayUtil.isNotEmpty(files)) { + this.libraryFileList.addAll(Arrays.asList(files)); } return this; } @@ -138,15 +171,13 @@ public class JavaSourceCompiler { final List classPath = getClassPath(); final URL[] urLs = URLUtil.getURLs(classPath.toArray(new File[0])); final URLClassLoader ucl = URLClassLoader.newInstance(urLs, this.parentClassLoader); - if (sourceCodeMap.isEmpty() && sourceFileList.isEmpty()) { - // 没有需要编译的源码 + if (sourceList.isEmpty()) { + // 没有需要编译的源码文件返回加载zip或jar包的类加载器 return ucl; } - // 没有需要编译的源码文件返回加载zip或jar包的类加载器 - // 创建编译器 - final JavaFileManager javaFileManager = new JavaClassFileManager(ucl, CompilerUtil.getFileManager()); + final JavaClassFileManager javaFileManager = new JavaClassFileManager(ucl, CompilerUtil.getFileManager()); // classpath final List options = new ArrayList<>(); @@ -160,7 +191,7 @@ public class JavaSourceCompiler { final DiagnosticCollector diagnosticCollector = new DiagnosticCollector<>(); final List javaFileObjectList = getJavaFileObject(); final CompilationTask task = CompilerUtil.getTask(javaFileManager, diagnosticCollector, options, javaFileObjectList); - try{ + try { if (task.call()) { // 加载编译后的类 return javaFileManager.getClassLoader(StandardLocation.CLASS_OUTPUT); @@ -180,7 +211,7 @@ public class JavaSourceCompiler { private List getClassPath() { List classPathFileList = new ArrayList<>(); for (File file : libraryFileList) { - List jarOrZipFile = FileUtil.loopFiles(file, (subFile)-> JavaFileObjectUtil.isJarOrZipFile(subFile.getName())); + List jarOrZipFile = FileUtil.loopFiles(file, (subFile) -> JavaFileObjectUtil.isJarOrZipFile(subFile.getName())); classPathFileList.addAll(jarOrZipFile); if (file.isDirectory()) { classPathFileList.add(file); @@ -195,16 +226,18 @@ public class JavaSourceCompiler { * @return 待编译的Java文件对象 */ private List getJavaFileObject() { - final List collection = new ArrayList<>(); + final List list = new ArrayList<>(); - // 源码文件 - for (File file : sourceFileList) { - FileUtil.walkFiles(file, (subFile)-> collection.addAll(JavaFileObjectUtil.getJavaFileObjects(file))); + for (Resource resource : this.sourceList) { + if (resource instanceof FileResource) { + final File file = ((FileResource) resource).getFile(); + FileUtil.walkFiles(file, (subFile) -> list.addAll(JavaFileObjectUtil.getJavaFileObjects(file))); + } else { + list.add(new JavaSourceFileObject(resource.getName(), resource.getStream())); + } } - // 源码Map - collection.addAll(getJavaFileObjectByMap(this.sourceCodeMap)); - return collection; + return list; } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceFileObject.java b/hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceFileObject.java index cf8a4e707..db0c0dd8c 100644 --- a/hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceFileObject.java +++ b/hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceFileObject.java @@ -1,6 +1,8 @@ package cn.hutool.core.compiler; import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.CharUtil; +import cn.hutool.core.util.URLUtil; import javax.tools.SimpleJavaFileObject; import java.io.BufferedInputStream; @@ -12,8 +14,8 @@ import java.nio.charset.Charset; /** * Java 源码文件对象,支持:
*
    - *
  1. 源文件
  2. - *
  3. 代码内容
  4. + *
  5. 源文件,通过文件的uri传入
  6. + *
  7. 代码内容,通过流传入
  8. *
* * @author lzpeng @@ -27,7 +29,7 @@ class JavaSourceFileObject extends SimpleJavaFileObject { private InputStream inputStream; /** - * 构造 + * 构造,支持File等路径类型的源码 * * @param uri 需要编译的文件uri */ @@ -36,7 +38,7 @@ class JavaSourceFileObject extends SimpleJavaFileObject { } /** - * 构造 + * 构造,支持String类型的源码 * * @param className 需要编译的类名 * @param code 需要编译的类源码 @@ -46,13 +48,13 @@ class JavaSourceFileObject extends SimpleJavaFileObject { } /** - * 构造 + * 构造,支持流中读取源码(例如zip或网络等) * * @param name 需要编译的文件名 * @param inputStream 输入流 */ protected JavaSourceFileObject(String name, InputStream inputStream) { - this(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension)); + this(URLUtil.getStringURI(name.replace(CharUtil.DOT, CharUtil.SLASH) + Kind.SOURCE.extension)); this.inputStream = inputStream; } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java index 6780b93d4..49ae0f596 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java @@ -183,7 +183,11 @@ public class FileUtil extends PathUtil { } /** - * 递归遍历目录并处理目录下的文件 + * 递归遍历目录并处理目录下的文件,可以处理目录或文件: + *
    + *
  • 非目录则直接调用{@link Consumer}处理
  • + *
  • 目录则递归调用此方法处理
  • + *
* * @param file 文件或目录,文件直接处理 * @param consumer 文件处理器,只会处理文件 diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java index 0d854b185..2fd77011f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java @@ -1,21 +1,24 @@ package cn.hutool.core.io.resource; -import java.io.File; -import java.nio.file.Path; - import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; +import java.io.File; +import java.io.InputStream; +import java.io.Serializable; +import java.net.URL; +import java.nio.file.Path; + /** - * 文件资源访问对象 + * 文件资源访问对象,支持{@link Path} 和 {@link File} 访问 * * @author looly - * */ -public class FileResource extends UrlResource { +public class FileResource implements Resource, Serializable { private static final long serialVersionUID = 1L; + private final File file; + // ----------------------------------------------------------------------- Constructor start /** * 构造 @@ -43,7 +46,7 @@ public class FileResource extends UrlResource { * @param fileName 文件名,如果为null获取文件本身的文件名 */ public FileResource(File file, String fileName) { - super(URLUtil.getURL(file), StrUtil.isBlank(fileName) ? file.getName() : fileName); + this.file = file; } /** @@ -56,4 +59,36 @@ public class FileResource extends UrlResource { } // ----------------------------------------------------------------------- Constructor end + @Override + public String getName() { + return this.file.getName(); + } + + @Override + public URL getUrl(){ + return URLUtil.getURL(this.file); + } + + @Override + public InputStream getStream() throws NoResourceException { + return FileUtil.getInputStream(this.file); + } + + /** + * 获取文件 + * + * @return 文件 + */ + public File getFile() { + return this.file; + } + + /** + * 返回路径 + * @return 返回URL路径 + */ + @Override + public String toString() { + return (null == this.file) ? "null" : this.file.toString(); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/Resource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/Resource.java index a174c9dce..2ec9d844f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/Resource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/Resource.java @@ -13,86 +13,102 @@ import java.nio.charset.Charset; /** * 资源接口定义
- * 资源可以是文件、URL、ClassPath中的文件亦或者jar包中的文件 - * + *

资源是数据表示的统称,我们可以将任意的数据封装为一个资源,然后读取其内容。

+ *

资源可以是文件、URL、ClassPath中的文件亦或者jar(zip)包中的文件。

+ *

+ * 提供资源接口的意义在于,我们可以使用一个方法接收任意类型的数据,从而处理数据, + * 无需专门针对File、InputStream等写多个重载方法,同时也为更好的扩展提供了可能。 + *

+ *

使用非常简单,假设我们需要从classpath中读取一个xml,我们不用关心这个文件在目录中还是在jar中:

+ *
+ *     Resource resource = new ClassPathResource("test.xml");
+ *     String xmlStr = resource.readUtf8Str();
+ * 
+ *

同样,我们可以自己实现Resource接口,按照业务需要从任意位置读取数据,比如从数据库中。

+ * * @author looly * @since 3.2.1 */ public interface Resource { - + /** * 获取资源名,例如文件资源的资源名为文件名 + * * @return 资源名 * @since 4.0.13 */ String getName(); - + /** - * 获得解析后的{@link URL} + * 获得解析后的{@link URL},无对应URL的返回{@code null} + * * @return 解析后的{@link URL} */ URL getUrl(); - + /** * 获得 {@link InputStream} + * * @return {@link InputStream} */ InputStream getStream(); /** * 将资源内容写出到流,不关闭输出流,但是关闭资源流 + * * @param out 输出流 * @throws IORuntimeException IO异常 * @since 5.3.5 */ - default void writeTo(OutputStream out) throws IORuntimeException{ + default void writeTo(OutputStream out) throws IORuntimeException { try (InputStream in = getStream()) { IoUtil.copy(in, out); } catch (IOException e) { throw new IORuntimeException(e); } } - + /** * 获得Reader + * * @param charset 编码 * @return {@link BufferedReader} */ - default BufferedReader getReader(Charset charset){ + default BufferedReader getReader(Charset charset) { return IoUtil.getReader(getStream(), charset); } - + /** * 读取资源内容,读取完毕后会关闭流
* 关闭流并不影响下一次读取 - * + * * @param charset 编码 * @return 读取资源内容 * @throws IORuntimeException 包装{@link IOException} */ - default String readStr(Charset charset) throws IORuntimeException{ + default String readStr(Charset charset) throws IORuntimeException { return IoUtil.read(getReader(charset)); } - + /** * 读取资源内容,读取完毕后会关闭流
* 关闭流并不影响下一次读取 - * + * * @return 读取资源内容 * @throws IORuntimeException 包装IOException */ - default String readUtf8Str() throws IORuntimeException{ + default String readUtf8Str() throws IORuntimeException { return readStr(CharsetUtil.CHARSET_UTF_8); } - + /** * 读取资源内容,读取完毕后会关闭流
* 关闭流并不影响下一次读取 - * + * * @return 读取资源内容 * @throws IORuntimeException 包装IOException */ - default byte[] readBytes() throws IORuntimeException{ + default byte[] readBytes() throws IORuntimeException { return IoUtil.readBytes(getStream()); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java index 8a52a090b..4ba026e38 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java @@ -134,6 +134,18 @@ public class URLUtil { } } + /** + * 获取string协议的URL,类似于string:///xxxxx + * + * @param content 正文 + * @return URL + * @since 5.5.2 + */ + public static URI getStringURI(CharSequence content){ + final String contentStr = StrUtil.addPrefixIfNot(content, "string:///"); + return URI.create(contentStr); + } + /** * 将URL字符串转换为URL对象,并做必要验证 * diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java index 0e63d35e1..dabca85c3 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java @@ -355,23 +355,53 @@ public class ZipUtil { * @since 3.0.9 */ public static File zip(File zipFile, String[] paths, InputStream[] ins, Charset charset) throws UtilException { + ZipOutputStream out = null; + try { + out = getZipOutputStream(zipFile, charset); + zip(out, paths, ins); + } finally { + IoUtil.close(out); + } + return zipFile; + } + + /** + * 将文件流压缩到目标流中 + * + * @param out 目标流,压缩完成自动关闭 + * @param paths 流数据在压缩文件中的路径或文件名 + * @param ins 要压缩的源,添加完成后自动关闭流 + * @since 5.5.2 + */ + public static void zip(OutputStream out, String[] paths, InputStream[] ins) { + ZipOutputStream zipOutputStream = null; + try { + zipOutputStream = getZipOutputStream(out, DEFAULT_CHARSET); + zip(zipOutputStream, paths, ins); + } finally { + IoUtil.close(zipOutputStream); + } + } + + /** + * 将文件流压缩到目标流中 + * + * @param zipOutputStream 目标流,压缩完成不关闭 + * @param paths 流数据在压缩文件中的路径或文件名 + * @param ins 要压缩的源,添加完成后自动关闭流 + * @throws IORuntimeException IO异常 + * @since 5.5.2 + */ + public static void zip(ZipOutputStream zipOutputStream, String[] paths, InputStream[] ins) throws IORuntimeException { if (ArrayUtil.isEmpty(paths) || ArrayUtil.isEmpty(ins)) { throw new IllegalArgumentException("Paths or ins is empty !"); } if (paths.length != ins.length) { throw new IllegalArgumentException("Paths length is not equals to ins length !"); } - - ZipOutputStream out = null; - try { - out = getZipOutputStream(zipFile, charset); - for (int i = 0; i < paths.length; i++) { - add(ins[i], paths[i], out); - } - } finally { - IoUtil.close(out); + for (int i = 0; i < paths.length; i++) { + add(ins[i], paths[i], zipOutputStream); } - return zipFile; } /** @@ -1056,10 +1086,10 @@ public class ZipUtil { * @param file 需要压缩的文件 * @param path 在压缩文件中的路径 * @param out 压缩文件存储对象 - * @throws UtilException IO异常 + * @throws IORuntimeException IO异常 * @since 4.0.5 */ - private static void add(File file, String path, ZipOutputStream out) throws UtilException { + private static void add(File file, String path, ZipOutputStream out) throws IORuntimeException { add(FileUtil.getInputStream(file), path, out); } @@ -1069,9 +1099,9 @@ public class ZipUtil { * @param in 需要压缩的输入流,使用完后自动关闭 * @param path 压缩的路径 * @param out 压缩文件存储对象 - * @throws UtilException IO异常 + * @throws IORuntimeException IO异常 */ - private static void add(InputStream in, String path, ZipOutputStream out) throws UtilException { + private static void add(InputStream in, String path, ZipOutputStream out) throws IORuntimeException { if (null == in) { return; } @@ -1079,7 +1109,7 @@ public class ZipUtil { out.putNextEntry(new ZipEntry(path)); IoUtil.copy(in, out); } catch (IOException e) { - throw new UtilException(e); + throw new IORuntimeException(e); } finally { IoUtil.close(in); closeEntry(out); diff --git a/hutool-core/src/test/java/cn/hutool/core/io/resource/ResourceUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/resource/ResourceUtilTest.java index eab36a616..1e4bedfc1 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/resource/ResourceUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/resource/ResourceUtilTest.java @@ -1,6 +1,8 @@ package cn.hutool.core.io.resource; +import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; @@ -10,6 +12,11 @@ public class ResourceUtilTest { public void readXmlTest(){ final String str = ResourceUtil.readUtf8Str("test.xml"); Assert.assertNotNull(str); + + Resource resource = new ClassPathResource("test.xml"); + final String xmlStr = resource.readUtf8Str(); + + Assert.assertEquals(str, xmlStr); } @Test @@ -19,4 +26,11 @@ public class ResourceUtilTest { Assert.assertArrayEquals("testData".getBytes(), stringResource.readBytes()); Assert.assertArrayEquals("testData".getBytes(), IoUtil.readBytes(stringResource.getStream())); } + + @Test + public void fileResourceTest(){ + final FileResource resource = new FileResource(FileUtil.file("test.xml")); + Assert.assertEquals("test.xml", resource.getName()); + Assert.assertTrue(StrUtil.isNotEmpty(resource.readUtf8Str())); + } } diff --git a/hutool-setting/src/main/java/cn/hutool/setting/SettingLoader.java b/hutool-setting/src/main/java/cn/hutool/setting/SettingLoader.java index 401e0a878..adc1fd9fe 100644 --- a/hutool-setting/src/main/java/cn/hutool/setting/SettingLoader.java +++ b/hutool-setting/src/main/java/cn/hutool/setting/SettingLoader.java @@ -2,7 +2,7 @@ package cn.hutool.setting; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; -import cn.hutool.core.io.resource.UrlResource; +import cn.hutool.core.io.resource.Resource; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.CharsetUtil; @@ -70,17 +70,17 @@ public class SettingLoader { /** * 加载设置文件 * - * @param urlResource 配置文件URL + * @param resource 配置文件URL * @return 加载是否成功 */ - public boolean load(UrlResource urlResource) { - if (urlResource == null) { + public boolean load(Resource resource) { + if (resource == null) { throw new NullPointerException("Null setting url define!"); } - log.debug("Load setting file [{}]", urlResource); + log.debug("Load setting file [{}]", resource); InputStream settingStream = null; try { - settingStream = urlResource.getStream(); + settingStream = resource.getStream(); load(settingStream); } catch (Exception e) { log.error(e, "Load setting error!"); diff --git a/hutool-setting/src/main/java/cn/hutool/setting/SettingUtil.java b/hutool-setting/src/main/java/cn/hutool/setting/SettingUtil.java index 44c7ba089..776ed8933 100644 --- a/hutool-setting/src/main/java/cn/hutool/setting/SettingUtil.java +++ b/hutool-setting/src/main/java/cn/hutool/setting/SettingUtil.java @@ -1,6 +1,6 @@ package cn.hutool.setting; -import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.file.FileNameUtil; import cn.hutool.core.io.resource.NoResourceException; import cn.hutool.core.util.StrUtil; @@ -27,22 +27,13 @@ public class SettingUtil { * @return 当前环境下配置文件 */ public static Setting get(String name) { - Setting setting = SETTING_MAP.get(name); - if (null == setting) { - synchronized (SettingUtil.class) { - setting = SETTING_MAP.get(name); - if (null == setting) { - String filePath = name; - String extName = FileUtil.extName(filePath); - if (StrUtil.isEmpty(extName)) { - filePath = filePath + "." + Setting.EXT_NAME; - } - setting = new Setting(filePath, true); - SETTING_MAP.put(name, setting); - } + return SETTING_MAP.computeIfAbsent(name, (filePath)->{ + final String extName = FileNameUtil.extName(filePath); + if (StrUtil.isEmpty(extName)) { + filePath = filePath + "." + Setting.EXT_NAME; } - } - return setting; + return new Setting(filePath, true); + }); } /** diff --git a/hutool-setting/src/main/java/cn/hutool/setting/dialect/Props.java b/hutool-setting/src/main/java/cn/hutool/setting/dialect/Props.java index 5c5c08513..afd72ce39 100644 --- a/hutool-setting/src/main/java/cn/hutool/setting/dialect/Props.java +++ b/hutool-setting/src/main/java/cn/hutool/setting/dialect/Props.java @@ -21,7 +21,6 @@ import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.log.StaticLog; -import cn.hutool.setting.Setting; import cn.hutool.setting.SettingRuntimeException; import java.io.BufferedReader; @@ -239,7 +238,7 @@ public final class Props extends Properties implements BasicTypeGetter, if (null != charset) { this.charset = charset; } - this.load(new UrlResource(propertiesUrl)); + this.load(propertiesUrl); } /** @@ -257,16 +256,26 @@ public final class Props extends Properties implements BasicTypeGetter, /** * 初始化配置文件 - * - * @param urlResource {@link UrlResource} + * + * @param url {@link URL} + * @since 5.5.2 */ - public void load(Resource urlResource) { - this.propertiesFileUrl = urlResource.getUrl(); + public void load(URL url) { + load(new UrlResource(url)); + } + + /** + * 初始化配置文件 + * + * @param resource {@link Resource} + */ + public void load(Resource resource) { + this.propertiesFileUrl = resource.getUrl(); if (null == this.propertiesFileUrl) { - throw new SettingRuntimeException("Can not find properties file: [{}]", urlResource); + throw new SettingRuntimeException("Can not find properties file: [{}]", resource); } - try (final BufferedReader reader = urlResource.getReader(charset)) { + try (final BufferedReader reader = resource.getReader(charset)) { super.load(reader); } catch (IOException e) { throw new IORuntimeException(e); @@ -277,7 +286,7 @@ public final class Props extends Properties implements BasicTypeGetter, * 重新加载配置文件 */ public void load() { - this.load(new UrlResource(this.propertiesFileUrl)); + this.load(this.propertiesFileUrl); } /** diff --git a/hutool-setting/src/main/java/cn/hutool/setting/dialect/PropsUtil.java b/hutool-setting/src/main/java/cn/hutool/setting/dialect/PropsUtil.java index 31cfed5ec..a8a121c63 100644 --- a/hutool-setting/src/main/java/cn/hutool/setting/dialect/PropsUtil.java +++ b/hutool-setting/src/main/java/cn/hutool/setting/dialect/PropsUtil.java @@ -20,7 +20,6 @@ public class PropsUtil { * 配置文件缓存 */ private static final Map propsMap = new ConcurrentHashMap<>(); - private static final Object lock = new Object(); /** * 获取当前环境下的配置文件
@@ -30,22 +29,13 @@ public class PropsUtil { * @return 当前环境下配置文件 */ public static Props get(String name) { - Props props = propsMap.get(name); - if (null == props) { - synchronized (lock) { - props = propsMap.get(name); - if (null == props) { - String filePath = name; - String extName = FileUtil.extName(filePath); - if (StrUtil.isEmpty(extName)) { - filePath = filePath + "." + Props.EXT_NAME; - } - props = new Props(filePath); - propsMap.put(name, props); - } + return propsMap.computeIfAbsent(name, (filePath)->{ + final String extName = FileUtil.extName(filePath); + if (StrUtil.isEmpty(extName)) { + filePath = filePath + "." + Props.EXT_NAME; } - } - return props; + return new Props(filePath); + }); } /** @@ -66,4 +56,14 @@ public class PropsUtil { } return null; } + + /** + * 获取系统参数,例如用户在执行java命令时定义的 -Duse=hutool + * + * @return 系统参数Props + * @since 5.5.2 + */ + public static Props getSystemProps(){ + return new Props(System.getProperties()); + } } From c1642522bbf5adc70fb57517e46d5a921d9a5703 Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 30 Nov 2020 02:29:04 +0800 Subject: [PATCH 4/4] add methods --- CHANGELOG.md | 1 + .../src/main/java/cn/hutool/Hutool.java | 37 ++++++++++++++++--- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6dca943a..c2ddb1051 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ * 【core 】 增加ResourceClassLoader、CharSequenceResource、FileObjectResource * 【core 】 修改IoUtil.read(Reader)逻辑默认关闭Reader * 【core 】 ZipUtil增加Zip方法(pr#222@Gitee) +* 【all 】 增加Hutool.getAllUtils和printAllUtils方法 ### Bug修复 * 【cron 】 修复CronTimer可能死循环的问题(issue#1224@Github) diff --git a/hutool-all/src/main/java/cn/hutool/Hutool.java b/hutool-all/src/main/java/cn/hutool/Hutool.java index 18fe66962..891b5b499 100644 --- a/hutool-all/src/main/java/cn/hutool/Hutool.java +++ b/hutool-all/src/main/java/cn/hutool/Hutool.java @@ -16,24 +16,51 @@ package cn.hutool; +import cn.hutool.core.lang.ConsoleTable; +import cn.hutool.core.util.ClassUtil; +import cn.hutool.core.util.StrUtil; + +import java.util.Set; + /** *

* Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 *

- * + * *

* Hutool中的工具方法来自于每个用户的精雕细琢,它涵盖了Java开发底层代码中的方方面面,它既是大型项目开发中解决小问题的利器,也是小型项目中的效率担当;
*

* *

Hutool是项目中“util”包友好的替代,它节省了开发人员对项目中公用类和公用工具方法的封装时间,使开发专注于业务,同时可以最大限度的避免封装不完善带来的bug。

- * - * @author Looly * + * @author Looly */ public class Hutool { - + public static final String AUTHOR = "Looly"; - + private Hutool() { } + + /** + * 显示Hutool所有的工具类 + * + * @since 5.5.2 + */ + public static Set> getAllUtils() { + return ClassUtil.scanPackage("cn.hutool", + (clazz) -> (false == clazz.isInterface()) && StrUtil.endWith(clazz.getSimpleName(), "Util")); + } + + /** + * 控制台打印所有工具类 + */ + public static void printAllUtils() { + final Set> allUtils = getAllUtils(); + final ConsoleTable consoleTable = ConsoleTable.create().addHeader("工具类名", "所在包"); + for (Class clazz : allUtils) { + consoleTable.addBody(clazz.getSimpleName(), clazz.getPackage().getName()); + } + consoleTable.print(); + } }