This commit is contained in:
Looly
2020-11-30 02:08:14 +08:00
parent 2f7cd18895
commit cdfae52eb9
16 changed files with 325 additions and 166 deletions

View File

@@ -26,6 +26,7 @@
* 【core 】 增加compile包pr#1243@Github * 【core 】 增加compile包pr#1243@Github
* 【core 】 增加ResourceClassLoader、CharSequenceResource、FileObjectResource * 【core 】 增加ResourceClassLoader、CharSequenceResource、FileObjectResource
* 【core 】 修改IoUtil.read(Reader)逻辑默认关闭Reader * 【core 】 修改IoUtil.read(Reader)逻辑默认关闭Reader
* 【core 】 ZipUtil增加Zip方法pr#222@Gitee
### Bug修复 ### Bug修复
* 【cron 】 修复CronTimer可能死循环的问题issue#1224@Github * 【cron 】 修复CronTimer可能死循环的问题issue#1224@Github

View File

@@ -4,6 +4,7 @@ import javax.tools.DiagnosticListener;
import javax.tools.JavaCompiler; import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager; import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject; import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider; import javax.tools.ToolProvider;
/** /**
@@ -13,6 +14,7 @@ import javax.tools.ToolProvider;
* @since 5.5.2 * @since 5.5.2
*/ */
public class CompilerUtil { public class CompilerUtil {
/** /**
* java 编译器 * 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); return SYSTEM_COMPILER.getStandardFileManager(null, null, null);
} }
/** /**
* 新建编译任务 * 新建编译任务
* *
* @param fileManager {@link JavaFileManager},用于管理已经编译好的文件 * @param fileManager {@link JavaFileManager},用于管理已经编译好的文件
* @param diagnosticListener 诊断监听 * @param diagnosticListener 诊断监听
* @param options 选项,例如 -cpXXX等 * @param options 选项,例如 -cpXXX等
* @param compilationUnits 编译单元,即需要编译的对象 * @param compilationUnits 编译单元,即需要编译的对象
* @return {@link JavaCompiler.CompilationTask} * @return {@link JavaCompiler.CompilationTask}
*/ */
public static JavaCompiler.CompilationTask getTask( public static JavaCompiler.CompilationTask getTask(
@@ -53,4 +55,15 @@ public class CompilerUtil {
Iterable<? extends JavaFileObject> compilationUnits) { Iterable<? extends JavaFileObject> compilationUnits) {
return SYSTEM_COMPILER.getTask(null, fileManager, diagnosticListener, options, null, 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);
}
} }

View File

@@ -70,7 +70,7 @@ class JavaClassFileManager extends ForwardingJavaFileManager<JavaFileManager> {
*/ */
@Override @Override
public JavaFileObject getJavaFileForOutput(final Location location, final String className, final Kind kind, final FileObject sibling) { 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)); this.classFileObjectMap.put(className, new FileObjectResource(javaFileObject));
return javaFileObject; return javaFileObject;
} }

View File

