mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-08-18 20:38:02 +08:00
Merge remote-tracking branch 'upstream/v5-dev' into v5-dev
This commit is contained in:
13
CHANGELOG.md
13
CHANGELOG.md
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
-------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
# 5.5.2 (2020-11-25)
|
# 5.5.2 (2020-11-26)
|
||||||
|
|
||||||
### 新特性
|
### 新特性
|
||||||
* 【crypto 】 KeyUtil增加重载,AES构造增加重载(issue#I25NNZ@Gitee)
|
* 【crypto 】 KeyUtil增加重载,AES构造增加重载(issue#I25NNZ@Gitee)
|
||||||
@@ -13,9 +13,17 @@
|
|||||||
* 【crypto 】 增加判空(issue#1230@Github)
|
* 【crypto 】 增加判空(issue#1230@Github)
|
||||||
* 【core 】 xml.setXmlStandalone(true)格式优化(pr#1234@Github)
|
* 【core 】 xml.setXmlStandalone(true)格式优化(pr#1234@Github)
|
||||||
* 【core 】 AnnotationUtil增加setValue方法(pr#1250@Github)
|
* 【core 】 AnnotationUtil增加setValue方法(pr#1250@Github)
|
||||||
* 【core 】 ZipUtil增加get方法
|
* 【core 】 ZipUtil增加get方法(issue#I27CUF@Gitee)
|
||||||
* 【cache 】 对CacheObj等变量使用volatile关键字
|
* 【cache 】 对CacheObj等变量使用volatile关键字
|
||||||
* 【core 】 Base64增加encodeWithoutPadding方法(issue#I26J16@Gitee)
|
* 【core 】 Base64增加encodeWithoutPadding方法(issue#I26J16@Gitee)
|
||||||
|
* 【core 】 ExceptionUtil增加message消息包装为运行时异常的方法(pr#1253@Gitee)
|
||||||
|
* 【core 】 DatePattern增加年月格式化常量(pr#220@Gitee)
|
||||||
|
* 【core 】 ArrayUtil增加shuffle方法(pr#1255@Github)
|
||||||
|
* 【core 】 ArrayUtil部分方法分离至PrimitiveArrayUtil
|
||||||
|
* 【crypto 】 opt改为otp包(issue#1257@Github)
|
||||||
|
* 【cache 】 增加CacheListener(issue#1257@Github)
|
||||||
|
* 【core 】 TimeInterval支持分组(issue#1238@Github)
|
||||||
|
* 【core 】 增加compile包(pr#1243@Github)
|
||||||
|
|
||||||
### Bug修复
|
### Bug修复
|
||||||
* 【cron 】 修复CronTimer可能死循环的问题(issue#1224@Github)
|
* 【cron 】 修复CronTimer可能死循环的问题(issue#1224@Github)
|
||||||
@@ -23,6 +31,7 @@
|
|||||||
* 【poi 】 修复ExcelUtil.getSaxReader使用非MarkSupport流报错问题(issue#1225@Github)
|
* 【poi 】 修复ExcelUtil.getSaxReader使用非MarkSupport流报错问题(issue#1225@Github)
|
||||||
* 【core 】 修复HexUtil.format问题(issue#I268XT@Gitee)
|
* 【core 】 修复HexUtil.format问题(issue#I268XT@Gitee)
|
||||||
* 【core 】 修复ZipUtil判断压缩文件是否位于压缩目录内的逻辑有误的问题(issue#1251@Github)
|
* 【core 】 修复ZipUtil判断压缩文件是否位于压缩目录内的逻辑有误的问题(issue#1251@Github)
|
||||||
|
* 【json 】 修复JSONObject.accumulate问题
|
||||||
|
|
||||||
-------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ public interface Cache<K, V> extends Iterable<V>, Serializable {
|
|||||||
* @param key 键
|
* @param key 键
|
||||||
* @param object 缓存的对象
|
* @param object 缓存的对象
|
||||||
* @param timeout 失效时长,单位毫秒
|
* @param timeout 失效时长,单位毫秒
|
||||||
* @see Cache#put(Object, Object, long)
|
|
||||||
*/
|
*/
|
||||||
void put(K key, V object, long timeout);
|
void put(K key, V object, long timeout);
|
||||||
|
|
||||||
@@ -159,4 +158,15 @@ public interface Cache<K, V> extends Iterable<V>, Serializable {
|
|||||||
* @return 是否包含key
|
* @return 是否包含key
|
||||||
*/
|
*/
|
||||||
boolean containsKey(K key);
|
boolean containsKey(K key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置监听
|
||||||
|
*
|
||||||
|
* @param listener 监听
|
||||||
|
* @return this
|
||||||
|
* @since 5.5.2
|
||||||
|
*/
|
||||||
|
default Cache<K, V> setListener(CacheListener<K, V> listener){
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
20
hutool-cache/src/main/java/cn/hutool/cache/CacheListener.java
vendored
Normal file
20
hutool-cache/src/main/java/cn/hutool/cache/CacheListener.java
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package cn.hutool.cache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缓存监听,用于实现缓存操作时的回调监听,例如缓存对象的移除事件等
|
||||||
|
*
|
||||||
|
* @param <K> 缓存键
|
||||||
|
* @param <V> 缓存值
|
||||||
|
* @author looly
|
||||||
|
* @since 5.5.2
|
||||||
|
*/
|
||||||
|
public interface CacheListener<K, V> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对象移除回调
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param cachedObject 被缓存的对象
|
||||||
|
*/
|
||||||
|
void onRemove(K key, V cachedObject);
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package cn.hutool.cache.impl;
|
package cn.hutool.cache.impl;
|
||||||
|
|
||||||
import cn.hutool.cache.Cache;
|
import cn.hutool.cache.Cache;
|
||||||
|
import cn.hutool.cache.CacheListener;
|
||||||
import cn.hutool.core.collection.CopiedIter;
|
import cn.hutool.core.collection.CopiedIter;
|
||||||
import cn.hutool.core.lang.func.Func0;
|
import cn.hutool.core.lang.func.Func0;
|
||||||
|
|
||||||
@@ -45,11 +46,16 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
|||||||
/**
|
/**
|
||||||
* 命中数
|
* 命中数
|
||||||
*/
|
*/
|
||||||
protected AtomicLong hitCount;
|
protected AtomicLong hitCount = new AtomicLong();
|
||||||
/**
|
/**
|
||||||
* 丢失数
|
* 丢失数
|
||||||
*/
|
*/
|
||||||
protected AtomicLong missCount;
|
protected AtomicLong missCount = new AtomicLong();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缓存监听
|
||||||
|
*/
|
||||||
|
protected CacheListener<K, V> listener;
|
||||||
|
|
||||||
// ---------------------------------------------------------------- put start
|
// ---------------------------------------------------------------- put start
|
||||||
@Override
|
@Override
|
||||||
@@ -164,7 +170,7 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
|||||||
|
|
||||||
if (co.isExpired()) {
|
if (co.isExpired()) {
|
||||||
missCount.getAndIncrement();
|
missCount.getAndIncrement();
|
||||||
} else{
|
} else {
|
||||||
// 命中
|
// 命中
|
||||||
hitCount.getAndIncrement();
|
hitCount.getAndIncrement();
|
||||||
return co.get(isUpdateLastAccess);
|
return co.get(isUpdateLastAccess);
|
||||||
@@ -280,13 +286,29 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
|||||||
// ---------------------------------------------------------------- common end
|
// ---------------------------------------------------------------- common end
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 对象移除回调。默认无动作
|
* 设置监听
|
||||||
|
*
|
||||||
|
* @param listener 监听
|
||||||
|
* @return this
|
||||||
|
* @since 5.5.2
|
||||||
|
*/
|
||||||
|
public AbstractCache<K, V> setListener(CacheListener<K, V> listener) {
|
||||||
|
this.listener = listener;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对象移除回调。默认无动作<br>
|
||||||
|
* 子类可重写此方法用于监听移除事件,如果重写,listener将无效
|
||||||
*
|
*
|
||||||
* @param key 键
|
* @param key 键
|
||||||
* @param cachedObject 被缓存的对象
|
* @param cachedObject 被缓存的对象
|
||||||
*/
|
*/
|
||||||
protected void onRemove(K key, V cachedObject) {
|
protected void onRemove(K key, V cachedObject) {
|
||||||
// ignore
|
final CacheListener<K, V> listener = this.listener;
|
||||||
|
if (null != listener) {
|
||||||
|
listener.onRemove(key, cachedObject);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ public class CacheObj<K, V> implements Serializable{
|
|||||||
/** 上次访问时间 */
|
/** 上次访问时间 */
|
||||||
private volatile long lastAccess;
|
private volatile long lastAccess;
|
||||||
/** 访问次数 */
|
/** 访问次数 */
|
||||||
protected AtomicLong accessCount;
|
protected AtomicLong accessCount = new AtomicLong();
|
||||||
/** 对象存活时长,0表示永久存活*/
|
/** 对象存活时长,0表示永久存活*/
|
||||||
private final long ttl;
|
private final long ttl;
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,12 @@ public class CacheTest {
|
|||||||
@Test
|
@Test
|
||||||
public void fifoCacheTest(){
|
public void fifoCacheTest(){
|
||||||
Cache<String,String> fifoCache = CacheUtil.newFIFOCache(3);
|
Cache<String,String> fifoCache = CacheUtil.newFIFOCache(3);
|
||||||
|
fifoCache.setListener((key, value)->{
|
||||||
|
// 监听测试,此测试中只有key1被移除,测试是否监听成功
|
||||||
|
Assert.assertEquals("key1", key);
|
||||||
|
Assert.assertEquals("value1", value);
|
||||||
|
});
|
||||||
|
|
||||||
fifoCache.put("key1", "value1", DateUnit.SECOND.getMillis() * 3);
|
fifoCache.put("key1", "value1", DateUnit.SECOND.getMillis() * 3);
|
||||||
fifoCache.put("key2", "value2", DateUnit.SECOND.getMillis() * 3);
|
fifoCache.put("key2", "value2", DateUnit.SECOND.getMillis() * 3);
|
||||||
fifoCache.put("key3", "value3", DateUnit.SECOND.getMillis() * 3);
|
fifoCache.put("key3", "value3", DateUnit.SECOND.getMillis() * 3);
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
package cn.hutool.core.compiler;
|
package cn.hutool.core.compiler;
|
||||||
|
|
||||||
|
import cn.hutool.core.io.IORuntimeException;
|
||||||
import cn.hutool.core.io.IoUtil;
|
import cn.hutool.core.io.IoUtil;
|
||||||
|
import cn.hutool.core.util.ClassLoaderUtil;
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
|
||||||
import javax.tools.FileObject;
|
import javax.tools.FileObject;
|
||||||
import javax.tools.ForwardingJavaFileManager;
|
import javax.tools.ForwardingJavaFileManager;
|
||||||
import javax.tools.JavaFileManager;
|
import javax.tools.JavaFileManager;
|
||||||
import javax.tools.JavaFileObject;
|
import javax.tools.JavaFileObject;
|
||||||
import javax.tools.JavaFileObject.Kind;
|
import javax.tools.JavaFileObject.Kind;
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.security.SecureClassLoader;
|
import java.security.SecureClassLoader;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -18,88 +22,78 @@ import java.util.Map;
|
|||||||
* 我们采取此对象来管理运行时动态编译类生成的字节码
|
* 我们采取此对象来管理运行时动态编译类生成的字节码
|
||||||
*
|
*
|
||||||
* @author lzpeng
|
* @author lzpeng
|
||||||
* @see JavaSourceCompilerBak#compile()
|
* @since 5.5.2
|
||||||
* @see com.sun.tools.javac.api.ClientCodeWrapper.WrappedJavaFileManager#getJavaFileForOutput(javax.tools.JavaFileManager.Location, java.lang.String, javax.tools.JavaFileObject.Kind, javax.tools.FileObject)
|
|
||||||
*/
|
*/
|
||||||
final class JavaClassFileManager extends ForwardingJavaFileManager<JavaFileManager> {
|
class JavaClassFileManager extends ForwardingJavaFileManager<JavaFileManager> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 存储java字节码文件对象映射
|
* 存储java字节码文件对象映射
|
||||||
*/
|
*/
|
||||||
private final Map<String, JavaFileObject> javaFileObjectMap = new HashMap<>();
|
private final Map<String, JavaFileObject> javaFileObjectMap = new HashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加载动态编译生成类的父类加载器
|
* 加载动态编译生成类的父类加载器
|
||||||
*/
|
*/
|
||||||
private final ClassLoader parent;
|
private final ClassLoader parent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造
|
* 构造
|
||||||
*
|
*
|
||||||
* @param parent 父类加载器
|
* @param parent 父类加载器
|
||||||
* @param fileManager 字节码文件管理器
|
* @param fileManager 字节码文件管理器
|
||||||
* @see JavaSourceCompilerBak#compile()
|
*/
|
||||||
*/
|
protected JavaClassFileManager(ClassLoader parent, JavaFileManager fileManager) {
|
||||||
protected JavaClassFileManager(final ClassLoader parent, final JavaFileManager fileManager) {
|
super(fileManager);
|
||||||
super(fileManager);
|
this.parent = ObjectUtil.defaultIfNull(parent, ClassLoaderUtil.getClassLoader());
|
||||||
if (parent == null) {
|
}
|
||||||
this.parent = Thread.currentThread().getContextClassLoader();
|
|
||||||
} else {
|
|
||||||
this.parent = parent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获得动态编译生成的类的类加载器
|
* 获得动态编译生成的类的类加载器
|
||||||
*
|
*
|
||||||
* @param location 源码位置
|
* @param location 源码位置
|
||||||
* @return 动态编译生成的类的类加载器
|
* @return 动态编译生成的类的类加载器
|
||||||
* @see JavaSourceCompilerBak#compile()
|
*/
|
||||||
*/
|
@Override
|
||||||
@Override
|
public ClassLoader getClassLoader(final Location location) {
|
||||||
public ClassLoader getClassLoader(final Location location) {
|
return new SecureClassLoader(parent) {
|
||||||
return new SecureClassLoader(parent) {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查找类
|
* 查找类
|
||||||
* @param name 类名
|
* @param name 类名
|
||||||
* @return 类的class对象
|
* @return 类的class对象
|
||||||
* @throws ClassNotFoundException 未找到类异常
|
* @throws ClassNotFoundException 未找到类异常
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected Class<?> findClass(final String name) throws ClassNotFoundException {
|
protected Class<?> findClass(final String name) throws ClassNotFoundException {
|
||||||
final JavaFileObject javaFileObject = javaFileObjectMap.get(name);
|
final JavaFileObject javaFileObject = javaFileObjectMap.get(name);
|
||||||
if (javaFileObject != null) {
|
if (null != javaFileObject) {
|
||||||
try {
|
try(final InputStream inputStream = javaFileObject.openInputStream()){
|
||||||
final InputStream inputStream = javaFileObject.openInputStream();
|
final byte[] bytes = IoUtil.readBytes(inputStream);
|
||||||
final byte[] bytes = IoUtil.readBytes(inputStream);
|
return defineClass(name, bytes, 0, bytes.length);
|
||||||
final Class<?> c = defineClass(name, bytes, 0, bytes.length);
|
} catch (IOException e) {
|
||||||
return c;
|
throw new IORuntimeException(e);
|
||||||
} catch (Exception e) {
|
}
|
||||||
e.printStackTrace();
|
}
|
||||||
}
|
throw new ClassNotFoundException(name);
|
||||||
}
|
}
|
||||||
throw new ClassNotFoundException(name);
|
};
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获得Java字节码文件对象
|
* 获得Java字节码文件对象
|
||||||
* 编译器编译源码时会将Java源码对象编译转为Java字节码对象
|
* 编译器编译源码时会将Java源码对象编译转为Java字节码对象
|
||||||
*
|
*
|
||||||
* @param location 源码位置
|
* @param location 源码位置
|
||||||
* @param className 类名
|
* @param className 类名
|
||||||
* @param kind 文件类型
|
* @param kind 文件类型
|
||||||
* @param sibling 将Java源码对象
|
* @param sibling 将Java源码对象
|
||||||
* @return Java字节码文件对象
|
* @return Java字节码文件对象
|
||||||
* @see com.sun.tools.javac.api.lientCodeWrapper.WrappedJavaFileManager#getJavaFileForOutput(javax.tools.JavaFileManager.Location, java.lang.String, javax.tools.JavaFileObject.Kind, javax.tools.FileObject)
|
*/
|
||||||
*/
|
@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, kind);
|
javaFileObjectMap.put(className, javaFileObject);
|
||||||
javaFileObjectMap.put(className, javaFileObject);
|
return javaFileObject;
|
||||||
return javaFileObject;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,50 +14,48 @@ import java.net.URI;
|
|||||||
* @author lzpeng
|
* @author lzpeng
|
||||||
* @see JavaClassFileManager#getClassLoader(javax.tools.JavaFileManager.Location
|
* @see JavaClassFileManager#getClassLoader(javax.tools.JavaFileManager.Location
|
||||||
* @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)
|
||||||
* @see com.sun.tools.javac.jvm.ClassWriter.ClassWriter#writeClass(com.sun.tools.javac.code.Symbol.ClassSymbol)
|
* @since 5.5.2
|
||||||
*/
|
*/
|
||||||
final class JavaClassFileObject extends SimpleJavaFileObject {
|
final class JavaClassFileObject extends SimpleJavaFileObject {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 字节码输出流
|
* 字节码输出流
|
||||||
*/
|
*/
|
||||||
private final ByteArrayOutputStream byteArrayOutputStream;
|
private final ByteArrayOutputStream byteArrayOutputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造
|
* 构造
|
||||||
*
|
*
|
||||||
* @param className 需要编译的类名
|
* @param className 需要编译的类名
|
||||||
* @param kind 需要编译的文件类型
|
* @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(final String className, final Kind kind) {
|
||||||
super(URI.create("string:///" + className.replaceAll("\\.", "/") + kind.extension), kind);
|
super(URI.create("string:///" + className.replaceAll("\\.", "/") + kind.extension), kind);
|
||||||
this.byteArrayOutputStream = new ByteArrayOutputStream();
|
this.byteArrayOutputStream = new ByteArrayOutputStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获得字节码输入流
|
* 获得字节码输入流
|
||||||
* 编译器编辑源码后,我们将通过此输出流获得编译后的字节码,以便运行时加载类
|
* 编译器编辑源码后,我们将通过此输出流获得编译后的字节码,以便运行时加载类
|
||||||
*
|
*
|
||||||
* @return 字节码输入流
|
* @return 字节码输入流
|
||||||
* @see JavaClassFileManager#getClassLoader(javax.tools.JavaFileManager.Location)
|
* @see JavaClassFileManager#getClassLoader(javax.tools.JavaFileManager.Location)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public InputStream openInputStream() {
|
public InputStream openInputStream() {
|
||||||
final byte[] bytes = byteArrayOutputStream.toByteArray();
|
return new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
|
||||||
return new ByteArrayInputStream(bytes);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获得字节码输出流
|
* 获得字节码输出流
|
||||||
* 编译器编辑源码时,会将编译结果输出到本输出流中
|
* 编译器编辑源码时,会将编译结果输出到本输出流中
|
||||||
*
|
*
|
||||||
* @return 字节码输出流
|
* @return 字节码输出流
|
||||||
* @see com.sun.tools.javac.jvm.ClassWriter.ClassWriter#writeClass(com.sun.tools.javac.code.Symbol.ClassSymbol)
|
*/
|
||||||
*/
|
@Override
|
||||||
@Override
|
public OutputStream openOutputStream() {
|
||||||
public OutputStream openOutputStream() {
|
return this.byteArrayOutputStream;
|
||||||
return this.byteArrayOutputStream;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -3,17 +3,31 @@ package cn.hutool.core.compiler;
|
|||||||
import cn.hutool.core.io.FileUtil;
|
import cn.hutool.core.io.FileUtil;
|
||||||
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.ClassLoaderUtil;
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import cn.hutool.core.util.URLUtil;
|
import cn.hutool.core.util.URLUtil;
|
||||||
|
|
||||||
import javax.tools.*;
|
import javax.tools.DiagnosticCollector;
|
||||||
|
import javax.tools.JavaCompiler;
|
||||||
import javax.tools.JavaCompiler.CompilationTask;
|
import javax.tools.JavaCompiler.CompilationTask;
|
||||||
import javax.tools.JavaFileObject.Kind;
|
import javax.tools.JavaFileManager;
|
||||||
|
import javax.tools.JavaFileObject;
|
||||||
|
import javax.tools.StandardLocation;
|
||||||
|
import javax.tools.ToolProvider;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLClassLoader;
|
import java.net.URLClassLoader;
|
||||||
import java.util.*;
|
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.stream.Collectors;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipFile;
|
import java.util.zip.ZipFile;
|
||||||
@@ -23,270 +37,269 @@ import java.util.zip.ZipFile;
|
|||||||
*
|
*
|
||||||
* @author lzpeng
|
* @author lzpeng
|
||||||
*/
|
*/
|
||||||
public final class JavaSourceCompiler {
|
public class JavaSourceCompiler {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* java 编译器
|
* java 编译器
|
||||||
*/
|
*/
|
||||||
private static final JavaCompiler JAVA_COMPILER = ToolProvider.getSystemJavaCompiler();
|
private static final JavaCompiler JAVA_COMPILER = ToolProvider.getSystemJavaCompiler();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 待编译的文件 可以是 .java文件 压缩文件 文件夹 递归搜索文件夹内的zip包和jar包
|
* 待编译的文件 可以是 .java文件 压缩文件 文件夹 递归搜索文件夹内的zip包和jar包
|
||||||
*/
|
*/
|
||||||
private final List<File> sourceFileList = new ArrayList<>();
|
private final List<File> sourceFileList = new ArrayList<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 编译时需要加入classpath中的文件 可以是 压缩文件 文件夹递归搜索文件夹内的zip包和jar包
|
* 编译时需要加入classpath中的文件 可以是 压缩文件 文件夹递归搜索文件夹内的zip包和jar包
|
||||||
*/
|
*/
|
||||||
private final List<File> libraryFileList = new ArrayList<>();
|
private final List<File> libraryFileList = new ArrayList<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 源码映射 key: 类名 value: 类源码
|
* 源码映射 key: 类名 value: 类源码
|
||||||
*/
|
*/
|
||||||
private final Map<String, String> sourceCodeMap = new LinkedHashMap<>();
|
private final Map<String, String> sourceCodeMap = new LinkedHashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 编译类时使用的父类加载器
|
* 编译类时使用的父类加载器
|
||||||
*/
|
*/
|
||||||
private final ClassLoader parentClassLoader;
|
private final ClassLoader parentClassLoader;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造
|
* 构造
|
||||||
*
|
*
|
||||||
* @param parent 父类加载器
|
* @param parent 父类加载器
|
||||||
*/
|
*/
|
||||||
private JavaSourceCompiler(ClassLoader parent) {
|
private JavaSourceCompiler(ClassLoader parent) {
|
||||||
this.parentClassLoader = parent;
|
this.parentClassLoader = parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建Java源码编译器
|
* 创建Java源码编译器
|
||||||
*
|
*
|
||||||
* @param parent 父类加载器
|
* @param parent 父类加载器
|
||||||
* @return Java源码编译器
|
* @return Java源码编译器
|
||||||
*/
|
*/
|
||||||
public static JavaSourceCompiler create(ClassLoader parent) {
|
public static JavaSourceCompiler create(ClassLoader parent) {
|
||||||
return new JavaSourceCompiler(parent);
|
return new JavaSourceCompiler(parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 向编译器中加入待编译的文件 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包
|
* 向编译器中加入待编译的文件 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包
|
||||||
*
|
*
|
||||||
* @param files 待编译的文件 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包
|
* @param files 待编译的文件 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包
|
||||||
* @return Java源码编译器
|
* @return Java源码编译器
|
||||||
*/
|
*/
|
||||||
public JavaSourceCompiler addSource(final File... files) {
|
public JavaSourceCompiler addSource(final File... files) {
|
||||||
if (ArrayUtil.isNotEmpty(files)) {
|
if (ArrayUtil.isNotEmpty(files)) {
|
||||||
this.sourceFileList.addAll(Arrays.asList(files));
|
this.sourceFileList.addAll(Arrays.asList(files));
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 向编译器中加入待编译的源码Map
|
* 向编译器中加入待编译的源码Map
|
||||||
*
|
*
|
||||||
* @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(final Map<String, String> sourceCodeMap) {
|
||||||
if (MapUtil.isNotEmpty(sourceCodeMap)) {
|
if (MapUtil.isNotEmpty(sourceCodeMap)) {
|
||||||
this.sourceCodeMap.putAll(sourceCodeMap);
|
this.sourceCodeMap.putAll(sourceCodeMap);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加入编译Java源码时所需要的jar包
|
* 加入编译Java源码时所需要的jar包
|
||||||
*
|
*
|
||||||
* @param files 编译Java源码时所需要的jar包
|
* @param files 编译Java源码时所需要的jar包
|
||||||
* @return Java源码编译器
|
* @return Java源码编译器
|
||||||
*/
|
*/
|
||||||
public JavaSourceCompiler addLibrary(final File... files) {
|
public JavaSourceCompiler addLibrary(final File... files) {
|
||||||
if (ArrayUtil.isNotEmpty(files)) {
|
if (ArrayUtil.isNotEmpty(files)) {
|
||||||
this.libraryFileList.addAll(Arrays.asList(files));
|
this.libraryFileList.addAll(Arrays.asList(files));
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 向编译器中加入待编译的源码Map
|
* 向编译器中加入待编译的源码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(final String className, final String sourceCode) {
|
||||||
if (className != null && sourceCode != null) {
|
if (className != null && sourceCode != null) {
|
||||||
this.sourceCodeMap.put(className, sourceCode);
|
this.sourceCodeMap.put(className, sourceCode);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 编译所有文件并返回类加载器
|
* 编译所有文件并返回类加载器
|
||||||
*
|
*
|
||||||
* @return 类加载器
|
* @return 类加载器
|
||||||
*/
|
*/
|
||||||
public ClassLoader compile() {
|
public ClassLoader compile() {
|
||||||
final ClassLoader parent;
|
final ClassLoader parent = ObjectUtil.defaultIfNull(this.parentClassLoader, ClassLoaderUtil.getClassLoader());
|
||||||
if (this.parentClassLoader == null) {
|
|
||||||
parent = Thread.currentThread().getContextClassLoader();
|
|
||||||
} else {
|
|
||||||
parent = this.parentClassLoader;
|
|
||||||
}
|
|
||||||
// 获得classPath
|
|
||||||
final List<File> classPath = getClassPath();
|
|
||||||
final URL[] urLs = URLUtil.getURLs(classPath.toArray(new File[0]));
|
|
||||||
final URLClassLoader ucl = URLClassLoader.newInstance(urLs, parent);
|
|
||||||
if (sourceCodeMap.isEmpty() && sourceFileList.isEmpty()) {
|
|
||||||
// 没有需要编译的源码
|
|
||||||
return ucl;
|
|
||||||
}
|
|
||||||
// 没有需要编译的源码文件返回加载zip或jar包的类加载器
|
|
||||||
final Iterable<JavaFileObject> javaFileObjectList = getJavaFileObject();
|
|
||||||
// 创建编译器
|
|
||||||
final JavaFileManager standardJavaFileManager = JAVA_COMPILER.getStandardFileManager(null, null, null);
|
|
||||||
final JavaFileManager javaFileManager = new JavaClassFileManager(ucl, standardJavaFileManager);
|
|
||||||
final DiagnosticCollector<? super JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
|
|
||||||
final List<String> options = new ArrayList<>();
|
|
||||||
if (!classPath.isEmpty()) {
|
|
||||||
final List<String> cp = classPath.stream().map(File::getAbsolutePath).collect(Collectors.toList());
|
|
||||||
options.add("-cp");
|
|
||||||
options.addAll(cp);
|
|
||||||
}
|
|
||||||
// 编译文件
|
|
||||||
final CompilationTask task = JAVA_COMPILER.getTask(null, javaFileManager, diagnosticCollector,
|
|
||||||
options, null, javaFileObjectList);
|
|
||||||
final Boolean result = task.call();
|
|
||||||
if (Boolean.TRUE.equals(result)) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// 获得classPath
|
||||||
* 获得编译源码时需要的classpath
|
final List<File> classPath = getClassPath();
|
||||||
*
|
final URL[] urLs = URLUtil.getURLs(classPath.toArray(new File[0]));
|
||||||
* @return 编译源码时需要的classpath
|
final URLClassLoader ucl = URLClassLoader.newInstance(urLs, parent);
|
||||||
*/
|
if (sourceCodeMap.isEmpty() && sourceFileList.isEmpty()) {
|
||||||
private List<File> getClassPath() {
|
// 没有需要编译的源码
|
||||||
List<File> classPathFileList = new ArrayList<>();
|
return ucl;
|
||||||
for (File file : libraryFileList) {
|
}
|
||||||
List<File> jarOrZipFile = FileUtil.loopFiles(file, this::isJarOrZipFile);
|
// 没有需要编译的源码文件返回加载zip或jar包的类加载器
|
||||||
classPathFileList.addAll(jarOrZipFile);
|
final Iterable<JavaFileObject> javaFileObjectList = getJavaFileObject();
|
||||||
if (file.isDirectory()) {
|
|
||||||
classPathFileList.add(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return classPathFileList;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// 创建编译器
|
||||||
* 获得待编译的Java文件对象
|
final JavaFileManager standardJavaFileManager = JAVA_COMPILER.getStandardFileManager(null, null, null);
|
||||||
*
|
final JavaFileManager javaFileManager = new JavaClassFileManager(ucl, standardJavaFileManager);
|
||||||
* @return 待编译的Java文件对象
|
|
||||||
*/
|
|
||||||
private Iterable<JavaFileObject> getJavaFileObject() {
|
|
||||||
final Collection<JavaFileObject> collection = new ArrayList<>();
|
|
||||||
for (File file : sourceFileList) {
|
|
||||||
// .java 文件
|
|
||||||
final List<File> javaFileList = FileUtil.loopFiles(file, this::isJavaFile);
|
|
||||||
for (File javaFile : javaFileList) {
|
|
||||||
collection.add(getJavaFileObjectByJavaFile(javaFile));
|
|
||||||
}
|
|
||||||
// 压缩包
|
|
||||||
final List<File> jarOrZipFileList = FileUtil.loopFiles(file, this::isJarOrZipFile);
|
|
||||||
for (File jarOrZipFile : jarOrZipFileList) {
|
|
||||||
collection.addAll(getJavaFileObjectByZipOrJarFile(jarOrZipFile));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 源码Map
|
|
||||||
collection.addAll(getJavaFileObjectByMap(this.sourceCodeMap));
|
|
||||||
return collection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// classpath
|
||||||
* 通过源码Map获得Java文件对象
|
final List<String> options = new ArrayList<>();
|
||||||
*
|
if (false == classPath.isEmpty()) {
|
||||||
* @param sourceCodeMap 源码Map
|
final List<String> cp = classPath.stream().map(File::getAbsolutePath).collect(Collectors.toList());
|
||||||
* @return Java文件对象集合
|
options.add("-cp");
|
||||||
*/
|
options.addAll(cp);
|
||||||
private Collection<JavaFileObject> getJavaFileObjectByMap(final Map<String, String> sourceCodeMap) {
|
}
|
||||||
if (MapUtil.isNotEmpty(sourceCodeMap)) {
|
|
||||||
return sourceCodeMap.entrySet().stream()
|
|
||||||
.map(entry -> new JavaSourceFileObject(entry.getKey(), entry.getValue(), Kind.SOURCE))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
return Collections.emptySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// 编译文件
|
||||||
* 通过.java文件创建Java文件对象
|
final DiagnosticCollector<? super JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
|
||||||
*
|
final CompilationTask task = JAVA_COMPILER.getTask(null, javaFileManager, diagnosticCollector,
|
||||||
* @param file .java文件
|
options, null, javaFileObjectList);
|
||||||
* @return Java文件对象
|
if (task.call()) {
|
||||||
*/
|
return javaFileManager.getClassLoader(StandardLocation.CLASS_OUTPUT);
|
||||||
private JavaFileObject getJavaFileObjectByJavaFile(final File file) {
|
} else {
|
||||||
return new JavaSourceFileObject(file.toURI(), Kind.SOURCE);
|
// 编译失败,收集错误信息
|
||||||
}
|
final List<?> diagnostics = diagnosticCollector.getDiagnostics();
|
||||||
|
final String errorMsg = diagnostics.stream().map(String::valueOf)
|
||||||
|
.collect(Collectors.joining(System.lineSeparator()));
|
||||||
|
// CompileException
|
||||||
|
throw new RuntimeException(errorMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通过zip包或jar包创建Java文件对象
|
* 获得编译源码时需要的classpath
|
||||||
*
|
*
|
||||||
* @param file 压缩文件
|
* @return 编译源码时需要的classpath
|
||||||
* @return Java文件对象
|
*/
|
||||||
*/
|
private List<File> getClassPath() {
|
||||||
private Collection<JavaFileObject> getJavaFileObjectByZipOrJarFile(final File file) {
|
List<File> classPathFileList = new ArrayList<>();
|
||||||
final Collection<JavaFileObject> collection = new ArrayList<>();
|
for (File file : libraryFileList) {
|
||||||
try {
|
List<File> jarOrZipFile = FileUtil.loopFiles(file, this::isJarOrZipFile);
|
||||||
final ZipFile zipFile = new ZipFile(file);
|
classPathFileList.addAll(jarOrZipFile);
|
||||||
final Enumeration<? extends ZipEntry> entries = zipFile.entries();
|
if (file.isDirectory()) {
|
||||||
while (entries.hasMoreElements()) {
|
classPathFileList.add(file);
|
||||||
final ZipEntry zipEntry = entries.nextElement();
|
}
|
||||||
final String name = zipEntry.getName();
|
}
|
||||||
if (name.endsWith(".java")) {
|
return classPathFileList;
|
||||||
final InputStream inputStream = zipFile.getInputStream(zipEntry);
|
}
|
||||||
final JavaSourceFileObject fileObject = new JavaSourceFileObject(name, inputStream, Kind.SOURCE);
|
|
||||||
collection.add(fileObject);
|
/**
|
||||||
}
|
* 获得待编译的Java文件对象
|
||||||
}
|
*
|
||||||
return collection;
|
* @return 待编译的Java文件对象
|
||||||
} catch (IOException e) {
|
*/
|
||||||
e.printStackTrace();
|
private Iterable<JavaFileObject> getJavaFileObject() {
|
||||||
}
|
final Collection<JavaFileObject> collection = new ArrayList<>();
|
||||||
return Collections.emptyList();
|
for (File file : sourceFileList) {
|
||||||
}
|
// .java 文件
|
||||||
|
final List<File> javaFileList = FileUtil.loopFiles(file, this::isJavaFile);
|
||||||
|
for (File javaFile : javaFileList) {
|
||||||
|
collection.add(getJavaFileObjectByJavaFile(javaFile));
|
||||||
|
}
|
||||||
|
// 压缩包
|
||||||
|
final List<File> jarOrZipFileList = FileUtil.loopFiles(file, this::isJarOrZipFile);
|
||||||
|
for (File jarOrZipFile : jarOrZipFileList) {
|
||||||
|
collection.addAll(getJavaFileObjectByZipOrJarFile(jarOrZipFile));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 源码Map
|
||||||
|
collection.addAll(getJavaFileObjectByMap(this.sourceCodeMap));
|
||||||
|
return collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过源码Map获得Java文件对象
|
||||||
|
*
|
||||||
|
* @param sourceCodeMap 源码Map
|
||||||
|
* @return Java文件对象集合
|
||||||
|
*/
|
||||||
|
private Collection<JavaFileObject> getJavaFileObjectByMap(final Map<String, String> sourceCodeMap) {
|
||||||
|
if (MapUtil.isNotEmpty(sourceCodeMap)) {
|
||||||
|
return sourceCodeMap.entrySet().stream()
|
||||||
|
.map(entry -> new JavaSourceFileObject(entry.getKey(), entry.getValue(), CharsetUtil.CHARSET_UTF_8))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过.java文件创建Java文件对象
|
||||||
|
*
|
||||||
|
* @param file .java文件
|
||||||
|
* @return Java文件对象
|
||||||
|
*/
|
||||||
|
private JavaFileObject getJavaFileObjectByJavaFile(final File file) {
|
||||||
|
return new JavaSourceFileObject(file.toURI());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过zip包或jar包创建Java文件对象
|
||||||
|
*
|
||||||
|
* @param file 压缩文件
|
||||||
|
* @return Java文件对象
|
||||||
|
*/
|
||||||
|
private Collection<JavaFileObject> getJavaFileObjectByZipOrJarFile(final File file) {
|
||||||
|
final Collection<JavaFileObject> collection = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
final ZipFile zipFile = new ZipFile(file);
|
||||||
|
final Enumeration<? extends ZipEntry> 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 文件
|
* 是否是jar 或 zip 文件
|
||||||
*
|
*
|
||||||
* @param file 文件
|
* @param file 文件
|
||||||
* @return 是否是jar 或 zip 文件
|
* @return 是否是jar 或 zip 文件
|
||||||
*/
|
*/
|
||||||
private boolean isJarOrZipFile(final File file) {
|
private boolean isJarOrZipFile(final File file) {
|
||||||
final String fileName = file.getName();
|
final String fileName = file.getName();
|
||||||
return fileName.endsWith(".jar") || fileName.endsWith(".zip");
|
return fileName.endsWith(".jar") || fileName.endsWith(".zip");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否是.java文件
|
* 是否是.java文件
|
||||||
*
|
*
|
||||||
* @param file 文件
|
* @param file 文件
|
||||||
* @return 是否是.java文件
|
* @return 是否是.java文件
|
||||||
*/
|
*/
|
||||||
private boolean isJavaFile(final File file) {
|
private boolean isJavaFile(final File file) {
|
||||||
final String fileName = file.getName();
|
final String fileName = file.getName();
|
||||||
return fileName.endsWith(".java");
|
return fileName.endsWith(".java");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,98 +1,85 @@
|
|||||||
package cn.hutool.core.compiler;
|
package cn.hutool.core.compiler;
|
||||||
|
|
||||||
|
import cn.hutool.core.io.IoUtil;
|
||||||
|
|
||||||
|
import javax.tools.SimpleJavaFileObject;
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
|
||||||
import javax.tools.SimpleJavaFileObject;
|
|
||||||
|
|
||||||
import cn.hutool.core.io.IoUtil;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Java 源码文件对象
|
* Java 源码文件对象
|
||||||
*
|
*
|
||||||
* @author lzpeng
|
* @author lzpeng
|
||||||
* @see JavaSourceCompilerBak#getJavaFileObjectByJavaFile(java.io.File)
|
* @since 5.5.2
|
||||||
* @see JavaSourceCompilerBak#getJavaFileObjectByZipOrJarFile(java.io.File)
|
|
||||||
* @see JavaSourceCompilerBak#getJavaFileObject(java.util.Map)
|
|
||||||
* @see com.sun.tools.javac.api.ClientCodeWrapper.WrappedFileObject#getCharContent(boolean)
|
|
||||||
*/
|
*/
|
||||||
final class JavaSourceFileObject extends SimpleJavaFileObject {
|
class JavaSourceFileObject extends SimpleJavaFileObject {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 输入流
|
* 输入流
|
||||||
*/
|
*/
|
||||||
private InputStream inputStream;
|
private InputStream inputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造
|
* 构造
|
||||||
*
|
*
|
||||||
* @param uri 需要编译的文件uri
|
* @param uri 需要编译的文件uri
|
||||||
* @param kind 需要编译的文件类型
|
*/
|
||||||
* @see JavaSourceCompilerBak#getJavaFileObjectByJavaFile(java.io.File)
|
protected JavaSourceFileObject(URI uri) {
|
||||||
*/
|
super(uri, Kind.SOURCE);
|
||||||
protected JavaSourceFileObject(URI uri, Kind kind) {
|
}
|
||||||
super(uri, kind);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造
|
* 构造
|
||||||
*
|
*
|
||||||
* @param name 需要编译的文件名
|
* @param name 需要编译的文件名
|
||||||
* @param inputStream 输入流
|
* @param inputStream 输入流
|
||||||
* @param kind 需要编译的文件类型
|
*/
|
||||||
* @see JavaSourceCompilerBak#getJavaFileObjectByZipOrJarFile(java.io.File)
|
protected JavaSourceFileObject(String name, InputStream inputStream) {
|
||||||
*/
|
this(URI.create("string:///" + name));
|
||||||
protected JavaSourceFileObject(final String name, final InputStream inputStream, final Kind kind) {
|
this.inputStream = inputStream;
|
||||||
super(URI.create("string:///" + name), kind);
|
}
|
||||||
this.inputStream = inputStream;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造
|
* 构造
|
||||||
*
|
*
|
||||||
* @param className 需要编译的类名
|
* @param className 需要编译的类名
|
||||||
* @param code 需要编译的类源码
|
* @param code 需要编译的类源码
|
||||||
* @param kind 需要编译的文件类型
|
*/
|
||||||
* @see JavaSourceCompilerBak#getJavaFileObject(java.util.Map)
|
protected JavaSourceFileObject(String className, String code, Charset charset) {
|
||||||
*/
|
this(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension));
|
||||||
protected JavaSourceFileObject(final String className, final String code, final Kind kind) {
|
this.inputStream = IoUtil.toStream(code, charset);
|
||||||
super(URI.create("string:///" + className.replaceAll("\\.", "/") + kind.extension), kind);
|
}
|
||||||
this.inputStream = new ByteArrayInputStream(code.getBytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获得类源码的输入流
|
* 获得类源码的输入流
|
||||||
*
|
*
|
||||||
* @return 类源码的输入流
|
* @return 类源码的输入流
|
||||||
* @throws IOException IO 异常
|
* @throws IOException IO 异常
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public InputStream openInputStream() throws IOException {
|
public InputStream openInputStream() throws IOException {
|
||||||
if (inputStream == null) {
|
if (inputStream == null) {
|
||||||
inputStream = toUri().toURL().openStream();
|
inputStream = toUri().toURL().openStream();
|
||||||
}
|
}
|
||||||
return new BufferedInputStream(inputStream);
|
return new BufferedInputStream(inputStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获得类源码
|
* 获得类源码
|
||||||
* 编译器编辑源码前,会通过此方法获取类的源码
|
* 编译器编辑源码前,会通过此方法获取类的源码
|
||||||
*
|
*
|
||||||
* @param ignoreEncodingErrors 是否忽略编码错误
|
* @param ignoreEncodingErrors 是否忽略编码错误
|
||||||
* @return 需要编译的类的源码
|
* @return 需要编译的类的源码
|
||||||
* @throws IOException IO异常
|
* @throws IOException IO异常
|
||||||
* @see com.sun.tools.javac.api.ClientCodeWrapper.WrappedFileObject#getCharContent(boolean)
|
*/
|
||||||
*/
|
@Override
|
||||||
@Override
|
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
|
||||||
public CharSequence getCharContent(final boolean ignoreEncodingErrors) throws IOException {
|
try(final InputStream in = openInputStream()){
|
||||||
final InputStream in = openInputStream();
|
return IoUtil.readUtf8(in);
|
||||||
final String code = IoUtil.read(in, Charset.defaultCharset());
|
}
|
||||||
IoUtil.close(in);
|
}
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -28,6 +28,32 @@ public class DatePattern {
|
|||||||
public static final Pattern REGEX_NORM = Pattern.compile("\\d{4}-\\d{1,2}-\\d{1,2}(\\s\\d{1,2}:\\d{1,2}(:\\d{1,2})?)?(.\\d{1,3})?");
|
public static final Pattern REGEX_NORM = Pattern.compile("\\d{4}-\\d{1,2}-\\d{1,2}(\\s\\d{1,2}:\\d{1,2}(:\\d{1,2})?)?(.\\d{1,3})?");
|
||||||
|
|
||||||
//-------------------------------------------------------------------------------------------------------------------------------- Normal
|
//-------------------------------------------------------------------------------------------------------------------------------- Normal
|
||||||
|
/**
|
||||||
|
* 年月格式:yyyy-MM
|
||||||
|
*/
|
||||||
|
public static final String NORM_MONTH_PATTERN = "yyyy-MM";
|
||||||
|
/**
|
||||||
|
* 年月格式 {@link FastDateFormat}:yyyy-MM
|
||||||
|
*/
|
||||||
|
public static final FastDateFormat NORM_MONTH_FORMAT = FastDateFormat.getInstance(NORM_MONTH_PATTERN);
|
||||||
|
/**
|
||||||
|
* 年月格式 {@link FastDateFormat}:yyyy-MM
|
||||||
|
*/
|
||||||
|
public static final DateTimeFormatter NORM_MONTH_FORMATTER = DateTimeFormatter.ofPattern(NORM_MONTH_PATTERN);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简单年月格式:yyyyMM
|
||||||
|
*/
|
||||||
|
public static final String SIMPLE_MONTH_PATTERN = "yyyyMM";
|
||||||
|
/**
|
||||||
|
* 简单年月格式 {@link FastDateFormat}:yyyyMM
|
||||||
|
*/
|
||||||
|
public static final FastDateFormat SIMPLE_MONTH_FORMAT = FastDateFormat.getInstance(SIMPLE_MONTH_PATTERN);
|
||||||
|
/**
|
||||||
|
* 简单年月格式 {@link FastDateFormat}:yyyyMM
|
||||||
|
*/
|
||||||
|
public static final DateTimeFormatter SIMPLE_MONTH_FORMATTER = DateTimeFormatter.ofPattern(SIMPLE_MONTH_PATTERN);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 标准日期格式:yyyy-MM-dd
|
* 标准日期格式:yyyy-MM-dd
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,177 @@
|
|||||||
|
package cn.hutool.core.date;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分组计时器<br>
|
||||||
|
* 计算某几个过程花费的时间,精确到毫秒或纳秒
|
||||||
|
*
|
||||||
|
* @author Looly
|
||||||
|
* @since 5.5.2
|
||||||
|
*/
|
||||||
|
public class GroupTimeInterval implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private final boolean isNano;
|
||||||
|
protected final Map<String, Long> groupMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
*
|
||||||
|
* @param isNano 是否使用纳秒计数,false则使用毫秒
|
||||||
|
*/
|
||||||
|
public GroupTimeInterval(boolean isNano) {
|
||||||
|
this.isNano = isNano;
|
||||||
|
groupMap = new ConcurrentHashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空所有定时记录
|
||||||
|
*
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public GroupTimeInterval clear(){
|
||||||
|
this.groupMap.clear();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始计时并返回当前时间
|
||||||
|
*
|
||||||
|
* @param id 分组ID
|
||||||
|
* @return 开始计时并返回当前时间
|
||||||
|
*/
|
||||||
|
public long start(String id) {
|
||||||
|
final long time = getTime();
|
||||||
|
this.groupMap.put(id, time);
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新计时并返回从开始到当前的持续时间秒<br>
|
||||||
|
* 如果此分组下没有记录,则返回0;
|
||||||
|
*
|
||||||
|
* @param id 分组ID
|
||||||
|
* @return 重新计时并返回从开始到当前的持续时间
|
||||||
|
*/
|
||||||
|
public long intervalRestart(String id) {
|
||||||
|
final long now = getTime();
|
||||||
|
return now - ObjectUtil.defaultIfNull(this.groupMap.put(id, now), now);
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------- Interval
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从开始到当前的间隔时间(毫秒数)<br>
|
||||||
|
* 如果使用纳秒计时,返回纳秒差,否则返回毫秒差<br>
|
||||||
|
* 如果分组下没有开始时间,返回{@code null}
|
||||||
|
*
|
||||||
|
* @param id 分组ID
|
||||||
|
* @return 从开始到当前的间隔时间(毫秒数)
|
||||||
|
*/
|
||||||
|
public long interval(String id) {
|
||||||
|
final Long lastTime = this.groupMap.get(id);
|
||||||
|
if (null == lastTime) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return getTime() - lastTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从开始到当前的间隔时间
|
||||||
|
*
|
||||||
|
* @param id 分组ID
|
||||||
|
* @param dateUnit 时间单位
|
||||||
|
* @return 从开始到当前的间隔时间(毫秒数)
|
||||||
|
*/
|
||||||
|
public long interval(String id, DateUnit dateUnit) {
|
||||||
|
final long intervalMs = isNano ? interval(id) / 1000000L : interval(id);
|
||||||
|
if (DateUnit.MS == dateUnit) {
|
||||||
|
return intervalMs;
|
||||||
|
}
|
||||||
|
return intervalMs / dateUnit.getMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从开始到当前的间隔时间(毫秒数)
|
||||||
|
*
|
||||||
|
* @param id 分组ID
|
||||||
|
* @return 从开始到当前的间隔时间(毫秒数)
|
||||||
|
*/
|
||||||
|
public long intervalMs(String id) {
|
||||||
|
return interval(id, DateUnit.MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从开始到当前的间隔秒数,取绝对值
|
||||||
|
*
|
||||||
|
* @param id 分组ID
|
||||||
|
* @return 从开始到当前的间隔秒数,取绝对值
|
||||||
|
*/
|
||||||
|
public long intervalSecond(String id) {
|
||||||
|
return interval(id, DateUnit.SECOND);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从开始到当前的间隔分钟数,取绝对值
|
||||||
|
*
|
||||||
|
* @param id 分组ID
|
||||||
|
* @return 从开始到当前的间隔分钟数,取绝对值
|
||||||
|
*/
|
||||||
|
public long intervalMinute(String id) {
|
||||||
|
return interval(id, DateUnit.MINUTE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从开始到当前的间隔小时数,取绝对值
|
||||||
|
*
|
||||||
|
* @param id 分组ID
|
||||||
|
* @return 从开始到当前的间隔小时数,取绝对值
|
||||||
|
*/
|
||||||
|
public long intervalHour(String id) {
|
||||||
|
return interval(id, DateUnit.HOUR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从开始到当前的间隔天数,取绝对值
|
||||||
|
*
|
||||||
|
* @param id 分组ID
|
||||||
|
* @return 从开始到当前的间隔天数,取绝对值
|
||||||
|
*/
|
||||||
|
public long intervalDay(String id) {
|
||||||
|
return interval(id, DateUnit.DAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从开始到当前的间隔周数,取绝对值
|
||||||
|
*
|
||||||
|
* @param id 分组ID
|
||||||
|
* @return 从开始到当前的间隔周数,取绝对值
|
||||||
|
*/
|
||||||
|
public long intervalWeek(String id) {
|
||||||
|
return interval(id, DateUnit.WEEK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从开始到当前的间隔时间(毫秒数),返回XX天XX小时XX分XX秒XX毫秒
|
||||||
|
*
|
||||||
|
* @param id 分组ID
|
||||||
|
* @return 从开始到当前的间隔时间(毫秒数)
|
||||||
|
*/
|
||||||
|
public String intervalPretty(String id) {
|
||||||
|
return DateUtil.formatBetween(intervalMs(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取时间的毫秒或纳秒数,纳秒非时间戳
|
||||||
|
*
|
||||||
|
* @return 时间
|
||||||
|
*/
|
||||||
|
private long getTime() {
|
||||||
|
return this.isNano ? System.nanoTime() : System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,6 +41,17 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public class StopWatch {
|
public class StopWatch {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建计时任务(秒表)
|
||||||
|
*
|
||||||
|
* @param id 用于标识秒表的唯一ID
|
||||||
|
* @return StopWatch
|
||||||
|
* @since 5.5.2
|
||||||
|
*/
|
||||||
|
public static StopWatch create(String id){
|
||||||
|
return new StopWatch(id);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 秒表唯一标识,用于多个秒表对象的区分
|
* 秒表唯一标识,用于多个秒表对象的区分
|
||||||
*/
|
*/
|
||||||
@@ -101,7 +112,7 @@ public class StopWatch {
|
|||||||
// ------------------------------------------------------------------------------------------- Constructor end
|
// ------------------------------------------------------------------------------------------- Constructor end
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取{@link StopWatch} 的ID,用于多个秒表对象的区分
|
* 获取StopWatch 的ID,用于多个秒表对象的区分
|
||||||
*
|
*
|
||||||
* @return the ID 空字符串为
|
* @return the ID 空字符串为
|
||||||
* @see #StopWatch(String)
|
* @see #StopWatch(String)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package cn.hutool.core.date;
|
package cn.hutool.core.date;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 计时器<br>
|
* 计时器<br>
|
||||||
@@ -8,11 +8,9 @@ import java.io.Serializable;
|
|||||||
*
|
*
|
||||||
* @author Looly
|
* @author Looly
|
||||||
*/
|
*/
|
||||||
public class TimeInterval implements Serializable {
|
public class TimeInterval extends GroupTimeInterval {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
private static final String DEFAULT_ID = StrUtil.EMPTY;
|
||||||
private long time;
|
|
||||||
private final boolean isNano;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造,默认使用毫秒计数
|
* 构造,默认使用毫秒计数
|
||||||
@@ -23,10 +21,11 @@ public class TimeInterval implements Serializable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造
|
* 构造
|
||||||
|
*
|
||||||
* @param isNano 是否使用纳秒计数,false则使用毫秒
|
* @param isNano 是否使用纳秒计数,false则使用毫秒
|
||||||
*/
|
*/
|
||||||
public TimeInterval(boolean isNano) {
|
public TimeInterval(boolean isNano) {
|
||||||
this.isNano = isNano;
|
super(isNano);
|
||||||
start();
|
start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,28 +33,25 @@ public class TimeInterval implements Serializable {
|
|||||||
* @return 开始计时并返回当前时间
|
* @return 开始计时并返回当前时间
|
||||||
*/
|
*/
|
||||||
public long start() {
|
public long start() {
|
||||||
time = getTime(isNano);
|
return start(DEFAULT_ID);
|
||||||
return time;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return 重新计时并返回从开始到当前的持续时间
|
* @return 重新计时并返回从开始到当前的持续时间
|
||||||
*/
|
*/
|
||||||
public long intervalRestart() {
|
public long intervalRestart() {
|
||||||
long now = getTime(isNano);
|
return intervalRestart(DEFAULT_ID);
|
||||||
long d = now - time;
|
|
||||||
time = now;
|
|
||||||
return d;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重新开始计算时间(重置开始时间)
|
* 重新开始计算时间(重置开始时间)
|
||||||
*
|
*
|
||||||
* @return this
|
* @return this
|
||||||
|
* @see #start()
|
||||||
* @since 3.0.1
|
* @since 3.0.1
|
||||||
*/
|
*/
|
||||||
public TimeInterval restart() {
|
public TimeInterval restart() {
|
||||||
time = getTime(isNano);
|
start(DEFAULT_ID);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +64,7 @@ public class TimeInterval implements Serializable {
|
|||||||
* @return 从开始到当前的间隔时间(毫秒数)
|
* @return 从开始到当前的间隔时间(毫秒数)
|
||||||
*/
|
*/
|
||||||
public long interval() {
|
public long interval() {
|
||||||
return getTime(isNano) - time;
|
return interval(DEFAULT_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -78,7 +74,7 @@ public class TimeInterval implements Serializable {
|
|||||||
* @since 4.6.7
|
* @since 4.6.7
|
||||||
*/
|
*/
|
||||||
public String intervalPretty() {
|
public String intervalPretty() {
|
||||||
return DateUtil.formatBetween(intervalMs());
|
return intervalPretty(DEFAULT_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -87,7 +83,7 @@ public class TimeInterval implements Serializable {
|
|||||||
* @return 从开始到当前的间隔时间(毫秒数)
|
* @return 从开始到当前的间隔时间(毫秒数)
|
||||||
*/
|
*/
|
||||||
public long intervalMs() {
|
public long intervalMs() {
|
||||||
return isNano ? interval() / 1000000L : interval();
|
return intervalMs(DEFAULT_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -96,7 +92,7 @@ public class TimeInterval implements Serializable {
|
|||||||
* @return 从开始到当前的间隔秒数,取绝对值
|
* @return 从开始到当前的间隔秒数,取绝对值
|
||||||
*/
|
*/
|
||||||
public long intervalSecond() {
|
public long intervalSecond() {
|
||||||
return intervalMs() / DateUnit.SECOND.getMillis();
|
return intervalSecond(DEFAULT_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -105,7 +101,7 @@ public class TimeInterval implements Serializable {
|
|||||||
* @return 从开始到当前的间隔分钟数,取绝对值
|
* @return 从开始到当前的间隔分钟数,取绝对值
|
||||||
*/
|
*/
|
||||||
public long intervalMinute() {
|
public long intervalMinute() {
|
||||||
return intervalMs() / DateUnit.MINUTE.getMillis();
|
return intervalMinute(DEFAULT_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -114,7 +110,7 @@ public class TimeInterval implements Serializable {
|
|||||||
* @return 从开始到当前的间隔小时数,取绝对值
|
* @return 从开始到当前的间隔小时数,取绝对值
|
||||||
*/
|
*/
|
||||||
public long intervalHour() {
|
public long intervalHour() {
|
||||||
return intervalMs() / DateUnit.HOUR.getMillis();
|
return intervalHour(DEFAULT_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -123,7 +119,7 @@ public class TimeInterval implements Serializable {
|
|||||||
* @return 从开始到当前的间隔天数,取绝对值
|
* @return 从开始到当前的间隔天数,取绝对值
|
||||||
*/
|
*/
|
||||||
public long intervalDay() {
|
public long intervalDay() {
|
||||||
return intervalMs() / DateUnit.DAY.getMillis();
|
return intervalDay(DEFAULT_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -132,16 +128,6 @@ public class TimeInterval implements Serializable {
|
|||||||
* @return 从开始到当前的间隔周数,取绝对值
|
* @return 从开始到当前的间隔周数,取绝对值
|
||||||
*/
|
*/
|
||||||
public long intervalWeek() {
|
public long intervalWeek() {
|
||||||
return intervalMs() / DateUnit.WEEK.getMillis();
|
return intervalWeek(DEFAULT_ID);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取时间的毫秒或纳秒数,纳秒非时间戳
|
|
||||||
*
|
|
||||||
* @param isNano 是否为高精度时间
|
|
||||||
* @return 时间
|
|
||||||
*/
|
|
||||||
private static long getTime(boolean isNano) {
|
|
||||||
return isNano ? System.nanoTime() : System.currentTimeMillis();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,15 +16,14 @@ import java.util.Map;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 异常工具类
|
* 异常工具类
|
||||||
*
|
|
||||||
* @author Looly
|
|
||||||
*
|
*
|
||||||
|
* @author Looly
|
||||||
*/
|
*/
|
||||||
public class ExceptionUtil {
|
public class ExceptionUtil {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获得完整消息,包括异常名,消息格式为:{SimpleClassName}: {ThrowableMessage}
|
* 获得完整消息,包括异常名,消息格式为:{SimpleClassName}: {ThrowableMessage}
|
||||||
*
|
*
|
||||||
* @param e 异常
|
* @param e 异常
|
||||||
* @return 完整消息
|
* @return 完整消息
|
||||||
*/
|
*/
|
||||||
@@ -37,7 +36,7 @@ public class ExceptionUtil {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获得消息,调用异常类的getMessage方法
|
* 获得消息,调用异常类的getMessage方法
|
||||||
*
|
*
|
||||||
* @param e 异常
|
* @param e 异常
|
||||||
* @return 消息
|
* @return 消息
|
||||||
*/
|
*/
|
||||||
@@ -47,9 +46,9 @@ public class ExceptionUtil {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 使用运行时异常包装编译异常<br>
|
* 使用运行时异常包装编译异常<br>
|
||||||
*
|
* <p>
|
||||||
* 如果
|
* 如果传入参数已经是运行时异常,则直接返回,不再额外包装
|
||||||
*
|
*
|
||||||
* @param throwable 异常
|
* @param throwable 异常
|
||||||
* @return 运行时异常
|
* @return 运行时异常
|
||||||
*/
|
*/
|
||||||
@@ -60,11 +59,22 @@ public class ExceptionUtil {
|
|||||||
return new RuntimeException(throwable);
|
return new RuntimeException(throwable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将指定的消息包装为运行时异常
|
||||||
|
*
|
||||||
|
* @param message 异常消息
|
||||||
|
* @return 运行时异常
|
||||||
|
* @since 5.5.2
|
||||||
|
*/
|
||||||
|
public static RuntimeException wrapRuntime(String message) {
|
||||||
|
return new RuntimeException(message);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 包装一个异常
|
* 包装一个异常
|
||||||
*
|
*
|
||||||
* @param <T> 被包装的异常类型
|
* @param <T> 被包装的异常类型
|
||||||
* @param throwable 异常
|
* @param throwable 异常
|
||||||
* @param wrapThrowable 包装后的异常类
|
* @param wrapThrowable 包装后的异常类
|
||||||
* @return 包装后的异常
|
* @return 包装后的异常
|
||||||
* @since 3.3.0
|
* @since 3.3.0
|
||||||
@@ -80,7 +90,7 @@ public class ExceptionUtil {
|
|||||||
/**
|
/**
|
||||||
* 包装异常并重新抛出此异常<br>
|
* 包装异常并重新抛出此异常<br>
|
||||||
* {@link RuntimeException} 和{@link Error} 直接抛出,其它检查异常包装为{@link UndeclaredThrowableException} 后抛出
|
* {@link RuntimeException} 和{@link Error} 直接抛出,其它检查异常包装为{@link UndeclaredThrowableException} 后抛出
|
||||||
*
|
*
|
||||||
* @param throwable 异常
|
* @param throwable 异常
|
||||||
*/
|
*/
|
||||||
public static void wrapAndThrow(Throwable throwable) {
|
public static void wrapAndThrow(Throwable throwable) {
|
||||||
@@ -93,9 +103,19 @@ public class ExceptionUtil {
|
|||||||
throw new UndeclaredThrowableException(throwable);
|
throw new UndeclaredThrowableException(throwable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将消息包装为运行时异常并抛出
|
||||||
|
*
|
||||||
|
* @param message 异常消息
|
||||||
|
* @since 5.5.2
|
||||||
|
*/
|
||||||
|
public static void wrapRuntimeAndThrow(String message) {
|
||||||
|
throw new RuntimeException(message);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 剥离反射引发的InvocationTargetException、UndeclaredThrowableException中间异常,返回业务本身的异常
|
* 剥离反射引发的InvocationTargetException、UndeclaredThrowableException中间异常,返回业务本身的异常
|
||||||
*
|
*
|
||||||
* @param wrapped 包装的异常
|
* @param wrapped 包装的异常
|
||||||
* @return 剥离后的异常
|
* @return 剥离后的异常
|
||||||
*/
|
*/
|
||||||
@@ -114,7 +134,7 @@ public class ExceptionUtil {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前栈信息
|
* 获取当前栈信息
|
||||||
*
|
*
|
||||||
* @return 当前栈信息
|
* @return 当前栈信息
|
||||||
*/
|
*/
|
||||||
public static StackTraceElement[] getStackElements() {
|
public static StackTraceElement[] getStackElements() {
|
||||||
@@ -135,7 +155,7 @@ public class ExceptionUtil {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取入口堆栈信息
|
* 获取入口堆栈信息
|
||||||
*
|
*
|
||||||
* @return 入口堆栈信息
|
* @return 入口堆栈信息
|
||||||
* @since 4.1.4
|
* @since 4.1.4
|
||||||
*/
|
*/
|
||||||
@@ -146,7 +166,7 @@ public class ExceptionUtil {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 堆栈转为单行完整字符串
|
* 堆栈转为单行完整字符串
|
||||||
*
|
*
|
||||||
* @param throwable 异常对象
|
* @param throwable 异常对象
|
||||||
* @return 堆栈转为的字符串
|
* @return 堆栈转为的字符串
|
||||||
*/
|
*/
|
||||||
@@ -156,9 +176,9 @@ public class ExceptionUtil {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 堆栈转为单行完整字符串
|
* 堆栈转为单行完整字符串
|
||||||
*
|
*
|
||||||
* @param throwable 异常对象
|
* @param throwable 异常对象
|
||||||
* @param limit 限制最大长度
|
* @param limit 限制最大长度
|
||||||
* @return 堆栈转为的字符串
|
* @return 堆栈转为的字符串
|
||||||
*/
|
*/
|
||||||
public static String stacktraceToOneLineString(Throwable throwable, int limit) {
|
public static String stacktraceToOneLineString(Throwable throwable, int limit) {
|
||||||
@@ -172,7 +192,7 @@ public class ExceptionUtil {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 堆栈转为完整字符串
|
* 堆栈转为完整字符串
|
||||||
*
|
*
|
||||||
* @param throwable 异常对象
|
* @param throwable 异常对象
|
||||||
* @return 堆栈转为的字符串
|
* @return 堆栈转为的字符串
|
||||||
*/
|
*/
|
||||||
@@ -182,9 +202,9 @@ public class ExceptionUtil {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 堆栈转为完整字符串
|
* 堆栈转为完整字符串
|
||||||
*
|
*
|
||||||
* @param throwable 异常对象
|
* @param throwable 异常对象
|
||||||
* @param limit 限制最大长度
|
* @param limit 限制最大长度
|
||||||
* @return 堆栈转为的字符串
|
* @return 堆栈转为的字符串
|
||||||
*/
|
*/
|
||||||
public static String stacktraceToString(Throwable throwable, int limit) {
|
public static String stacktraceToString(Throwable throwable, int limit) {
|
||||||
@@ -193,9 +213,9 @@ public class ExceptionUtil {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 堆栈转为完整字符串
|
* 堆栈转为完整字符串
|
||||||
*
|
*
|
||||||
* @param throwable 异常对象
|
* @param throwable 异常对象
|
||||||
* @param limit 限制最大长度
|
* @param limit 限制最大长度
|
||||||
* @param replaceCharToStrMap 替换字符为指定字符串
|
* @param replaceCharToStrMap 替换字符为指定字符串
|
||||||
* @return 堆栈转为的字符串
|
* @return 堆栈转为的字符串
|
||||||
*/
|
*/
|
||||||
@@ -229,8 +249,8 @@ public class ExceptionUtil {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 判断是否由指定异常类引起
|
* 判断是否由指定异常类引起
|
||||||
*
|
*
|
||||||
* @param throwable 异常
|
* @param throwable 异常
|
||||||
* @param causeClasses 定义的引起异常的类
|
* @param causeClasses 定义的引起异常的类
|
||||||
* @return 是否由指定异常类引起
|
* @return 是否由指定异常类引起
|
||||||
* @since 4.1.13
|
* @since 4.1.13
|
||||||
@@ -242,8 +262,8 @@ public class ExceptionUtil {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取由指定异常类引起的异常
|
* 获取由指定异常类引起的异常
|
||||||
*
|
*
|
||||||
* @param throwable 异常
|
* @param throwable 异常
|
||||||
* @param causeClasses 定义的引起异常的类
|
* @param causeClasses 定义的引起异常的类
|
||||||
* @return 是否由指定异常类引起
|
* @return 是否由指定异常类引起
|
||||||
* @since 4.1.13
|
* @since 4.1.13
|
||||||
@@ -265,7 +285,7 @@ public class ExceptionUtil {
|
|||||||
/**
|
/**
|
||||||
* 判断指定异常是否来自或者包含指定异常
|
* 判断指定异常是否来自或者包含指定异常
|
||||||
*
|
*
|
||||||
* @param throwable 异常
|
* @param throwable 异常
|
||||||
* @param exceptionClass 定义的引起异常的类
|
* @param exceptionClass 定义的引起异常的类
|
||||||
* @return true 来自或者包含
|
* @return true 来自或者包含
|
||||||
* @since 4.3.2
|
* @since 4.3.2
|
||||||
@@ -277,9 +297,9 @@ public class ExceptionUtil {
|
|||||||
/**
|
/**
|
||||||
* 判断指定异常是否来自或者包含指定异常
|
* 判断指定异常是否来自或者包含指定异常
|
||||||
*
|
*
|
||||||
* @param throwable 异常
|
* @param throwable 异常
|
||||||
* @param exceptionClass 定义的引起异常的类
|
* @param exceptionClass 定义的引起异常的类
|
||||||
* @param checkCause 判断cause
|
* @param checkCause 判断cause
|
||||||
* @return true 来自或者包含
|
* @return true 来自或者包含
|
||||||
* @since 4.4.1
|
* @since 4.4.1
|
||||||
*/
|
*/
|
||||||
@@ -290,8 +310,8 @@ public class ExceptionUtil {
|
|||||||
/**
|
/**
|
||||||
* 转化指定异常为来自或者包含指定异常
|
* 转化指定异常为来自或者包含指定异常
|
||||||
*
|
*
|
||||||
* @param <T> 异常类型
|
* @param <T> 异常类型
|
||||||
* @param throwable 异常
|
* @param throwable 异常
|
||||||
* @param exceptionClass 定义的引起异常的类
|
* @param exceptionClass 定义的引起异常的类
|
||||||
* @return 结果为null 不是来自或者包含
|
* @return 结果为null 不是来自或者包含
|
||||||
* @since 4.3.2
|
* @since 4.3.2
|
||||||
@@ -303,10 +323,10 @@ public class ExceptionUtil {
|
|||||||
/**
|
/**
|
||||||
* 转化指定异常为来自或者包含指定异常
|
* 转化指定异常为来自或者包含指定异常
|
||||||
*
|
*
|
||||||
* @param <T> 异常类型
|
* @param <T> 异常类型
|
||||||
* @param throwable 异常
|
* @param throwable 异常
|
||||||
* @param exceptionClass 定义的引起异常的类
|
* @param exceptionClass 定义的引起异常的类
|
||||||
* @param checkCause 判断cause
|
* @param checkCause 判断cause
|
||||||
* @return 结果为null 不是来自或者包含
|
* @return 结果为null 不是来自或者包含
|
||||||
* @since 4.4.1
|
* @since 4.4.1
|
||||||
*/
|
*/
|
||||||
@@ -338,11 +358,11 @@ public class ExceptionUtil {
|
|||||||
/**
|
/**
|
||||||
* 获取异常链上所有异常的集合,如果{@link Throwable} 对象没有cause,返回只有一个节点的List<br>
|
* 获取异常链上所有异常的集合,如果{@link Throwable} 对象没有cause,返回只有一个节点的List<br>
|
||||||
* 如果传入null,返回空集合
|
* 如果传入null,返回空集合
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* 此方法来自Apache-Commons-Lang3
|
* 此方法来自Apache-Commons-Lang3
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param throwable 异常对象,可以为null
|
* @param throwable 异常对象,可以为null
|
||||||
* @return 异常链中所有异常集合
|
* @return 异常链中所有异常集合
|
||||||
* @since 4.6.2
|
* @since 4.6.2
|
||||||
@@ -360,11 +380,11 @@ public class ExceptionUtil {
|
|||||||
* 获取异常链中最尾端的异常,即异常最早发生的异常对象。<br>
|
* 获取异常链中最尾端的异常,即异常最早发生的异常对象。<br>
|
||||||
* 此方法通过调用{@link Throwable#getCause()} 直到没有cause为止,如果异常本身没有cause,返回异常本身<br>
|
* 此方法通过调用{@link Throwable#getCause()} 直到没有cause为止,如果异常本身没有cause,返回异常本身<br>
|
||||||
* 传入null返回也为null
|
* 传入null返回也为null
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* 此方法来自Apache-Commons-Lang3
|
* 此方法来自Apache-Commons-Lang3
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param throwable 异常对象,可能为null
|
* @param throwable 异常对象,可能为null
|
||||||
* @return 最尾端异常,传入null参数返回也为null
|
* @return 最尾端异常,传入null参数返回也为null
|
||||||
*/
|
*/
|
||||||
@@ -375,7 +395,7 @@ public class ExceptionUtil {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取异常链中最尾端的异常的消息,消息格式为:{SimpleClassName}: {ThrowableMessage}
|
* 获取异常链中最尾端的异常的消息,消息格式为:{SimpleClassName}: {ThrowableMessage}
|
||||||
*
|
*
|
||||||
* @param th 异常
|
* @param th 异常
|
||||||
* @return 消息
|
* @return 消息
|
||||||
* @since 4.6.2
|
* @since 4.6.2
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ public class ConcurrencyTester {
|
|||||||
private final TimeInterval timeInterval;
|
private final TimeInterval timeInterval;
|
||||||
private long interval;
|
private long interval;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
* @param threadSize 线程数
|
||||||
|
*/
|
||||||
public ConcurrencyTester(int threadSize) {
|
public ConcurrencyTester(int threadSize) {
|
||||||
this.sf = new SyncFinisher(threadSize);
|
this.sf = new SyncFinisher(threadSize);
|
||||||
this.timeInterval = new TimeInterval();
|
this.timeInterval = new TimeInterval();
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -16,27 +16,27 @@ import java.io.InputStream;
|
|||||||
*/
|
*/
|
||||||
public class JavaSourceCompilerTest {
|
public class JavaSourceCompilerTest {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 测试编译Java源码
|
* 测试编译Java源码
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testCompile() throws ClassNotFoundException {
|
public void testCompile() throws ClassNotFoundException {
|
||||||
final File libFile = ZipUtil.zip(FileUtil.file("lib.jar"),
|
final File libFile = ZipUtil.zip(FileUtil.file("lib.jar"),
|
||||||
new String[]{"a/A.class", "a/A$1.class", "a/A$InnerClass.class"},
|
new String[]{"a/A.class", "a/A$1.class", "a/A$InnerClass.class"},
|
||||||
new InputStream[]{
|
new InputStream[]{
|
||||||
FileUtil.getInputStream("test-compile/a/A.class"),
|
FileUtil.getInputStream("test-compile/a/A.class"),
|
||||||
FileUtil.getInputStream("test-compile/a/A$1.class"),
|
FileUtil.getInputStream("test-compile/a/A$1.class"),
|
||||||
FileUtil.getInputStream("test-compile/a/A$InnerClass.class")
|
FileUtil.getInputStream("test-compile/a/A$InnerClass.class")
|
||||||
});
|
});
|
||||||
final ClassLoader classLoader = JavaSourceCompiler.create(null)
|
final ClassLoader classLoader = JavaSourceCompiler.create(null)
|
||||||
.addSource(FileUtil.file("test-compile/b/B.java"))
|
.addSource(FileUtil.file("test-compile/b/B.java"))
|
||||||
.addSource("c.C", FileUtil.readUtf8String("test-compile/c/C.java"))
|
.addSource("c.C", FileUtil.readUtf8String("test-compile/c/C.java"))
|
||||||
.addLibrary(libFile)
|
.addLibrary(libFile)
|
||||||
.compile();
|
.compile();
|
||||||
final Class<?> clazz = classLoader.loadClass("c.C");
|
final Class<?> clazz = classLoader.loadClass("c.C");
|
||||||
Object obj = ReflectUtil.newInstance(clazz);
|
Object obj = ReflectUtil.newInstance(clazz);
|
||||||
Assert.assertTrue(String.valueOf(obj).startsWith("c.C@"));
|
Assert.assertTrue(String.valueOf(obj).startsWith("c.C@"));
|
||||||
FileUtil.del(libFile);
|
FileUtil.del(libFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package cn.hutool.core.date;
|
||||||
|
|
||||||
|
import cn.hutool.core.lang.Console;
|
||||||
|
import cn.hutool.core.thread.ThreadUtil;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class TimeIntervalTest {
|
||||||
|
@Test
|
||||||
|
public void intervalGroupTest(){
|
||||||
|
final TimeInterval timer = new TimeInterval();
|
||||||
|
timer.start("1");
|
||||||
|
ThreadUtil.sleep(800);
|
||||||
|
timer.start("2");
|
||||||
|
ThreadUtil.sleep(900);
|
||||||
|
|
||||||
|
|
||||||
|
Console.log("Timer 1 took {} ms", timer.intervalMs("1"));
|
||||||
|
Console.log("Timer 2 took {} ms", timer.intervalMs("2"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package cn.hutool.crypto.digest.opt;
|
package cn.hutool.crypto.digest.otp;
|
||||||
|
|
||||||
import cn.hutool.crypto.digest.HMac;
|
import cn.hutool.crypto.digest.HMac;
|
||||||
import cn.hutool.crypto.digest.HmacAlgorithm;
|
import cn.hutool.crypto.digest.HmacAlgorithm;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package cn.hutool.crypto.digest.opt;
|
package cn.hutool.crypto.digest.otp;
|
||||||
|
|
||||||
import cn.hutool.crypto.digest.HmacAlgorithm;
|
import cn.hutool.crypto.digest.HmacAlgorithm;
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ import java.time.Instant;
|
|||||||
*
|
*
|
||||||
* @author Looly
|
* @author Looly
|
||||||
*/
|
*/
|
||||||
public class TOPT extends HOTP {
|
public class TOTP extends HOTP {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 默认步进 (30秒).
|
* 默认步进 (30秒).
|
||||||
@@ -27,7 +27,7 @@ public class TOPT extends HOTP {
|
|||||||
*
|
*
|
||||||
* @param key 共享密码,RFC 4226要求最少128位
|
* @param key 共享密码,RFC 4226要求最少128位
|
||||||
*/
|
*/
|
||||||
public TOPT(byte[] key) {
|
public TOTP(byte[] key) {
|
||||||
this(DEFAULT_TIME_STEP, key);
|
this(DEFAULT_TIME_STEP, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ public class TOPT extends HOTP {
|
|||||||
* @param timeStep 日期步进,用于生成移动因子(moving factor)
|
* @param timeStep 日期步进,用于生成移动因子(moving factor)
|
||||||
* @param key 共享密码,RFC 4226要求最少128位
|
* @param key 共享密码,RFC 4226要求最少128位
|
||||||
*/
|
*/
|
||||||
public TOPT(Duration timeStep, byte[] key) {
|
public TOTP(Duration timeStep, byte[] key) {
|
||||||
this(timeStep, DEFAULT_PASSWORD_LENGTH, key);
|
this(timeStep, DEFAULT_PASSWORD_LENGTH, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ public class TOPT extends HOTP {
|
|||||||
* @param passwordLength 密码长度,可以是6,7,8
|
* @param passwordLength 密码长度,可以是6,7,8
|
||||||
* @param key 共享密码,RFC 4226要求最少128位
|
* @param key 共享密码,RFC 4226要求最少128位
|
||||||
*/
|
*/
|
||||||
public TOPT(Duration timeStep, int passwordLength, byte[] key) {
|
public TOTP(Duration timeStep, int passwordLength, byte[] key) {
|
||||||
this(timeStep, passwordLength, HOTP_HMAC_ALGORITHM, key);
|
this(timeStep, passwordLength, HOTP_HMAC_ALGORITHM, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ public class TOPT extends HOTP {
|
|||||||
* @param algorithm HMAC算法枚举
|
* @param algorithm HMAC算法枚举
|
||||||
* @param key 共享密码,RFC 4226要求最少128位
|
* @param key 共享密码,RFC 4226要求最少128位
|
||||||
*/
|
*/
|
||||||
public TOPT(Duration timeStep, int passwordLength, HmacAlgorithm algorithm, byte[] key) {
|
public TOTP(Duration timeStep, int passwordLength, HmacAlgorithm algorithm, byte[] key) {
|
||||||
super(passwordLength, algorithm, key);
|
super(passwordLength, algorithm, key);
|
||||||
this.timeStep = timeStep;
|
this.timeStep = timeStep;
|
||||||
}
|
}
|
||||||
@@ -11,4 +11,4 @@
|
|||||||
*
|
*
|
||||||
* @author looly
|
* @author looly
|
||||||
*/
|
*/
|
||||||
package cn.hutool.crypto.digest.opt;
|
package cn.hutool.crypto.digest.otp;
|
||||||
@@ -420,7 +420,7 @@ public class JSONObject implements JSON, JSONGetter<String>, Map<String, Object>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 积累值。类似于put,当key对应value已经存在时,与value组成新的JSONArray. <br>
|
* 积累值。类似于set,当key对应value已经存在时,与value组成新的JSONArray. <br>
|
||||||
* 如果只有一个值,此值就是value,如果多个值,则是添加到新的JSONArray中
|
* 如果只有一个值,此值就是value,如果多个值,则是添加到新的JSONArray中
|
||||||
*
|
*
|
||||||
* @param key 键
|
* @param key 键
|
||||||
@@ -432,11 +432,11 @@ public class JSONObject implements JSON, JSONGetter<String>, Map<String, Object>
|
|||||||
InternalJSONUtil.testValidity(value);
|
InternalJSONUtil.testValidity(value);
|
||||||
Object object = this.getObj(key);
|
Object object = this.getObj(key);
|
||||||
if (object == null) {
|
if (object == null) {
|
||||||
this.set(key, value instanceof JSONArray ? new JSONArray(this.config).set(value) : value);
|
this.set(key, value);
|
||||||
} else if (object instanceof JSONArray) {
|
} else if (object instanceof JSONArray) {
|
||||||
((JSONArray) object).set(value);
|
((JSONArray) object).set(value);
|
||||||
} else {
|
} else {
|
||||||
this.set(key, new JSONArray(this.config).set(object).set(value));
|
this.set(key, JSONUtil.createArray(this.config).set(object).set(value));
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -535,4 +535,16 @@ public class JSONObjectTest {
|
|||||||
final String s = JSONUtil.toJsonStr(map);
|
final String s = JSONUtil.toJsonStr(map);
|
||||||
Console.log(s);
|
Console.log(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void accumulateTest(){
|
||||||
|
final JSONObject jsonObject = JSONUtil.createObj().accumulate("key1", "value1");
|
||||||
|
Assert.assertEquals("{\"key1\":\"value1\"}", jsonObject.toString());
|
||||||
|
|
||||||
|
jsonObject.accumulate("key1", "value2");
|
||||||
|
Assert.assertEquals("{\"key1\":[\"value1\",\"value2\"]}", jsonObject.toString());
|
||||||
|
|
||||||
|
jsonObject.accumulate("key1", "value3");
|
||||||
|
Assert.assertEquals("{\"key1\":[\"value1\",\"value2\",\"value3\"]}", jsonObject.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,6 +165,7 @@ public class JSONUtilTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void parseObjTest() {
|
public void parseObjTest() {
|
||||||
|
// 测试转义
|
||||||
final JSONObject jsonObject = JSONUtil.parseObj("{\n" +
|
final JSONObject jsonObject = JSONUtil.parseObj("{\n" +
|
||||||
" \"test\": \"\\\\地库地库\",\n" +
|
" \"test\": \"\\\\地库地库\",\n" +
|
||||||
"}");
|
"}");
|
||||||
|
|||||||
Reference in New Issue
Block a user