Merge remote-tracking branch 'upstream/v5-dev' into v5-dev

This commit is contained in:
lzpeng723
2020-11-29 01:00:21 +08:00
26 changed files with 3929 additions and 3267 deletions

View File

@@ -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 】 增加CacheListenerissue#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问题
------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------

View File

@@ -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;
}
} }

View 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);
}

View File

@@ -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);
}
} }
/** /**

View File

@@ -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;

View File

@@ -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);

View File

@@ -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,10 +22,9 @@ 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字节码文件对象映射
@@ -38,15 +41,10 @@ final class JavaClassFileManager extends ForwardingJavaFileManager<JavaFileManag
* *
* @param parent 父类加载器 * @param parent 父类加载器
* @param fileManager 字节码文件管理器 * @param fileManager 字节码文件管理器
* @see JavaSourceCompilerBak#compile()
*/ */
protected JavaClassFileManager(final ClassLoader parent, final JavaFileManager fileManager) { protected JavaClassFileManager(ClassLoader parent, JavaFileManager fileManager) {
super(fileManager); super(fileManager);
if (parent == null) { this.parent = ObjectUtil.defaultIfNull(parent, ClassLoaderUtil.getClassLoader());
this.parent = Thread.currentThread().getContextClassLoader();
} else {
this.parent = parent;
}
} }
/** /**
@@ -54,7 +52,6 @@ final class JavaClassFileManager extends ForwardingJavaFileManager<JavaFileManag
* *
* @param location 源码位置 * @param location 源码位置
* @return 动态编译生成的类的类加载器 * @return 动态编译生成的类的类加载器
* @see JavaSourceCompilerBak#compile()
*/ */
@Override @Override
public ClassLoader getClassLoader(final Location location) { public ClassLoader getClassLoader(final Location location) {
@@ -69,14 +66,12 @@ final class JavaClassFileManager extends ForwardingJavaFileManager<JavaFileManag
@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);
final Class<?> c = defineClass(name, bytes, 0, bytes.length); return defineClass(name, bytes, 0, bytes.length);
return c; } catch (IOException e) {
} catch (Exception e) { throw new IORuntimeException(e);
e.printStackTrace();
} }
} }
throw new ClassNotFoundException(name); throw new ClassNotFoundException(name);
@@ -93,7 +88,6 @@ final class JavaClassFileManager extends ForwardingJavaFileManager<JavaFileManag
* @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) {

View File

@@ -14,7 +14,7 @@ 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 {
@@ -44,8 +44,7 @@ final class JavaClassFileObject extends SimpleJavaFileObject {
*/ */
@Override @Override
public InputStream openInputStream() { public InputStream openInputStream() {
final byte[] bytes = byteArrayOutputStream.toByteArray(); return new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
return new ByteArrayInputStream(bytes);
} }
/** /**
@@ -53,7 +52,6 @@ final class JavaClassFileObject extends SimpleJavaFileObject {
* 编译器编辑源码时,会将编译结果输出到本输出流中 * 编译器编辑源码时,会将编译结果输出到本输出流中
* *
* @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() {

View File

@@ -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,7 +37,7 @@ import java.util.zip.ZipFile;
* *
* @author lzpeng * @author lzpeng
*/ */
public final class JavaSourceCompiler { public class JavaSourceCompiler {
/** /**
* java 编译器 * java 编译器
@@ -131,12 +145,8 @@ public final class JavaSourceCompiler {
* @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 // 获得classPath
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]));
@@ -147,21 +157,24 @@ public final class JavaSourceCompiler {
} }
// 没有需要编译的源码文件返回加载zip或jar包的类加载器 // 没有需要编译的源码文件返回加载zip或jar包的类加载器
final Iterable<JavaFileObject> javaFileObjectList = getJavaFileObject(); final Iterable<JavaFileObject> javaFileObjectList = getJavaFileObject();
// 创建编译器 // 创建编译器
final JavaFileManager standardJavaFileManager = JAVA_COMPILER.getStandardFileManager(null, null, null); final JavaFileManager standardJavaFileManager = JAVA_COMPILER.getStandardFileManager(null, null, null);
final JavaFileManager javaFileManager = new JavaClassFileManager(ucl, standardJavaFileManager); final JavaFileManager javaFileManager = new JavaClassFileManager(ucl, standardJavaFileManager);
final DiagnosticCollector<? super JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
// classpath
final List<String> options = new ArrayList<>(); final List<String> options = new ArrayList<>();
if (!classPath.isEmpty()) { if (false == classPath.isEmpty()) {
final List<String> cp = classPath.stream().map(File::getAbsolutePath).collect(Collectors.toList()); final List<String> cp = classPath.stream().map(File::getAbsolutePath).collect(Collectors.toList());
options.add("-cp"); options.add("-cp");
options.addAll(cp); options.addAll(cp);
} }
// 编译文件 // 编译文件
final DiagnosticCollector<? super JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
final CompilationTask task = JAVA_COMPILER.getTask(null, javaFileManager, diagnosticCollector, final CompilationTask task = JAVA_COMPILER.getTask(null, javaFileManager, diagnosticCollector,
options, null, javaFileObjectList); options, null, javaFileObjectList);
final Boolean result = task.call(); if (task.call()) {
if (Boolean.TRUE.equals(result)) {
return javaFileManager.getClassLoader(StandardLocation.CLASS_OUTPUT); return javaFileManager.getClassLoader(StandardLocation.CLASS_OUTPUT);
} else { } else {
// 编译失败,收集错误信息 // 编译失败,收集错误信息
@@ -223,7 +236,7 @@ public final class JavaSourceCompiler {
private Collection<JavaFileObject> getJavaFileObjectByMap(final Map<String, String> sourceCodeMap) { private Collection<JavaFileObject> getJavaFileObjectByMap(final Map<String, String> sourceCodeMap) {
if (MapUtil.isNotEmpty(sourceCodeMap)) { if (MapUtil.isNotEmpty(sourceCodeMap)) {
return sourceCodeMap.entrySet().stream() return sourceCodeMap.entrySet().stream()
.map(entry -> new JavaSourceFileObject(entry.getKey(), entry.getValue(), Kind.SOURCE)) .map(entry -> new JavaSourceFileObject(entry.getKey(), entry.getValue(), CharsetUtil.CHARSET_UTF_8))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
return Collections.emptySet(); return Collections.emptySet();
@@ -236,7 +249,7 @@ public final class JavaSourceCompiler {
* @return Java文件对象 * @return Java文件对象
*/ */
private JavaFileObject getJavaFileObjectByJavaFile(final File file) { private JavaFileObject getJavaFileObjectByJavaFile(final File file) {
return new JavaSourceFileObject(file.toURI(), Kind.SOURCE); return new JavaSourceFileObject(file.toURI());
} }
/** /**
@@ -255,7 +268,7 @@ public final class JavaSourceCompiler {
final String name = zipEntry.getName(); final String name = zipEntry.getName();
if (name.endsWith(".java")) { if (name.endsWith(".java")) {
final InputStream inputStream = zipFile.getInputStream(zipEntry); final InputStream inputStream = zipFile.getInputStream(zipEntry);
final JavaSourceFileObject fileObject = new JavaSourceFileObject(name, inputStream, Kind.SOURCE); final JavaSourceFileObject fileObject = new JavaSourceFileObject(name, inputStream);
collection.add(fileObject); collection.add(fileObject);
} }
} }

View File

@@ -1,26 +1,21 @@
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 {
/** /**
* 输入流 * 输入流
@@ -31,11 +26,9 @@ final class JavaSourceFileObject extends SimpleJavaFileObject {
* 构造 * 构造
* *
* @param uri 需要编译的文件uri * @param uri 需要编译的文件uri
* @param kind 需要编译的文件类型
* @see JavaSourceCompilerBak#getJavaFileObjectByJavaFile(java.io.File)
*/ */
protected JavaSourceFileObject(URI uri, Kind kind) { protected JavaSourceFileObject(URI uri) {
super(uri, kind); super(uri, Kind.SOURCE);
} }
/** /**
@@ -43,11 +36,9 @@ final class JavaSourceFileObject extends SimpleJavaFileObject {
* *
* @param name 需要编译的文件名 * @param name 需要编译的文件名
* @param inputStream 输入流 * @param inputStream 输入流
* @param kind 需要编译的文件类型
* @see JavaSourceCompilerBak#getJavaFileObjectByZipOrJarFile(java.io.File)
*/ */
protected JavaSourceFileObject(final String name, final InputStream inputStream, final Kind kind) { protected JavaSourceFileObject(String name, InputStream inputStream) {
super(URI.create("string:///" + name), kind); this(URI.create("string:///" + name));
this.inputStream = inputStream; this.inputStream = inputStream;
} }
@@ -56,12 +47,10 @@ final class JavaSourceFileObject extends SimpleJavaFileObject {
* *
* @param className 需要编译的类名 * @param className 需要编译的类名
* @param code 需要编译的类源码 * @param code 需要编译的类源码
* @param kind 需要编译的文件类型
* @see JavaSourceCompilerBak#getJavaFileObject(java.util.Map)
*/ */
protected JavaSourceFileObject(final String className, final String code, final Kind kind) { protected JavaSourceFileObject(String className, String code, Charset charset) {
super(URI.create("string:///" + className.replaceAll("\\.", "/") + kind.extension), kind); this(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension));
this.inputStream = new ByteArrayInputStream(code.getBytes()); this.inputStream = IoUtil.toStream(code, charset);
} }
/** /**
@@ -85,14 +74,12 @@ final class JavaSourceFileObject extends SimpleJavaFileObject {
* @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(final boolean ignoreEncodingErrors) throws IOException { public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
final InputStream in = openInputStream(); try(final InputStream in = openInputStream()){
final String code = IoUtil.read(in, Charset.defaultCharset()); return IoUtil.readUtf8(in);
IoUtil.close(in); }
return code;
} }
} }

View File

@@ -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
*/ */

View File

@@ -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();
}
}

View File

@@ -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)

View File

@@ -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();
} }
} }

View File

@@ -18,7 +18,6 @@ import java.util.Map;
* 异常工具类 * 异常工具类
* *
* @author Looly * @author Looly
*
*/ */
public class ExceptionUtil { public class ExceptionUtil {
@@ -47,8 +46,8 @@ public class ExceptionUtil {
/** /**
* 使用运行时异常包装编译异常<br> * 使用运行时异常包装编译异常<br>
* * <p>
* 如果 * 如果传入参数已经是运行时异常,则直接返回,不再额外包装
* *
* @param throwable 异常 * @param throwable 异常
* @return 运行时异常 * @return 运行时异常
@@ -60,6 +59,17 @@ 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);
}
/** /**
* 包装一个异常 * 包装一个异常
* *
@@ -93,6 +103,16 @@ 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中间异常返回业务本身的异常
* *

View File

@@ -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

View File

@@ -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"));
}
}

View File

@@ -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;

View File

@@ -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;
} }

View File

@@ -11,4 +11,4 @@
* *
* @author looly * @author looly
*/ */
package cn.hutool.crypto.digest.opt; package cn.hutool.crypto.digest.otp;

View File

@@ -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;
} }

View File

@@ -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());
}
} }

View File

@@ -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" +
"}"); "}");