@@ -1,22 +1,22 @@
package cn.hutool.core.compiler; package cn.hutool.core.compiler;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.URLUtil;
import javax.tools.SimpleJavaFileObject; import javax.tools.SimpleJavaFileObject;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.URI;
/** /**
* Java 字节码文件对象 * Java 字节码文件对象用于在内存中暂存class字节码从而可以在ClassLoader中动态加载。
* *
* @author lzpeng * @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 * @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 className 编译后的class文件的类名
* @param kind 需要编译的文件类型
* @see JavaClassFileManager#getJavaFileForOutput(javax.tools.JavaFileManager.Location, java.lang.String, javax.tools.JavaFileObject.Kind, javax.tools.FileObject) * @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) { protected JavaClassFileObject(String className) {
super(URI.create("string:///" + className.replaceAll("\\.", "/") + kind.extension), kind); super(URLUtil.getStringURI(className.replace(CharUtil.DOT, CharUtil.SLASH) + Kind.CLASS.extension), Kind.CLASS);
this.byteArrayOutputStream = new ByteArrayOutputStream(); this.byteArrayOutputStream = new ByteArrayOutputStream();
} }

View File

@@ -2,6 +2,9 @@ package cn.hutool.core.compiler;
import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil; 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.map.MapUtil;
import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.CharsetUtil;
@@ -11,7 +14,6 @@ import cn.hutool.core.util.URLUtil;
import javax.tools.DiagnosticCollector; import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler.CompilationTask; import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject; import javax.tools.JavaFileObject;
import javax.tools.StandardLocation; import javax.tools.StandardLocation;
import java.io.File; import java.io.File;
@@ -21,49 +23,55 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
* Java 源码编译器 * Java 源码编译器
* <p>通过此类可以动态编译java源码并加载到ClassLoader从而动态获取加载的类。</p>
* <p>JavaSourceCompiler支持加载的源码类型包括</p>
* <ul>
* <li>源码文件</li>
* <li>源码文件源码字符串</li>
* </ul>
*
* <p>使用方法如下:</p>
* <pre>
* 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&lt;?&gt; clazz = classLoader.loadClass("c.C");
* </pre>
* *
* @author lzpeng * @author lzpeng
*/ */
public class JavaSourceCompiler { public class JavaSourceCompiler {
/** /**
* 待编译的文件 可以是 .java文件 压缩文件 文件夹 递归搜索文件夹内的zip包和jar包 * 待编译的资源,支持:
*
* <ul>
* <li>源码字符串,使用{@link StringResource}</li>
* <li>源码文件、源码jar包或源码zip包亦或者文件夹使用{@link FileResource}</li>
* </ul>
* 可以是 .java文件 压缩文件 文件夹 递归搜索文件夹内的zip包和jar包
*/ */
private final List<File> sourceFileList = new ArrayList<>(); private final List<Resource> sourceList = new ArrayList<>();
/** /**
* 编译时需要加入classpath中的文件 可以是 压缩文件 文件夹递归搜索文件夹内的zip包和jar包 * 编译时需要加入classpath中的文件 可以是 压缩文件 文件夹递归搜索文件夹内的zip包和jar包
*/ */
private final List<File> libraryFileList = new ArrayList<>(); private final List<File> libraryFileList = new ArrayList<>();
/**
* 源码映射 key: 类名 value: 类源码
*/
private final Map<String, String> sourceCodeMap = new LinkedHashMap<>();
/** /**
* 编译类时使用的父类加载器 * 编译类时使用的父类加载器
*/ */
private final ClassLoader parentClassLoader; private final ClassLoader parentClassLoader;
/**
* 构造
*
* @param parent 父类加载器null则使用默认类加载器
*/
private JavaSourceCompiler(ClassLoader parent) {
this.parentClassLoader = ObjectUtil.defaultIfNull(parent, ClassLoaderUtil.getClassLoader());
}
/** /**
* 创建Java源码编译器 * 创建Java源码编译器
* *
@@ -74,16 +82,41 @@ public class JavaSourceCompiler {
return new JavaSourceCompiler(parent); return new JavaSourceCompiler(parent);
} }
/**
* 构造
*
* @param parent 父类加载器null则使用默认类加载器
*/
private JavaSourceCompiler(ClassLoader parent) {
this.parentClassLoader = ObjectUtil.defaultIfNull(parent, ClassLoaderUtil.getClassLoader());
}
/** /**
* 向编译器中加入待编译的文件 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包 * 向编译器中加入待编译的资源<br>
* 支持 .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;
}
/**
* 向编译器中加入待编译的文件<br>
* 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包
* *
* @param files 待编译的文件 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包 * @param files 待编译的文件 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包
* @return Java源码编译器 * @return Java源码编译器
*/ */
public JavaSourceCompiler addSource(final File... files) { public JavaSourceCompiler addSource(File... files) {
if (ArrayUtil.isNotEmpty(files)) { if (ArrayUtil.isNotEmpty(files)) {
this.sourceFileList.addAll(Arrays.asList(files)); for (File file : files) {
this.sourceList.add(new FileResource(file));
}
} }
return this; return this;
} }
@@ -94,36 +127,36 @@ public class JavaSourceCompiler {
* @param sourceCodeMap 源码Map key: 类名 value 源码 * @param sourceCodeMap 源码Map key: 类名 value 源码
* @return Java源码编译器 * @return Java源码编译器
*/ */
public JavaSourceCompiler addSource(final Map<String, String> sourceCodeMap) { public JavaSourceCompiler addSource(Map<String, String> sourceCodeMap) {
if (MapUtil.isNotEmpty(sourceCodeMap)) { if (MapUtil.isNotEmpty(sourceCodeMap)) {
this.sourceCodeMap.putAll(sourceCodeMap); sourceCodeMap.forEach(this::addSource);
} }
return this; 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 className 类名
* @param sourceCode 源码 * @param sourceCode 源码
* @return Java文件编译器 * @return Java文件编译器
*/ */
public JavaSourceCompiler addSource(final String className, final String sourceCode) { public JavaSourceCompiler addSource(String className, String sourceCode) {
if (className != null && sourceCode != null) { 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; return this;
} }
@@ -138,15 +171,13 @@ public class JavaSourceCompiler {
final List<File> classPath = getClassPath(); final List<File> classPath = getClassPath();
final URL[] urLs = URLUtil.getURLs(classPath.toArray(new File[0])); final URL[] urLs = URLUtil.getURLs(classPath.toArray(new File[0]));
final URLClassLoader ucl = URLClassLoader.newInstance(urLs, this.parentClassLoader); final URLClassLoader ucl = URLClassLoader.newInstance(urLs, this.parentClassLoader);
if (sourceCodeMap.isEmpty() && sourceFileList.isEmpty()) { if (sourceList.isEmpty()) {
// 没有需要编译的源码 // 没有需要编译的源码文件返回加载zip或jar包的类加载器
return ucl; return ucl;
} }
// 没有需要编译的源码文件返回加载zip或jar包的类加载器
// 创建编译器 // 创建编译器
final JavaFileManager javaFileManager = new JavaClassFileManager(ucl, CompilerUtil.getFileManager()); final JavaClassFileManager javaFileManager = new JavaClassFileManager(ucl, CompilerUtil.getFileManager());
// classpath // classpath
final List<String> options = new ArrayList<>(); final List<String> options = new ArrayList<>();
@@ -160,7 +191,7 @@ public class JavaSourceCompiler {
final DiagnosticCollector<? super JavaFileObject> diagnosticCollector = new DiagnosticCollector<>(); final DiagnosticCollector<? super JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
final List<JavaFileObject> javaFileObjectList = getJavaFileObject(); final List<JavaFileObject> javaFileObjectList = getJavaFileObject();
final CompilationTask task = CompilerUtil.getTask(javaFileManager, diagnosticCollector, options, javaFileObjectList); final CompilationTask task = CompilerUtil.getTask(javaFileManager, diagnosticCollector, options, javaFileObjectList);
try{ try {
if (task.call()) { if (task.call()) {
// 加载编译后的类 // 加载编译后的类
return javaFileManager.getClassLoader(StandardLocation.CLASS_OUTPUT); return javaFileManager.getClassLoader(StandardLocation.CLASS_OUTPUT);
@@ -180,7 +211,7 @@ public class JavaSourceCompiler {
private List<File> getClassPath() { private List<File> getClassPath() {
List<File> classPathFileList = new ArrayList<>(); List<File> classPathFileList = new ArrayList<>();
for (File file : libraryFileList) { for (File file : libraryFileList) {
List<File> jarOrZipFile = FileUtil.loopFiles(file, (subFile)-> JavaFileObjectUtil.isJarOrZipFile(subFile.getName())); List<File> jarOrZipFile = FileUtil.loopFiles(file, (subFile) -> JavaFileObjectUtil.isJarOrZipFile(subFile.getName()));
classPathFileList.addAll(jarOrZipFile); classPathFileList.addAll(jarOrZipFile);
if (file.isDirectory()) { if (file.isDirectory()) {
classPathFileList.add(file); classPathFileList.add(file);
@@ -195,16 +226,18 @@ public class JavaSourceCompiler {
* @return 待编译的Java文件对象 * @return 待编译的Java文件对象
*/ */
private List<JavaFileObject> getJavaFileObject() { private List<JavaFileObject> getJavaFileObject() {
final List<JavaFileObject> collection = new ArrayList<>(); final List<JavaFileObject> list = new ArrayList<>();
// 源码文件 for (Resource resource : this.sourceList) {
for (File file : sourceFileList) { if (resource instanceof FileResource) {
FileUtil.walkFiles(file, (subFile)-> collection.addAll(JavaFileObjectUtil.getJavaFileObjects(file))); 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 return list;
collection.addAll(getJavaFileObjectByMap(this.sourceCodeMap));
return collection;
} }
/** /**

View File

@@ -1,6 +1,8 @@
package cn.hutool.core.compiler; package cn.hutool.core.compiler;
import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.URLUtil;
import javax.tools.SimpleJavaFileObject; import javax.tools.SimpleJavaFileObject;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
@@ -12,8 +14,8 @@ import java.nio.charset.Charset;
/** /**
* Java 源码文件对象,支持:<br> * Java 源码文件对象,支持:<br>
* <ol> * <ol>
* <li>源文件</li> * <li>源文件通过文件的uri传入</li>
* <li>代码内容</li> * <li>代码内容,通过流传入</li>
* </ol> * </ol>
* *
* @author lzpeng * @author lzpeng
@@ -27,7 +29,7 @@ class JavaSourceFileObject extends SimpleJavaFileObject {
private InputStream inputStream; private InputStream inputStream;
/** /**
* 构造 * 构造支持File等路径类型的源码
* *
* @param uri 需要编译的文件uri * @param uri 需要编译的文件uri
*/ */
@@ -36,7 +38,7 @@ class JavaSourceFileObject extends SimpleJavaFileObject {
} }
/** /**
* 构造 * 构造支持String类型的源码
* *
* @param className 需要编译的类名 * @param className 需要编译的类名
* @param code 需要编译的类源码 * @param code 需要编译的类源码
@@ -46,13 +48,13 @@ class JavaSourceFileObject extends SimpleJavaFileObject {
} }
/** /**
* 构造 * 构造支持流中读取源码例如zip或网络等
* *
* @param name 需要编译的文件名 * @param name 需要编译的文件名
* @param inputStream 输入流 * @param inputStream 输入流
*/ */
protected JavaSourceFileObject(String name, InputStream 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; this.inputStream = inputStream;
} }

View File

@@ -183,7 +183,11 @@ public class FileUtil extends PathUtil {
} }
/** /**
* 递归遍历目录并处理目录下的文件 * 递归遍历目录并处理目录下的文件,可以处理目录或文件:
* <ul>
* <li>非目录则直接调用{@link Consumer}处理</li>
* <li>目录则递归调用此方法处理</li>
* </ul>
* *
* @param file 文件或目录,文件直接处理 * @param file 文件或目录,文件直接处理
* @param consumer 文件处理器,只会处理文件 * @param consumer 文件处理器,只会处理文件

View File

@@ -1,21 +1,24 @@
package cn.hutool.core.io.resource; 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.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil; 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 * @author looly
*
*/ */
public class FileResource extends UrlResource { public class FileResource implements Resource, Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private final File file;
// ----------------------------------------------------------------------- Constructor start // ----------------------------------------------------------------------- Constructor start
/** /**
* 构造 * 构造
@@ -43,7 +46,7 @@ public class FileResource extends UrlResource {
* @param fileName 文件名如果为null获取文件本身的文件名 * @param fileName 文件名如果为null获取文件本身的文件名
*/ */
public FileResource(File file, String fileName) { 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 // ----------------------------------------------------------------------- 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();
}
} }

View File

@@ -13,86 +13,102 @@ import java.nio.charset.Charset;
/** /**
* 资源接口定义<br> * 资源接口定义<br>
* 资源可以是文件、URL、ClassPath中的文件亦或者jar包中的文件 * <p>资源是数据表示的统称,我们可以将任意的数据封装为一个资源,然后读取其内容。</p>
* * <p>资源可以是文件、URL、ClassPath中的文件亦或者jar(zip)包中的文件。</p>
* <p>
* 提供资源接口的意义在于,我们可以使用一个方法接收任意类型的数据,从而处理数据,
* 无需专门针对File、InputStream等写多个重载方法同时也为更好的扩展提供了可能。
* </p>
* <p>使用非常简单假设我们需要从classpath中读取一个xml我们不用关心这个文件在目录中还是在jar中</p>
* <pre>
* Resource resource = new ClassPathResource("test.xml");
* String xmlStr = resource.readUtf8Str();
* </pre>
* <p>同样我们可以自己实现Resource接口按照业务需要从任意位置读取数据比如从数据库中。</p>
*
* @author looly * @author looly
* @since 3.2.1 * @since 3.2.1
*/ */
public interface Resource { public interface Resource {
/** /**
* 获取资源名,例如文件资源的资源名为文件名 * 获取资源名,例如文件资源的资源名为文件名
*
* @return 资源名 * @return 资源名
* @since 4.0.13 * @since 4.0.13
*/ */
String getName(); String getName();
/** /**
* 获得解析后的{@link URL} * 获得解析后的{@link URL}无对应URL的返回{@code null}
*
* @return 解析后的{@link URL} * @return 解析后的{@link URL}
*/ */
URL getUrl(); URL getUrl();
/** /**
* 获得 {@link InputStream} * 获得 {@link InputStream}
*
* @return {@link InputStream} * @return {@link InputStream}
*/ */
InputStream getStream(); InputStream getStream();
/** /**
* 将资源内容写出到流,不关闭输出流,但是关闭资源流 * 将资源内容写出到流,不关闭输出流,但是关闭资源流
*
* @param out 输出流 * @param out 输出流
* @throws IORuntimeException IO异常 * @throws IORuntimeException IO异常
* @since 5.3.5 * @since 5.3.5
*/ */
default void writeTo(OutputStream out) throws IORuntimeException{ default void writeTo(OutputStream out) throws IORuntimeException {
try (InputStream in = getStream()) { try (InputStream in = getStream()) {
IoUtil.copy(in, out); IoUtil.copy(in, out);
} catch (IOException e) { } catch (IOException e) {
throw new IORuntimeException(e); throw new IORuntimeException(e);
} }
} }
/** /**
* 获得Reader * 获得Reader
*
* @param charset 编码 * @param charset 编码
* @return {@link BufferedReader} * @return {@link BufferedReader}
*/ */
default BufferedReader getReader(Charset charset){ default BufferedReader getReader(Charset charset) {
return IoUtil.getReader(getStream(), charset); return IoUtil.getReader(getStream(), charset);
} }
/** /**
* 读取资源内容,读取完毕后会关闭流<br> * 读取资源内容,读取完毕后会关闭流<br>
* 关闭流并不影响下一次读取 * 关闭流并不影响下一次读取
* *
* @param charset 编码 * @param charset 编码
* @return 读取资源内容 * @return 读取资源内容
* @throws IORuntimeException 包装{@link IOException} * @throws IORuntimeException 包装{@link IOException}
*/ */
default String readStr(Charset charset) throws IORuntimeException{ default String readStr(Charset charset) throws IORuntimeException {
return IoUtil.read(getReader(charset)); return IoUtil.read(getReader(charset));
} }
/** /**
* 读取资源内容,读取完毕后会关闭流<br> * 读取资源内容,读取完毕后会关闭流<br>
* 关闭流并不影响下一次读取 * 关闭流并不影响下一次读取
* *
* @return 读取资源内容 * @return 读取资源内容
* @throws IORuntimeException 包装IOException * @throws IORuntimeException 包装IOException
*/ */
default String readUtf8Str() throws IORuntimeException{ default String readUtf8Str() throws IORuntimeException {
return readStr(CharsetUtil.CHARSET_UTF_8); return readStr(CharsetUtil.CHARSET_UTF_8);
} }
/** /**
* 读取资源内容,读取完毕后会关闭流<br> * 读取资源内容,读取完毕后会关闭流<br>
* 关闭流并不影响下一次读取 * 关闭流并不影响下一次读取
* *
* @return 读取资源内容 * @return 读取资源内容
* @throws IORuntimeException 包装IOException * @throws IORuntimeException 包装IOException
*/ */
default byte[] readBytes() throws IORuntimeException{ default byte[] readBytes() throws IORuntimeException {
return IoUtil.readBytes(getStream()); return IoUtil.readBytes(getStream());
} }
} }

View File

@@ -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对象并做必要验证 * 将URL字符串转换为URL对象并做必要验证
* *

View File

@@ -355,23 +355,53 @@ public class ZipUtil {
* @since 3.0.9 * @since 3.0.9
*/ */
public static File zip(File zipFile, String[] paths, InputStream[] ins, Charset charset) throws UtilException { 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)) { if (ArrayUtil.isEmpty(paths) || ArrayUtil.isEmpty(ins)) {
throw new IllegalArgumentException("Paths or ins is empty !"); throw new IllegalArgumentException("Paths or ins is empty !");
} }
if (paths.length != ins.length) { if (paths.length != ins.length) {
throw new IllegalArgumentException("Paths length is not equals to ins length !"); throw new IllegalArgumentException("Paths length is not equals to ins length !");
} }
for (int i = 0; i < paths.length; i++) {
ZipOutputStream out = null; add(ins[i], paths[i], zipOutputStream);
try {
out = getZipOutputStream(zipFile, charset);
for (int i = 0; i < paths.length; i++) {
add(ins[i], paths[i], out);
}
} finally {
IoUtil.close(out);
} }
return zipFile;
} }
/** /**
@@ -1056,10 +1086,10 @@ public class ZipUtil {
* @param file 需要压缩的文件 * @param file 需要压缩的文件
* @param path 在压缩文件中的路径 * @param path 在压缩文件中的路径
* @param out 压缩文件存储对象 * @param out 压缩文件存储对象
* @throws UtilException IO异常 * @throws IORuntimeException IO异常
* @since 4.0.5 * @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); add(FileUtil.getInputStream(file), path, out);
} }
@@ -1069,9 +1099,9 @@ public class ZipUtil {
* @param in 需要压缩的输入流,使用完后自动关闭 * @param in 需要压缩的输入流,使用完后自动关闭
* @param path 压缩的路径 * @param path 压缩的路径
* @param out 压缩文件存储对象 * @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) { if (null == in) {
return; return;
} }
@@ -1079,7 +1109,7 @@ public class ZipUtil {
out.putNextEntry(new ZipEntry(path)); out.putNextEntry(new ZipEntry(path));
IoUtil.copy(in, out); IoUtil.copy(in, out);
} catch (IOException e) { } catch (IOException e) {
throw new UtilException(e); throw new IORuntimeException(e);
} finally { } finally {
IoUtil.close(in); IoUtil.close(in);
closeEntry(out); closeEntry(out);

View File

@@ -1,6 +1,8 @@
package cn.hutool.core.io.resource; package cn.hutool.core.io.resource;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
@@ -10,6 +12,11 @@ public class ResourceUtilTest {
public void readXmlTest(){ public void readXmlTest(){
final String str = ResourceUtil.readUtf8Str("test.xml"); final String str = ResourceUtil.readUtf8Str("test.xml");
Assert.assertNotNull(str); Assert.assertNotNull(str);
Resource resource = new ClassPathResource("test.xml");
final String xmlStr = resource.readUtf8Str();
Assert.assertEquals(str, xmlStr);
} }
@Test @Test
@@ -19,4 +26,11 @@ public class ResourceUtilTest {
Assert.assertArrayEquals("testData".getBytes(), stringResource.readBytes()); Assert.assertArrayEquals("testData".getBytes(), stringResource.readBytes());
Assert.assertArrayEquals("testData".getBytes(), IoUtil.readBytes(stringResource.getStream())); 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()));
}
} }

View File

@@ -2,7 +2,7 @@ package cn.hutool.setting;
import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil; 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.lang.Assert;
import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.CharsetUtil;
@@ -70,17 +70,17 @@ public class SettingLoader {
/** /**
* 加载设置文件 * 加载设置文件
* *
* @param urlResource 配置文件URL * @param resource 配置文件URL
* @return 加载是否成功 * @return 加载是否成功
*/ */
public boolean load(UrlResource urlResource) { public boolean load(Resource resource) {
if (urlResource == null) { if (resource == null) {
throw new NullPointerException("Null setting url define!"); throw new NullPointerException("Null setting url define!");
} }
log.debug("Load setting file [{}]", urlResource); log.debug("Load setting file [{}]", resource);
InputStream settingStream = null; InputStream settingStream = null;
try { try {
settingStream = urlResource.getStream(); settingStream = resource.getStream();
load(settingStream); load(settingStream);
} catch (Exception e) { } catch (Exception e) {
log.error(e, "Load setting error!"); log.error(e, "Load setting error!");

View File

@@ -1,6 +1,6 @@
package cn.hutool.setting; 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.io.resource.NoResourceException;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
@@ -27,22 +27,13 @@ public class SettingUtil {
* @return 当前环境下配置文件 * @return 当前环境下配置文件
*/ */
public static Setting get(String name) { public static Setting get(String name) {
Setting setting = SETTING_MAP.get(name); return SETTING_MAP.computeIfAbsent(name, (filePath)->{
if (null == setting) { final String extName = FileNameUtil.extName(filePath);
synchronized (SettingUtil.class) { if (StrUtil.isEmpty(extName)) {
setting = SETTING_MAP.get(name); filePath = filePath + "." + Setting.EXT_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 new Setting(filePath, true);
return setting; });
} }
/** /**

View File

@@ -21,7 +21,6 @@ import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.log.StaticLog; import cn.hutool.log.StaticLog;
import cn.hutool.setting.Setting;
import cn.hutool.setting.SettingRuntimeException; import cn.hutool.setting.SettingRuntimeException;
import java.io.BufferedReader; import java.io.BufferedReader;
@@ -239,7 +238,7 @@ public final class Props extends Properties implements BasicTypeGetter<String>,
if (null != charset) { if (null != charset) {
this.charset = charset; this.charset = charset;
} }
this.load(new UrlResource(propertiesUrl)); this.load(propertiesUrl);
} }
/** /**
@@ -257,16 +256,26 @@ public final class Props extends Properties implements BasicTypeGetter<String>,
/** /**
* 初始化配置文件 * 初始化配置文件
* *
* @param urlResource {@link UrlResource} * @param url {@link URL}
* @since 5.5.2
*/ */
public void load(Resource urlResource) { public void load(URL url) {
this.propertiesFileUrl = urlResource.getUrl(); load(new UrlResource(url));
}
/**
* 初始化配置文件
*
* @param resource {@link Resource}
*/
public void load(Resource resource) {
this.propertiesFileUrl = resource.getUrl();
if (null == this.propertiesFileUrl) { 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); super.load(reader);
} catch (IOException e) { } catch (IOException e) {
throw new IORuntimeException(e); throw new IORuntimeException(e);
@@ -277,7 +286,7 @@ public final class Props extends Properties implements BasicTypeGetter<String>,
* 重新加载配置文件 * 重新加载配置文件
*/ */
public void load() { public void load() {
this.load(new UrlResource(this.propertiesFileUrl)); this.load(this.propertiesFileUrl);
} }
/** /**

View File

@@ -20,7 +20,6 @@ public class PropsUtil {
* 配置文件缓存 * 配置文件缓存
*/ */
private static final Map<String, Props> propsMap = new ConcurrentHashMap<>(); private static final Map<String, Props> propsMap = new ConcurrentHashMap<>();
private static final Object lock = new Object();
/** /**
* 获取当前环境下的配置文件<br> * 获取当前环境下的配置文件<br>
@@ -30,22 +29,13 @@ public class PropsUtil {
* @return 当前环境下配置文件 * @return 当前环境下配置文件
*/ */
public static Props get(String name) { public static Props get(String name) {
Props props = propsMap.get(name); return propsMap.computeIfAbsent(name, (filePath)->{
if (null == props) { final String extName = FileUtil.extName(filePath);
synchronized (lock) { if (StrUtil.isEmpty(extName)) {
props = propsMap.get(name); filePath = filePath + "." + Props.EXT_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 new Props(filePath);
return props; });
} }
/** /**
@@ -66,4 +56,14 @@ public class PropsUtil {
} }
return null; return null;
} }
/**
* 获取系统参数例如用户在执行java命令时定义的 -Duse=hutool
*
* @return 系统参数Props
* @since 5.5.2
*/
public static Props getSystemProps(){
return new Props(System.getProperties());
}
} }