From b2ee5fbfec75d157617aaed1574fad7c08dcd579 Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 29 Nov 2020 15:18:00 +0800 Subject: [PATCH] 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())); + } }