Prepare release

This commit is contained in:
Looly 2022-01-14 09:46:18 +08:00
commit 3715024abd
66 changed files with 662 additions and 121 deletions

View File

@ -3,6 +3,22 @@
------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------
# 5.7.20 (2022-01-14)
### 🐣新特性
* 【core 】 增加对null值友好的groupingBy操作的Collector实现可指定map类型pr#498@Gitee
* 【core 】 增加KetamaHashissue#2084@Github
* 【crypto 】 增加SignUtil
* 【json 】 JSONGetter增加getBeanList方法
* 【core 】 ObjectUtil 添加三个defaultIfXxxx方法用于节省CPU及内存损耗。(pr#2094@Github)
*
### 🐞Bug修复
* 【core 】 修复setter重载导致匹配错误issue#2082@Github
* 【core 】 修复RegexPool汉字匹配范围小问题pr#2081@Github
* 【core 】 修复OS中的拼写错误pr#500@Gitee
* 【core 】 修复CustomKeyMap的merge失效问题issue#2086@Github
-------------------------------------------------------------------------------------------------------------
# 5.7.19 (2022-01-07) # 5.7.19 (2022-01-07)
### 🐣新特性 ### 🐣新特性

View File

@ -142,18 +142,18 @@ We provide the T-Shirt and Sweater with Hutool Logo, please visit the shop
<dependency> <dependency>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId> <artifactId>hutool-all</artifactId>
<version>5.7.19</version> <version>5.7.20</version>
</dependency> </dependency>
``` ```
### 🍐Gradle ### 🍐Gradle
``` ```
implementation 'cn.hutool:hutool-all:5.7.19' implementation 'cn.hutool:hutool-all:5.7.20'
``` ```
## 📥Download ## 📥Download
- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.7.19/) - [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.7.20/)
> 🔔note: > 🔔note:
> Hutool 5.x supports JDK8+ and is not tested on Android platforms, and cannot guarantee that all tool classes or tool methods are available. > Hutool 5.x supports JDK8+ and is not tested on Android platforms, and cannot guarantee that all tool classes or tool methods are available.

View File

@ -142,20 +142,20 @@ Hutool的存在就是为了减少代码搜索成本避免网络上参差不
<dependency> <dependency>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId> <artifactId>hutool-all</artifactId>
<version>5.7.19</version> <version>5.7.20</version>
</dependency> </dependency>
``` ```
### 🍐Gradle ### 🍐Gradle
``` ```
implementation 'cn.hutool:hutool-all:5.7.19' implementation 'cn.hutool:hutool-all:5.7.20'
``` ```
### 📥下载jar ### 📥下载jar
点击以下链接,下载`hutool-all-X.X.X.jar`即可: 点击以下链接,下载`hutool-all-X.X.X.jar`即可:
- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.7.19/) - [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.7.20/)
> 🔔️注意 > 🔔️注意
> Hutool 5.x支持JDK8+对Android平台没有测试不能保证所有工具类或工具方法可用。 > Hutool 5.x支持JDK8+对Android平台没有测试不能保证所有工具类或工具方法可用。

View File

@ -1 +1 @@
5.7.19 5.7.20

View File

@ -1 +1 @@
var version = '5.7.19' var version = '5.7.20'

View File

@ -9,7 +9,7 @@
<parent> <parent>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId> <artifactId>hutool-parent</artifactId>
<version>5.7.19</version> <version>5.7.20-SNAPSHOT</version>
</parent> </parent>
<artifactId>hutool-all</artifactId> <artifactId>hutool-all</artifactId>

View File

@ -9,7 +9,7 @@
<parent> <parent>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId> <artifactId>hutool-parent</artifactId>
<version>5.7.19</version> <version>5.7.20-SNAPSHOT</version>
</parent> </parent>
<artifactId>hutool-aop</artifactId> <artifactId>hutool-aop</artifactId>

View File

@ -9,7 +9,7 @@
<parent> <parent>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId> <artifactId>hutool-parent</artifactId>
<version>5.7.19</version> <version>5.7.20-SNAPSHOT</version>
</parent> </parent>
<artifactId>hutool-bloomFilter</artifactId> <artifactId>hutool-bloomFilter</artifactId>

View File

@ -9,7 +9,7 @@
<parent> <parent>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId> <artifactId>hutool-parent</artifactId>
<version>5.7.19</version> <version>5.7.20-SNAPSHOT</version>
</parent> </parent>
<artifactId>hutool-bom</artifactId> <artifactId>hutool-bom</artifactId>

View File

@ -9,7 +9,7 @@
<parent> <parent>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId> <artifactId>hutool-parent</artifactId>
<version>5.7.19</version> <version>5.7.20-SNAPSHOT</version>
</parent> </parent>
<artifactId>hutool-cache</artifactId> <artifactId>hutool-cache</artifactId>

View File

@ -9,7 +9,7 @@
<parent> <parent>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId> <artifactId>hutool-parent</artifactId>
<version>5.7.19</version> <version>5.7.20-SNAPSHOT</version>
</parent> </parent>
<artifactId>hutool-captcha</artifactId> <artifactId>hutool-captcha</artifactId>

View File

@ -9,7 +9,7 @@
<parent> <parent>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId> <artifactId>hutool-parent</artifactId>
<version>5.7.19</version> <version>5.7.20-SNAPSHOT</version>
</parent> </parent>
<artifactId>hutool-core</artifactId> <artifactId>hutool-core</artifactId>

View File

@ -140,14 +140,12 @@ public class BeanDesc implements Serializable {
* @return this * @return this
*/ */
private BeanDesc init() { private BeanDesc init() {
final Method[] methods = ReflectUtil.getMethods(this.beanClass); final Method[] gettersAndSetters = ReflectUtil.getMethods(this.beanClass, ReflectUtil::isGetterOrSetterIgnoreCase);
PropDesc prop; PropDesc prop;
for (Field field : ReflectUtil.getFields(this.beanClass)) { for (Field field : ReflectUtil.getFields(this.beanClass)) {
if (false == ModifierUtil.isStatic(field) && // 排除静态属性和对象子类
// 排除对象子类 if (false == ModifierUtil.isStatic(field) && false == ReflectUtil.isOuterClassField(field)) {
false == "this$0".equals(field.getName())) { prop = createProp(field, gettersAndSetters);
//只针对非static属性
prop = createProp(field, methods);
// 只有不存在时才放入防止父类属性覆盖子类属性 // 只有不存在时才放入防止父类属性覆盖子类属性
this.propMap.putIfAbsent(prop.getFieldName(), prop); this.propMap.putIfAbsent(prop.getFieldName(), prop);
} }
@ -191,11 +189,11 @@ public class BeanDesc implements Serializable {
* 查找字段对应的Getter和Setter方法 * 查找字段对应的Getter和Setter方法
* *
* @param field 字段 * @param field 字段
* @param methods 类中所有的方法 * @param gettersOrSetters 类中所有的Getter或Setter方法
* @param ignoreCase 是否忽略大小写匹配 * @param ignoreCase 是否忽略大小写匹配
* @return PropDesc * @return PropDesc
*/ */
private PropDesc findProp(Field field, Method[] methods, boolean ignoreCase) { private PropDesc findProp(Field field, Method[] gettersOrSetters, boolean ignoreCase) {
final String fieldName = field.getName(); final String fieldName = field.getName();
final Class<?> fieldType = field.getType(); final Class<?> fieldType = field.getType();
final boolean isBooleanField = BooleanUtil.isBoolean(fieldType); final boolean isBooleanField = BooleanUtil.isBoolean(fieldType);
@ -203,25 +201,20 @@ public class BeanDesc implements Serializable {
Method getter = null; Method getter = null;
Method setter = null; Method setter = null;
String methodName; String methodName;
Class<?>[] parameterTypes; for (Method method : gettersOrSetters) {
for (Method method : methods) {
parameterTypes = method.getParameterTypes();
if (parameterTypes.length > 1) {
// 多于1个参数说明非Getter或Setter
continue;
}
methodName = method.getName(); methodName = method.getName();
if (parameterTypes.length == 0) { if (method.getParameterCount() == 0) {
// 无参数可能为Getter方法 // 无参数可能为Getter方法
if (isMatchGetter(methodName, fieldName, isBooleanField, ignoreCase)) { if (isMatchGetter(methodName, fieldName, isBooleanField, ignoreCase)) {
// 方法名与字段名匹配则为Getter方法 // 方法名与字段名匹配则为Getter方法
getter = method; getter = method;
} }
} else if (isMatchSetter(methodName, fieldName, isBooleanField, ignoreCase)) { } else if (isMatchSetter(methodName, fieldName, isBooleanField, ignoreCase)) {
// 只有一个参数的情况下方法名与字段名对应匹配则为Setter方法 // setter方法的参数类型和字段类型必须一致或参数类型是字段类型的子类
if(fieldType.isAssignableFrom(method.getParameterTypes()[0])){
setter = method; setter = method;
} }
}
if (null != getter && null != setter) { if (null != getter && null != setter) {
// 如果Getter和Setter方法都找到了不再继续寻找 // 如果Getter和Setter方法都找到了不再继续寻找
break; break;
@ -261,15 +254,6 @@ public class BeanDesc implements Serializable {
handledFieldName = StrUtil.upperFirst(fieldName); handledFieldName = StrUtil.upperFirst(fieldName);
} }
if (false == methodName.startsWith("get") && false == methodName.startsWith("is")) {
// 非标准Getter方法
return false;
}
if ("getclass".equals(methodName)) {
//跳过getClass方法
return false;
}
// 针对Boolean类型特殊检查 // 针对Boolean类型特殊检查
if (isBooleanField) { if (isBooleanField) {
if (fieldName.startsWith("is")) { if (fieldName.startsWith("is")) {

View File

@ -88,9 +88,8 @@ public class BeanUtil {
*/ */
public static boolean hasSetter(Class<?> clazz) { public static boolean hasSetter(Class<?> clazz) {
if (ClassUtil.isNormalClass(clazz)) { if (ClassUtil.isNormalClass(clazz)) {
final Method[] methods = clazz.getMethods(); for (Method method : clazz.getMethods()) {
for (Method method : methods) { if (method.getParameterCount() == 1 && method.getName().startsWith("set")) {
if (method.getParameterTypes().length == 1 && method.getName().startsWith("set")) {
// 检测包含标准的setXXX方法即视为标准的JavaBean // 检测包含标准的setXXX方法即视为标准的JavaBean
return true; return true;
} }
@ -110,7 +109,7 @@ public class BeanUtil {
public static boolean hasGetter(Class<?> clazz) { public static boolean hasGetter(Class<?> clazz) {
if (ClassUtil.isNormalClass(clazz)) { if (ClassUtil.isNormalClass(clazz)) {
for (Method method : clazz.getMethods()) { for (Method method : clazz.getMethods()) {
if (method.getParameterTypes().length == 0) { if (method.getParameterCount() == 0) {
if (method.getName().startsWith("get") || method.getName().startsWith("is")) { if (method.getName().startsWith("get") || method.getName().startsWith("is")) {
return true; return true;
} }
@ -739,7 +738,7 @@ public class BeanUtil {
* @param copyOptions 拷贝选项 {@link CopyOptions} * @param copyOptions 拷贝选项 {@link CopyOptions}
*/ */
public static void copyProperties(Object source, Object target, CopyOptions copyOptions) { public static void copyProperties(Object source, Object target, CopyOptions copyOptions) {
BeanCopier.create(source, target, ObjectUtil.defaultIfNull(copyOptions, CopyOptions.create())).copy(); BeanCopier.create(source, target, ObjectUtil.defaultIfNull(copyOptions, CopyOptions::create)).copy();
} }
/** /**

View File

@ -3,6 +3,7 @@ package cn.hutool.core.collection;
import cn.hutool.core.lang.Opt; import cn.hutool.core.lang.Opt;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import cn.hutool.core.stream.CollectorUtil;
import cn.hutool.core.stream.StreamUtil; import cn.hutool.core.stream.StreamUtil;
import java.util.Collection; import java.util.Collection;
@ -161,7 +162,7 @@ public class CollStreamUtil {
if (CollUtil.isEmpty(collection)) { if (CollUtil.isEmpty(collection)) {
return Collections.emptyMap(); return Collections.emptyMap();
} }
return groupBy(collection, key1, Collectors.groupingBy(key2, Collectors.toList()), isParallel); return groupBy(collection, key1, CollectorUtil.groupingBy(key2, Collectors.toList()), isParallel);
} }
/** /**
@ -236,7 +237,7 @@ public class CollStreamUtil {
if (CollUtil.isEmpty(collection)) { if (CollUtil.isEmpty(collection)) {
return Collections.emptyMap(); return Collections.emptyMap();
} }
return groupBy(collection, key, Collectors.mapping(value, Collectors.toList()), isParallel); return groupBy(collection, key, Collectors.mapping(v -> Opt.ofNullable(v).map(value).orElse(null), Collectors.toList()), isParallel);
} }
/** /**
@ -276,7 +277,7 @@ public class CollStreamUtil {
if (CollUtil.isEmpty(collection)) { if (CollUtil.isEmpty(collection)) {
return Collections.emptyMap(); return Collections.emptyMap();
} }
return StreamUtil.of(collection, isParallel).collect(Collectors.groupingBy(key, downstream)); return StreamUtil.of(collection, isParallel).collect(CollectorUtil.groupingBy(key, downstream));
} }
/** /**

View File

@ -44,7 +44,7 @@ class JavaClassFileManager extends ForwardingJavaFileManager<JavaFileManager> {
*/ */
protected JavaClassFileManager(ClassLoader parent, JavaFileManager fileManager) { protected JavaClassFileManager(ClassLoader parent, JavaFileManager fileManager) {
super(fileManager); super(fileManager);
this.parent = ObjectUtil.defaultIfNull(parent, ClassLoaderUtil.getClassLoader()); this.parent = ObjectUtil.defaultIfNull(parent, ClassLoaderUtil::getClassLoader);
} }
/** /**

View File

@ -89,7 +89,7 @@ public class JavaSourceCompiler {
* @param parent 父类加载器null则使用默认类加载器 * @param parent 父类加载器null则使用默认类加载器
*/ */
private JavaSourceCompiler(ClassLoader parent) { private JavaSourceCompiler(ClassLoader parent) {
this.parentClassLoader = ObjectUtil.defaultIfNull(parent, ClassLoaderUtil.getClassLoader()); this.parentClassLoader = ObjectUtil.defaultIfNull(parent, ClassLoaderUtil::getClassLoader);
} }
/** /**

View File

@ -227,7 +227,7 @@ public class TemporalAccessorConverter extends AbstractConverter<TemporalAccesso
return instant; return instant;
} }
zoneId = ObjectUtil.defaultIfNull(zoneId, ZoneId.systemDefault()); zoneId = ObjectUtil.defaultIfNull(zoneId, ZoneId::systemDefault);
TemporalAccessor result = null; TemporalAccessor result = null;
if (LocalDateTime.class.equals(this.targetType)) { if (LocalDateTime.class.equals(this.targetType)) {

View File

@ -143,7 +143,7 @@ public class DateTime extends Date {
* @since 4.1.2 * @since 4.1.2
*/ */
public DateTime(Date date, TimeZone timeZone) { public DateTime(Date date, TimeZone timeZone) {
this(ObjectUtil.defaultIfNull(date, new Date()).getTime(), timeZone); this(ObjectUtil.defaultIfNull(date, Date::new).getTime(), timeZone);
} }
/** /**
@ -216,7 +216,7 @@ public class DateTime extends Date {
*/ */
public DateTime(long timeMillis, TimeZone timeZone) { public DateTime(long timeMillis, TimeZone timeZone) {
super(timeMillis); super(timeMillis);
this.timeZone = ObjectUtil.defaultIfNull(timeZone, TimeZone.getDefault()); this.timeZone = ObjectUtil.defaultIfNull(timeZone, TimeZone::getDefault);
} }
/** /**
@ -913,7 +913,7 @@ public class DateTime extends Date {
* @since 4.1.2 * @since 4.1.2
*/ */
public DateTime setTimeZone(TimeZone timeZone) { public DateTime setTimeZone(TimeZone timeZone) {
this.timeZone = ObjectUtil.defaultIfNull(timeZone, TimeZone.getDefault()); this.timeZone = ObjectUtil.defaultIfNull(timeZone, TimeZone::getDefault);
return this; return this;
} }

View File

@ -88,7 +88,7 @@ public class LocalDateTimeUtil {
return null; return null;
} }
return LocalDateTime.ofInstant(instant, ObjectUtil.defaultIfNull(zoneId, ZoneId.systemDefault())); return LocalDateTime.ofInstant(instant, ObjectUtil.defaultIfNull(zoneId, ZoneId::systemDefault));
} }
/** /**
@ -103,7 +103,7 @@ public class LocalDateTimeUtil {
return null; return null;
} }
return of(instant, ObjectUtil.defaultIfNull(timeZone, TimeZone.getDefault()).toZoneId()); return of(instant, ObjectUtil.defaultIfNull(timeZone, TimeZone::getDefault).toZoneId());
} }
/** /**

View File

@ -117,7 +117,7 @@ public class FastByteArrayOutputStream extends OutputStream {
*/ */
public String toString(Charset charset) { public String toString(Charset charset) {
return new String(toByteArray(), return new String(toByteArray(),
ObjectUtil.defaultIfNull(charset, CharsetUtil.defaultCharset())); ObjectUtil.defaultIfNull(charset, CharsetUtil::defaultCharset));
} }
} }

View File

@ -16,7 +16,6 @@ import java.io.InputStream;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.Collection;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
@ -165,7 +164,7 @@ public class FileWriter extends FileWrapper {
* @return 目标文件 * @return 目标文件
* @throws IORuntimeException IO异常 * @throws IORuntimeException IO异常
*/ */
public <T> File writeLines(Collection<T> list) throws IORuntimeException { public <T> File writeLines(Iterable<T> list) throws IORuntimeException {
return writeLines(list, false); return writeLines(list, false);
} }
@ -177,7 +176,7 @@ public class FileWriter extends FileWrapper {
* @return 目标文件 * @return 目标文件
* @throws IORuntimeException IO异常 * @throws IORuntimeException IO异常
*/ */
public <T> File appendLines(Collection<T> list) throws IORuntimeException { public <T> File appendLines(Iterable<T> list) throws IORuntimeException {
return writeLines(list, true); return writeLines(list, true);
} }
@ -190,7 +189,7 @@ public class FileWriter extends FileWrapper {
* @return 目标文件 * @return 目标文件
* @throws IORuntimeException IO异常 * @throws IORuntimeException IO异常
*/ */
public <T> File writeLines(Collection<T> list, boolean isAppend) throws IORuntimeException { public <T> File writeLines(Iterable<T> list, boolean isAppend) throws IORuntimeException {
return writeLines(list, null, isAppend); return writeLines(list, null, isAppend);
} }
@ -205,12 +204,22 @@ public class FileWriter extends FileWrapper {
* @throws IORuntimeException IO异常 * @throws IORuntimeException IO异常
* @since 3.1.0 * @since 3.1.0
*/ */
public <T> File writeLines(Collection<T> list, LineSeparator lineSeparator, boolean isAppend) throws IORuntimeException { public <T> File writeLines(Iterable<T> list, LineSeparator lineSeparator, boolean isAppend) throws IORuntimeException {
try (PrintWriter writer = getPrintWriter(isAppend)) { try (PrintWriter writer = getPrintWriter(isAppend)) {
boolean isFirst = true;
for (T t : list) { for (T t : list) {
if (null != t) { if (null != t) {
writer.print(t); if(isFirst){
isFirst = false;
if(isAppend && FileUtil.isNotEmpty(this.file)){
// 追加模式下且文件非空补充换行符
printNewLine(writer, lineSeparator); printNewLine(writer, lineSeparator);
}
} else{
printNewLine(writer, lineSeparator);
}
writer.print(t);
writer.flush(); writer.flush();
} }
} }

View File

@ -69,7 +69,7 @@ public class ClassPathResource extends UrlResource {
this.path = path; this.path = path;
this.name = StrUtil.isBlank(path) ? null : FileUtil.getName(path); this.name = StrUtil.isBlank(path) ? null : FileUtil.getName(path);
this.classLoader = ObjectUtil.defaultIfNull(classLoader, ClassUtil.getClassLoader()); this.classLoader = ObjectUtil.defaultIfNull(classLoader, ClassUtil::getClassLoader);
this.clazz = clazz; this.clazz = clazz;
initUrl(); initUrl();
} }

View File

@ -60,7 +60,7 @@ public class FileResource implements Resource, Serializable {
public FileResource(File file, String fileName) { public FileResource(File file, String fileName) {
Assert.notNull(file, "File must be not null !"); Assert.notNull(file, "File must be not null !");
this.file = file; this.file = file;
this.name = ObjectUtil.defaultIfNull(fileName, file.getName()); this.name = ObjectUtil.defaultIfNull(fileName, file::getName);
} }
// ----------------------------------------------------------------------- Constructor end // ----------------------------------------------------------------------- Constructor end

View File

@ -36,7 +36,7 @@ public class UrlResource implements Resource, Serializable{
*/ */
public UrlResource(URL url, String name) { public UrlResource(URL url, String name) {
this.url = url; this.url = url;
this.name = ObjectUtil.defaultIfNull(name, (null != url) ? FileUtil.getName(url.getPath()) : null); this.name = ObjectUtil.defaultIfNull(name, () -> (null != url ? FileUtil.getName(url.getPath()) : null));
} }
/** /**

View File

@ -20,9 +20,10 @@ public interface RegexPool {
*/ */
String WORD = "[a-zA-Z]+"; String WORD = "[a-zA-Z]+";
/** /**
* 单个中文汉字 * 单个中文汉字<br>
* 参照维基百科汉字Unicode范围(https://zh.wikipedia.org/wiki/%E6%B1%89%E5%AD%97 页面右侧)
*/ */
String CHINESE = "[\u4E00-\u9FFF]"; String CHINESE = "[\u2E80-\u2EFF\u2F00-\u2FDF\u31C0-\u31EF\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF\uD840\uDC00-\uD869\uDEDF\uD869\uDF00-\uD86D\uDF3F\uD86D\uDF40-\uD86E\uDC1F\uD86E\uDC20-\uD873\uDEAF\uD87E\uDC00-\uD87E\uDE1F]";
/** /**
* 中文汉字 * 中文汉字
*/ */

View File

@ -30,8 +30,8 @@ public class ResourceClassLoader<T extends Resource> extends SecureClassLoader {
* @param resourceMap 资源map * @param resourceMap 资源map
*/ */
public ResourceClassLoader(ClassLoader parentClassLoader, Map<String, T> resourceMap) { public ResourceClassLoader(ClassLoader parentClassLoader, Map<String, T> resourceMap) {
super(ObjectUtil.defaultIfNull(parentClassLoader, ClassLoaderUtil.getClassLoader())); super(ObjectUtil.defaultIfNull(parentClassLoader, ClassLoaderUtil::getClassLoader));
this.resourceMap = ObjectUtil.defaultIfNull(resourceMap, new HashMap<>()); this.resourceMap = ObjectUtil.defaultIfNull(resourceMap, HashMap::new);
this.cacheClassMap = new HashMap<>(); this.cacheClassMap = new HashMap<>();
} }

View File

@ -0,0 +1,51 @@
package cn.hutool.core.lang.hash;
import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.util.StrUtil;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* Ketama算法用于在一致性Hash中快速定位服务器位置
*
* @author looly
* @since 5.7.20
*/
public class KetamaHash implements Hash64<String>, Hash32<String> {
@Override
public long hash64(String key) {
byte[] bKey = md5(key);
return ((long) (bKey[3] & 0xFF) << 24)
| ((long) (bKey[2] & 0xFF) << 16)
| ((long) (bKey[1] & 0xFF) << 8)
| (bKey[0] & 0xFF);
}
@Override
public int hash32(String key) {
return (int) (hash64(key) & 0xffffffffL);
}
@Override
public Number hash(String key) {
return hash64(key);
}
/**
* 计算MD5值使用UTF-8编码
*
* @param key 被计算的键
* @return MD5值
*/
private static byte[] md5(String key) {
final MessageDigest md5;
try {
md5 = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new UtilException("MD5 algorithm not suooport!", e);
}
return md5.digest(StrUtil.utf8Bytes(key));
}
}

View File

@ -12,6 +12,7 @@ import java.lang.reflect.Method;
/** /**
* 方法句柄{@link MethodHandle}封装工具类<br> * 方法句柄{@link MethodHandle}封装工具类<br>
* 方法句柄是一个有类型的可以直接执行的指向底层方法构造器field等的引用可以简单理解为函数指针它是一种更加底层的查找调整和调用方法的机制
* 参考 * 参考
* <ul> * <ul>
* <li>https://stackoverflow.com/questions/22614746/how-do-i-invoke-java-8-default-methods-reflectively</li> * <li>https://stackoverflow.com/questions/22614746/how-do-i-invoke-java-8-default-methods-reflectively</li>
@ -113,7 +114,7 @@ public class MethodHandleUtil {
} }
/** /**
* 执行接口或对象中的方法<br> * 执行接口或对象中的特殊方法privatestatic等<br>
* *
* <pre class="code"> * <pre class="code">
* interface Duck { * interface Duck {
@ -159,7 +160,7 @@ public class MethodHandleUtil {
} }
/** /**
* 执行接口或对象中的方法<br> * 执行接口或对象中的特殊方法privatestatic等<br>
* *
* <pre class="code"> * <pre class="code">
* interface Duck { * interface Duck {

View File

@ -1,6 +1,7 @@
package cn.hutool.core.map; package cn.hutool.core.map;
import java.util.Map; import java.util.Map;
import java.util.function.BiFunction;
/** /**
* 自定义键的Map默认HashMap实现 * 自定义键的Map默认HashMap实现
@ -67,6 +68,31 @@ public abstract class CustomKeyMap<K, V> extends MapWrapper<K, V> {
return super.replace((K) customKey(key), value); return super.replace((K) customKey(key), value);
} }
//---------------------------------------------------------------------------- Override default methods start
@Override
public V getOrDefault(Object key, V defaultValue) {
return super.getOrDefault(customKey(key), defaultValue);
}
@Override
public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
//noinspection unchecked
return super.computeIfPresent((K) customKey(key), remappingFunction);
}
@Override
public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
//noinspection unchecked
return super.compute((K) customKey(key), remappingFunction);
}
@Override
public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
//noinspection unchecked
return super.merge((K) customKey(key), value, remappingFunction);
}
//---------------------------------------------------------------------------- Override default methods end
/** /**
* 自定义键 * 自定义键
* *

View File

@ -16,7 +16,6 @@ import java.util.function.Function;
* @param <K> 键类型 * @param <K> 键类型
* @param <V> 值类型 * @param <V> 值类型
* @author looly * @author looly
* @author looly
* @since 4.3.3 * @since 4.3.3
*/ */
public class MapWrapper<K, V> implements Map<K, V>, Iterable<Map.Entry<K, V>>, Serializable, Cloneable { public class MapWrapper<K, V> implements Map<K, V>, Iterable<Map.Entry<K, V>>, Serializable, Cloneable {
@ -179,6 +178,7 @@ public class MapWrapper<K, V> implements Map<K, V>, Iterable<Map.Entry<K, V>>, S
return raw.computeIfAbsent(key, mappingFunction); return raw.computeIfAbsent(key, mappingFunction);
} }
// 重写默认方法的意义在于如果被包装的Map自定义了这些默认方法包装类就可以保持这些行为的一致性
//---------------------------------------------------------------------------- Override default methods start //---------------------------------------------------------------------------- Override default methods start
@Override @Override
public V getOrDefault(Object key, V defaultValue) { public V getOrDefault(Object key, V defaultValue) {

View File

@ -1,10 +1,17 @@
package cn.hutool.core.stream; package cn.hutool.core.stream;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.StringJoiner; import java.util.StringJoiner;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector; import java.util.stream.Collector;
/** /**
@ -61,4 +68,70 @@ public class CollectorUtil {
Collections.emptySet() Collections.emptySet()
); );
} }
/**
* 提供对null值友好的groupingBy操作的{@link Collector}实现可指定map类型
*
* @param classifier 分组依据
* @param mapFactory 提供的map
* @param downstream 下游操作
* @param <T> 实体类型
* @param <K> 实体中的分组依据对应类型也是Map中key的类型
* @param <D> 下游操作对应返回类型也是Map中value的类型
* @param <A> 下游操作在进行中间操作时对应类型
* @param <M> 最后返回结果Map类型
* @return {@link Collector}
*/
public static <T, K, D, A, M extends Map<K, D>> Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier,
Supplier<M> mapFactory,
Collector<? super T, A, D> downstream) {
Supplier<A> downstreamSupplier = downstream.supplier();
BiConsumer<A, ? super T> downstreamAccumulator = downstream.accumulator();
BiConsumer<Map<K, A>, T> accumulator = (m, t) -> {
K key = Opt.ofNullable(t).map(classifier).orElse(null);
A container = m.computeIfAbsent(key, k -> downstreamSupplier.get());
downstreamAccumulator.accept(container, t);
};
BinaryOperator<Map<K, A>> merger = (m1, m2) -> {
for (Map.Entry<K, A> e : m2.entrySet()) {
m1.merge(e.getKey(), e.getValue(), downstream.combiner());
}
return m1;
};
@SuppressWarnings("unchecked")
Supplier<Map<K, A>> mangledFactory = (Supplier<Map<K, A>>) mapFactory;
if (downstream.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) {
return new SimpleCollector<>(mangledFactory, accumulator, merger, Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH)));
} else {
@SuppressWarnings("unchecked")
Function<A, A> downstreamFinisher = (Function<A, A>) downstream.finisher();
Function<Map<K, A>, M> finisher = intermediate -> {
intermediate.replaceAll((k, v) -> downstreamFinisher.apply(v));
@SuppressWarnings("unchecked")
M castResult = (M) intermediate;
return castResult;
};
return new SimpleCollector<>(mangledFactory, accumulator, merger, finisher, Collections.emptySet());
}
}
/**
* 提供对null值友好的groupingBy操作的{@link Collector}实现
*
* @param classifier 分组依据
* @param downstream 下游操作
* @param <T> 实体类型
* @param <K> 实体中的分组依据对应类型也是Map中key的类型
* @param <D> 下游操作对应返回类型也是Map中value的类型
* @param <A> 下游操作在进行中间操作时对应类型
* @return {@link Collector}
*/
public static <T, K, A, D>
Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,
Collector<? super T, A, D> downstream) {
return groupingBy(classifier, HashMap::new, downstream);
}
} }

View File

@ -49,7 +49,7 @@ public class CsvBaseReader implements Serializable {
* @param config 配置项 * @param config 配置项
*/ */
public CsvBaseReader(CsvReadConfig config) { public CsvBaseReader(CsvReadConfig config) {
this.config = ObjectUtil.defaultIfNull(config, CsvReadConfig.defaultConfig()); this.config = ObjectUtil.defaultIfNull(config, CsvReadConfig::defaultConfig);
} }
//--------------------------------------------------------------------------------------------- Constructor end //--------------------------------------------------------------------------------------------- Constructor end

View File

@ -80,7 +80,7 @@ public final class CsvParser extends ComputeIter<CsvRow> implements Closeable, S
*/ */
public CsvParser(final Reader reader, CsvReadConfig config) { public CsvParser(final Reader reader, CsvReadConfig config) {
this.reader = Objects.requireNonNull(reader, "reader must not be null"); this.reader = Objects.requireNonNull(reader, "reader must not be null");
this.config = ObjectUtil.defaultIfNull(config, CsvReadConfig.defaultConfig()); this.config = ObjectUtil.defaultIfNull(config, CsvReadConfig::defaultConfig);
} }
/** /**

View File

@ -149,7 +149,7 @@ public final class CsvWriter implements Closeable, Flushable, Serializable {
*/ */
public CsvWriter(Writer writer, CsvWriteConfig config) { public CsvWriter(Writer writer, CsvWriteConfig config) {
this.writer = (writer instanceof BufferedWriter) ? writer : new BufferedWriter(writer); this.writer = (writer instanceof BufferedWriter) ? writer : new BufferedWriter(writer);
this.config = ObjectUtil.defaultIfNull(config, CsvWriteConfig.defaultConfig()); this.config = ObjectUtil.defaultIfNull(config, CsvWriteConfig::defaultConfig);
} }
// --------------------------------------------------------------------------------------------------- Constructor end // --------------------------------------------------------------------------------------------------- Constructor end

View File

@ -242,7 +242,7 @@ public class ExecutorBuilder implements Builder<ThreadPoolExecutor> {
workQueue = (corePoolSize <= 0) ? new SynchronousQueue<>() : new LinkedBlockingQueue<>(DEFAULT_QUEUE_CAPACITY); workQueue = (corePoolSize <= 0) ? new SynchronousQueue<>() : new LinkedBlockingQueue<>(DEFAULT_QUEUE_CAPACITY);
} }
final ThreadFactory threadFactory = (null != builder.threadFactory) ? builder.threadFactory : Executors.defaultThreadFactory(); final ThreadFactory threadFactory = (null != builder.threadFactory) ? builder.threadFactory : Executors.defaultThreadFactory();
RejectedExecutionHandler handler = ObjectUtil.defaultIfNull(builder.handler, new ThreadPoolExecutor.AbortPolicy()); RejectedExecutionHandler handler = ObjectUtil.defaultIfNull(builder.handler, ThreadPoolExecutor.AbortPolicy::new);
final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(// final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(//
corePoolSize, // corePoolSize, //

View File

@ -295,6 +295,22 @@ public class ObjectUtil {
return isNull(object) ? defaultValue : object; return isNull(object) ? defaultValue : object;
} }
/**
* 如果被检查对象为 {@code null} 返回默认值 defaultValueSupplier 提供否则直接返回
*
* @param source 被检查对象
* @param defaultValueSupplier 默认值提供者
* @param <T> 对象类型
* @return 被检查对象为{@code null}返回默认值否则返回自定义handle处理后的返回值
* @throws NullPointerException {@code defaultValueSupplier == null} 抛出
* @since 5.7.20
*/
public static <T> T defaultIfNull(T source, Supplier<? extends T> defaultValueSupplier) {
if (isNull(source)) {
return defaultValueSupplier.get();
}
return source;
}
/** /**
* 如果给定对象为{@code null} 返回默认值, 如果不为null 返回自定义handle处理后的返回值 * 如果给定对象为{@code null} 返回默认值, 如果不为null 返回自定义handle处理后的返回值
@ -351,6 +367,23 @@ public class ObjectUtil {
return StrUtil.isEmpty(str) ? defaultValue : str; return StrUtil.isEmpty(str) ? defaultValue : str;
} }
/**
* 如果被检查对象为 {@code null} "" 返回默认值 defaultValueSupplier 提供否则直接返回
*
* @param str 被检查对象
* @param defaultValueSupplier 默认值提供者
* @param <T> 对象类型必须实现CharSequence接口
* @return 被检查对象为{@code null}返回默认值否则返回自定义handle处理后的返回值
* @throws NullPointerException {@code defaultValueSupplier == null} 抛出
* @since 5.7.20
*/
public static <T extends CharSequence> T defaultIfEmpty(T str, Supplier<? extends T> defaultValueSupplier) {
if (StrUtil.isEmpty(str)) {
return defaultValueSupplier.get();
}
return str;
}
/** /**
* 如果给定对象为{@code null}或者""或者空白符返回默认值 * 如果给定对象为{@code null}或者""或者空白符返回默认值
* *
@ -372,6 +405,23 @@ public class ObjectUtil {
return StrUtil.isBlank(str) ? defaultValue : str; return StrUtil.isBlank(str) ? defaultValue : str;
} }
/**
* 如果被检查对象为 {@code null} "" 空白字符串时返回默认值 defaultValueSupplier 提供否则直接返回
*
* @param str 被检查对象
* @param defaultValueSupplier 默认值提供者
* @param <T> 对象类型必须实现CharSequence接口
* @return 被检查对象为{@code null}返回默认值否则返回自定义handle处理后的返回值
* @throws NullPointerException {@code defaultValueSupplier == null} 抛出
* @since 5.7.20
*/
public static <T extends CharSequence> T defaultIfBlank(T str, Supplier<? extends T> defaultValueSupplier) {
if (StrUtil.isBlank(str)) {
return defaultValueSupplier.get();
}
return str;
}
/** /**
* 克隆对象<br> * 克隆对象<br>
* 如果对象实现Cloneable接口调用其clone方法<br> * 如果对象实现Cloneable接口调用其clone方法<br>

View File

@ -344,6 +344,17 @@ public class ReflectUtil {
} }
} }
/**
* 是否为父类引用字段<br>
* 当字段所在类是对象子类时对象中定义的非static的class会自动生成一个以"this$0"为名称的字段指向父类对象
* @param field 字段
* @return 是否为父类引用字段
* @since 5.7.20
*/
public static boolean isOuterClassField(Field field){
return "this$0".equals(field.getName());
}
// --------------------------------------------------------------------------------------------------------- method // --------------------------------------------------------------------------------------------------------- method
/** /**
@ -673,11 +684,12 @@ public class ReflectUtil {
* @return 是否为equals方法 * @return 是否为equals方法
*/ */
public static boolean isEqualsMethod(Method method) { public static boolean isEqualsMethod(Method method) {
if (method == null || false == "equals".equals(method.getName())) { if (method == null ||
1 != method.getParameterCount() ||
false == "equals".equals(method.getName())) {
return false; return false;
} }
final Class<?>[] paramTypes = method.getParameterTypes(); return (method.getParameterTypes()[0] == Object.class);
return (1 == paramTypes.length && paramTypes[0] == Object.class);
} }
/** /**
@ -712,9 +724,67 @@ public class ReflectUtil {
* @since 5.1.1 * @since 5.1.1
*/ */
public static boolean isEmptyParam(Method method) { public static boolean isEmptyParam(Method method) {
return method.getParameterTypes().length == 0; return method.getParameterCount() == 0;
} }
/**
* 检查给定方法是否为Getter或者Setter方法规则为<br>
* <ul>
* <li>方法参数必须为0个或1个</li>
* <li>如果是无参方法则判断是否以getis开头</li>
* <li>如果方法参数1个则判断是否以set开头</li>
* </ul>
*
* @param method 方法
* @return 是否为Getter或者Setter方法
* @since 5.7.20
*/
public static boolean isGetterOrSetterIgnoreCase(Method method) {
return isGetterOrSetter(method, true);
}
/**
* 检查给定方法是否为Getter或者Setter方法规则为<br>
* <ul>
* <li>方法参数必须为0个或1个</li>
* <li>方法名称不能是getClass</li>
* <li>如果是无参方法则判断是否以getis开头</li>
* <li>如果方法参数1个则判断是否以set开头</li>
* </ul>
*
* @param method 方法
* @param ignoreCase 是否忽略方法名的大小写
* @return 是否为Getter或者Setter方法
* @since 5.7.20
*/
public static boolean isGetterOrSetter(Method method, boolean ignoreCase) {
if (null == method) {
return false;
}
// 参数个数必须为0或1
final int parameterCount = method.getParameterCount();
if (parameterCount > 1) {
return false;
}
String name = method.getName();
// 跳过getClass这个特殊方法
if("getClass".equals(name)){
return false;
}
if(ignoreCase){
name = name.toLowerCase();
}
switch (parameterCount) {
case 0:
return name.startsWith("get") || name.startsWith("is");
case 1:
return name.startsWith("set");
default:
return false;
}
}
// --------------------------------------------------------------------------------------------------------- newInstance // --------------------------------------------------------------------------------------------------------- newInstance
/** /**

View File

@ -76,7 +76,7 @@ public class ServiceLoaderUtil {
* @return 服务接口实现列表 * @return 服务接口实现列表
*/ */
public static <T> ServiceLoader<T> load(Class<T> clazz, ClassLoader loader) { public static <T> ServiceLoader<T> load(Class<T> clazz, ClassLoader loader) {
return ServiceLoader.load(clazz, ObjectUtil.defaultIfNull(loader, ClassLoaderUtil.getClassLoader())); return ServiceLoader.load(clazz, ObjectUtil.defaultIfNull(loader, ClassLoaderUtil::getClassLoader));
} }
/** /**

View File

@ -0,0 +1,33 @@
package cn.hutool.core.bean;
import lombok.Data;
import org.junit.Assert;
import org.junit.Test;
/**
* https://github.com/dromara/hutool/issues/2082<br>
* 当setXXX有重载方法的时候BeanDesc中会匹配到重载方法增加类型检查来规避之
*/
public class Issue2082Test {
@Test
public void toBeanTest() {
TestBean2 testBean2 = new TestBean2();
TestBean test = BeanUtil.toBean(testBean2, TestBean.class);
Assert.assertNull(test.getId());
}
@Data
static class TestBean {
private Long id;
public void setId(String id) {
this.id = Long.valueOf(id);
}
}
@Data
static class TestBean2 {
private String id;
}
}

View File

@ -203,6 +203,20 @@ public class CollStreamUtilTest {
groupThen); groupThen);
// 总之如果你是想要group分组后还要进行别的操作用它就对了 // 总之如果你是想要group分组后还要进行别的操作用它就对了
// 并且对null值进行了友好处理例如
List<Student> students = Arrays.asList(null, null, new Student(1, 1, 1, "张三"),
new Student(1, 2, 1, "李四"));
Map<Long, List<Student>> termIdStudentsMap = CollStreamUtil.groupBy(students, Student::getTermId, Collectors.toList());
Map<Long, List<Student>> termIdStudentsCompareMap = new HashMap<>();
termIdStudentsCompareMap.put(null, Arrays.asList(null, null));
termIdStudentsCompareMap.put(1L, Arrays.asList(new Student(1L, 1, 1, "张三"), new Student(1L, 2, 1, "李四")));
Assert.assertEquals(termIdStudentsCompareMap, termIdStudentsMap);
Map<Long, Long> termIdCountMap = CollStreamUtil.groupBy(students, Student::getTermId, Collectors.counting());
Map<Long, Long> termIdCountCompareMap = new HashMap<>();
termIdCountCompareMap.put(null, 2L);
termIdCountCompareMap.put(1L, 2L);
Assert.assertEquals(termIdCountCompareMap, termIdCountMap);
} }

View File

@ -162,9 +162,18 @@ public class ValidatorTest {
@Test @Test
public void isChineseTest(){ public void isChineseTest(){
Assert.assertTrue(Validator.isChinese("全都是中文")); Assert.assertTrue(Validator.isChinese("全都是中文"));
Assert.assertTrue(Validator.isChinese("㐓㐘"));
Assert.assertFalse(Validator.isChinese("not全都是中文")); Assert.assertFalse(Validator.isChinese("not全都是中文"));
} }
@Test
public void hasChineseTest() {
Assert.assertTrue(Validator.hasChinese("黄单桑米"));
Assert.assertTrue(Validator.hasChinese("Kn 四兄弟"));
Assert.assertTrue(Validator.hasChinese("\uD840\uDDA3"));
Assert.assertFalse(Validator.hasChinese("Abc"));
}
@Test @Test
public void isUUIDTest(){ public void isUUIDTest(){
Assert.assertTrue(Validator.isUUID(IdUtil.randomUUID())); Assert.assertTrue(Validator.isUUID(IdUtil.randomUUID()));

View File

@ -1,5 +1,6 @@
package cn.hutool.core.map; package cn.hutool.core.map;
import cn.hutool.core.lang.Pair;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
@ -20,4 +21,16 @@ public class CaseInsensitiveMapTest {
Assert.assertEquals("OK", map.get("aaa")); Assert.assertEquals("OK", map.get("aaa"));
Assert.assertEquals("OK", map.get("AAA")); Assert.assertEquals("OK", map.get("AAA"));
} }
@Test
public void mergeTest(){
//https://github.com/dromara/hutool/issues/2086
Pair<String, String> b = new Pair<>("a", "value");
Pair<String, String> a = new Pair<>("A", "value");
final CaseInsensitiveMap<Object, Object> map = new CaseInsensitiveMap<>();
map.merge(b.getKey(), b.getValue(), (A, B) -> A);
map.merge(a.getKey(), a.getValue(), (A, B) -> A);
Assert.assertEquals(1, map.size());
}
} }

View File

@ -9,7 +9,7 @@
<parent> <parent>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId> <artifactId>hutool-parent</artifactId>
<version>5.7.19</version> <version>5.7.20-SNAPSHOT</version>
</parent> </parent>
<artifactId>hutool-cron</artifactId> <artifactId>hutool-cron</artifactId>

View File

@ -9,7 +9,7 @@
<parent> <parent>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId> <artifactId>hutool-parent</artifactId>
<version>5.7.19</version> <version>5.7.20-SNAPSHOT</version>
</parent> </parent>
<artifactId>hutool-crypto</artifactId> <artifactId>hutool-crypto</artifactId>

View File

@ -3,7 +3,6 @@ package cn.hutool.crypto;
import cn.hutool.core.codec.Base64; import cn.hutool.core.codec.Base64;
import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Validator; import cn.hutool.core.lang.Validator;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.HexUtil; import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.asymmetric.AsymmetricAlgorithm; import cn.hutool.crypto.asymmetric.AsymmetricAlgorithm;
@ -830,7 +829,7 @@ public class SecureUtil {
* @since 3.3.0 * @since 3.3.0
*/ */
public static Sign sign(SignAlgorithm algorithm) { public static Sign sign(SignAlgorithm algorithm) {
return new Sign(algorithm); return SignUtil.sign(algorithm);
} }
/** /**
@ -845,7 +844,7 @@ public class SecureUtil {
* @since 3.3.0 * @since 3.3.0
*/ */
public static Sign sign(SignAlgorithm algorithm, String privateKeyBase64, String publicKeyBase64) { public static Sign sign(SignAlgorithm algorithm, String privateKeyBase64, String publicKeyBase64) {
return new Sign(algorithm, privateKeyBase64, publicKeyBase64); return SignUtil.sign(algorithm, privateKeyBase64, publicKeyBase64);
} }
/** /**
@ -860,7 +859,7 @@ public class SecureUtil {
* @since 3.3.0 * @since 3.3.0
*/ */
public static Sign sign(SignAlgorithm algorithm, byte[] privateKey, byte[] publicKey) { public static Sign sign(SignAlgorithm algorithm, byte[] privateKey, byte[] publicKey) {
return new Sign(algorithm, privateKey, publicKey); return SignUtil.sign(algorithm, privateKey, publicKey);
} }
/** /**
@ -875,7 +874,7 @@ public class SecureUtil {
* @since 4.0.1 * @since 4.0.1
*/ */
public static String signParams(SymmetricCrypto crypto, Map<?, ?> params, String... otherParams) { public static String signParams(SymmetricCrypto crypto, Map<?, ?> params, String... otherParams) {
return signParams(crypto, params, StrUtil.EMPTY, StrUtil.EMPTY, true, otherParams); return SignUtil.signParams(crypto, params, otherParams);
} }
/** /**
@ -893,7 +892,7 @@ public class SecureUtil {
*/ */
public static String signParams(SymmetricCrypto crypto, Map<?, ?> params, String separator, public static String signParams(SymmetricCrypto crypto, Map<?, ?> params, String separator,
String keyValueSeparator, boolean isIgnoreNull, String... otherParams) { String keyValueSeparator, boolean isIgnoreNull, String... otherParams) {
return crypto.encryptHex(MapUtil.sortJoin(params, separator, keyValueSeparator, isIgnoreNull, otherParams)); return SignUtil.signParams(crypto, params, separator, keyValueSeparator, isIgnoreNull, otherParams);
} }
/** /**
@ -907,7 +906,7 @@ public class SecureUtil {
* @since 4.0.1 * @since 4.0.1
*/ */
public static String signParamsMd5(Map<?, ?> params, String... otherParams) { public static String signParamsMd5(Map<?, ?> params, String... otherParams) {
return signParams(DigestAlgorithm.MD5, params, otherParams); return SignUtil.signParamsMd5(params, otherParams);
} }
/** /**
@ -921,7 +920,7 @@ public class SecureUtil {
* @since 4.0.8 * @since 4.0.8
*/ */
public static String signParamsSha1(Map<?, ?> params, String... otherParams) { public static String signParamsSha1(Map<?, ?> params, String... otherParams) {
return signParams(DigestAlgorithm.SHA1, params, otherParams); return SignUtil.signParamsSha1(params, otherParams);
} }
/** /**
@ -935,7 +934,7 @@ public class SecureUtil {
* @since 4.0.1 * @since 4.0.1
*/ */
public static String signParamsSha256(Map<?, ?> params, String... otherParams) { public static String signParamsSha256(Map<?, ?> params, String... otherParams) {
return signParams(DigestAlgorithm.SHA256, params, otherParams); return SignUtil.signParamsSha256(params, otherParams);
} }
/** /**
@ -950,7 +949,7 @@ public class SecureUtil {
* @since 4.0.1 * @since 4.0.1
*/ */
public static String signParams(DigestAlgorithm digestAlgorithm, Map<?, ?> params, String... otherParams) { public static String signParams(DigestAlgorithm digestAlgorithm, Map<?, ?> params, String... otherParams) {
return signParams(digestAlgorithm, params, StrUtil.EMPTY, StrUtil.EMPTY, true, otherParams); return SignUtil.signParams(digestAlgorithm, params, otherParams);
} }
/** /**
@ -968,7 +967,7 @@ public class SecureUtil {
*/ */
public static String signParams(DigestAlgorithm digestAlgorithm, Map<?, ?> params, String separator, public static String signParams(DigestAlgorithm digestAlgorithm, Map<?, ?> params, String separator,
String keyValueSeparator, boolean isIgnoreNull, String... otherParams) { String keyValueSeparator, boolean isIgnoreNull, String... otherParams) {
return new Digester(digestAlgorithm).digestHex(MapUtil.sortJoin(params, separator, keyValueSeparator, isIgnoreNull, otherParams)); return SignUtil.signParams(digestAlgorithm, params, separator, keyValueSeparator, isIgnoreNull, otherParams);
} }
/** /**

View File

@ -0,0 +1,176 @@
package cn.hutool.crypto;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.asymmetric.Sign;
import cn.hutool.crypto.asymmetric.SignAlgorithm;
import cn.hutool.crypto.digest.DigestAlgorithm;
import cn.hutool.crypto.digest.Digester;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
import java.util.Map;
/**
* 签名工具类<br>
* 封装包括
* <ul>
* <li>非堆成签名签名算法支持见{@link SignAlgorithm}</li>
* <li>对称签名支持Map类型参数排序后签名</li>
* <li>摘要签名支持Map类型参数排序后签名签名方法见{@link DigestAlgorithm}</li>
* </ul>
*
* @author looly
* @since 5.7.20
*/
public class SignUtil {
/**
* 创建签名算法对象<br>
* 生成新的私钥公钥对
*
* @param algorithm 签名算法
* @return {@link Sign}
* @since 3.3.0
*/
public static Sign sign(SignAlgorithm algorithm) {
return new Sign(algorithm);
}
/**
* 创建签名算法对象<br>
* 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
* 私钥和公钥可以单独传入一个如此则只能使用此钥匙来做签名或验证
*
* @param algorithm 签名算法
* @param privateKeyBase64 私钥Base64
* @param publicKeyBase64 公钥Base64
* @return {@link Sign}
* @since 3.3.0
*/
public static Sign sign(SignAlgorithm algorithm, String privateKeyBase64, String publicKeyBase64) {
return new Sign(algorithm, privateKeyBase64, publicKeyBase64);
}
/**
* 创建Sign算法对象<br>
* 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
* 私钥和公钥可以单独传入一个如此则只能使用此钥匙来做签名或验证
*
* @param algorithm 算法枚举
* @param privateKey 私钥
* @param publicKey 公钥
* @return {@link Sign}
* @since 3.3.0
*/
public static Sign sign(SignAlgorithm algorithm, byte[] privateKey, byte[] publicKey) {
return new Sign(algorithm, privateKey, publicKey);
}
/**
* 对参数做签名<br>
* 参数签名为对Map参数按照key的顺序排序后拼接为字符串然后根据提供的签名算法生成签名字符串<br>
* 拼接后的字符串键值对之间无符号键值对之间无符号忽略null值
*
* @param crypto 对称加密算法
* @param params 参数
* @param otherParams 其它附加参数字符串例如密钥
* @return 签名
* @since 4.0.1
*/
public static String signParams(SymmetricCrypto crypto, Map<?, ?> params, String... otherParams) {
return signParams(crypto, params, StrUtil.EMPTY, StrUtil.EMPTY, true, otherParams);
}
/**
* 对参数做签名<br>
* 参数签名为对Map参数按照key的顺序排序后拼接为字符串然后根据提供的签名算法生成签名字符串
*
* @param crypto 对称加密算法
* @param params 参数
* @param separator entry之间的连接符
* @param keyValueSeparator kv之间的连接符
* @param isIgnoreNull 是否忽略null的键和值
* @param otherParams 其它附加参数字符串例如密钥
* @return 签名
* @since 4.0.1
*/
public static String signParams(SymmetricCrypto crypto, Map<?, ?> params, String separator,
String keyValueSeparator, boolean isIgnoreNull, String... otherParams) {
return crypto.encryptHex(MapUtil.sortJoin(params, separator, keyValueSeparator, isIgnoreNull, otherParams));
}
/**
* 对参数做md5签名<br>
* 参数签名为对Map参数按照key的顺序排序后拼接为字符串然后根据提供的签名算法生成签名字符串<br>
* 拼接后的字符串键值对之间无符号键值对之间无符号忽略null值
*
* @param params 参数
* @param otherParams 其它附加参数字符串例如密钥
* @return 签名
* @since 4.0.1
*/
public static String signParamsMd5(Map<?, ?> params, String... otherParams) {
return signParams(DigestAlgorithm.MD5, params, otherParams);
}
/**
* 对参数做Sha1签名<br>
* 参数签名为对Map参数按照key的顺序排序后拼接为字符串然后根据提供的签名算法生成签名字符串<br>
* 拼接后的字符串键值对之间无符号键值对之间无符号忽略null值
*
* @param params 参数
* @param otherParams 其它附加参数字符串例如密钥
* @return 签名
* @since 4.0.8
*/
public static String signParamsSha1(Map<?, ?> params, String... otherParams) {
return signParams(DigestAlgorithm.SHA1, params, otherParams);
}
/**
* 对参数做Sha256签名<br>
* 参数签名为对Map参数按照key的顺序排序后拼接为字符串然后根据提供的签名算法生成签名字符串<br>
* 拼接后的字符串键值对之间无符号键值对之间无符号忽略null值
*
* @param params 参数
* @param otherParams 其它附加参数字符串例如密钥
* @return 签名
* @since 4.0.1
*/
public static String signParamsSha256(Map<?, ?> params, String... otherParams) {
return signParams(DigestAlgorithm.SHA256, params, otherParams);
}
/**
* 对参数做签名<br>
* 参数签名为对Map参数按照key的顺序排序后拼接为字符串然后根据提供的签名算法生成签名字符串<br>
* 拼接后的字符串键值对之间无符号键值对之间无符号忽略null值
*
* @param digestAlgorithm 摘要算法
* @param params 参数
* @param otherParams 其它附加参数字符串例如密钥
* @return 签名
* @since 4.0.1
*/
public static String signParams(DigestAlgorithm digestAlgorithm, Map<?, ?> params, String... otherParams) {
return signParams(digestAlgorithm, params, StrUtil.EMPTY, StrUtil.EMPTY, true, otherParams);
}
/**
* 对参数做签名<br>
* 参数签名为对Map参数按照key的顺序排序后拼接为字符串然后根据提供的签名算法生成签名字符串
*
* @param digestAlgorithm 摘要算法
* @param params 参数
* @param separator entry之间的连接符
* @param keyValueSeparator kv之间的连接符
* @param isIgnoreNull 是否忽略null的键和值
* @param otherParams 其它附加参数字符串例如密钥
* @return 签名
* @since 4.0.1
*/
public static String signParams(DigestAlgorithm digestAlgorithm, Map<?, ?> params, String separator,
String keyValueSeparator, boolean isIgnoreNull, String... otherParams) {
return new Digester(digestAlgorithm).digestHex(MapUtil.sortJoin(params, separator, keyValueSeparator, isIgnoreNull, otherParams));
}
}

View File

@ -9,7 +9,7 @@
<parent> <parent>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId> <artifactId>hutool-parent</artifactId>
<version>5.7.19</version> <version>5.7.20-SNAPSHOT</version>
</parent> </parent>
<artifactId>hutool-db</artifactId> <artifactId>hutool-db</artifactId>

View File

@ -9,7 +9,7 @@
<parent> <parent>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId> <artifactId>hutool-parent</artifactId>
<version>5.7.19</version> <version>5.7.20-SNAPSHOT</version>
</parent> </parent>
<artifactId>hutool-dfa</artifactId> <artifactId>hutool-dfa</artifactId>

View File

@ -9,7 +9,7 @@
<parent> <parent>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId> <artifactId>hutool-parent</artifactId>
<version>5.7.19</version> <version>5.7.20-SNAPSHOT</version>
</parent> </parent>
<artifactId>hutool-extra</artifactId> <artifactId>hutool-extra</artifactId>

View File

@ -9,7 +9,7 @@
<parent> <parent>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId> <artifactId>hutool-parent</artifactId>
<version>5.7.19</version> <version>5.7.20-SNAPSHOT</version>
</parent> </parent>
<artifactId>hutool-http</artifactId> <artifactId>hutool-http</artifactId>

View File

@ -25,8 +25,8 @@ public class OS extends UserAgentInfo {
*/ */
public static final List<OS> oses = CollUtil.newArrayList(// public static final List<OS> oses = CollUtil.newArrayList(//
new OS("Windows 10 or Windows Server 2016", "windows nt 10\\.0", "windows nt (10\\.0)"),// new OS("Windows 10 or Windows Server 2016", "windows nt 10\\.0", "windows nt (10\\.0)"),//
new OS("Windows 8.1 or Winsows Server 2012R2", "windows nt 6\\.3", "windows nt (6\\.3)"),// new OS("Windows 8.1 or Windows Server 2012R2", "windows nt 6\\.3", "windows nt (6\\.3)"),//
new OS("Windows 8 or Winsows Server 2012", "windows nt 6\\.2", "windows nt (6\\.2)"),// new OS("Windows 8 or Windows Server 2012", "windows nt 6\\.2", "windows nt (6\\.2)"),//
new OS("Windows Vista", "windows nt 6\\.0", "windows nt (6\\.0)"), // new OS("Windows Vista", "windows nt 6\\.0", "windows nt (6\\.0)"), //
new OS("Windows 7 or Windows Server 2008R2", "windows nt 6\\.1", "windows nt (6\\.1)"), // new OS("Windows 7 or Windows Server 2008R2", "windows nt 6\\.1", "windows nt (6\\.1)"), //
new OS("Windows 2003", "windows nt 5\\.2", "windows nt (5\\.2)"), // new OS("Windows 2003", "windows nt 5\\.2", "windows nt (5\\.2)"), //

View File

@ -169,7 +169,7 @@ public class UserAgentUtilTest {
Assert.assertEquals("63.0.3239.132", ua.getVersion()); Assert.assertEquals("63.0.3239.132", ua.getVersion());
Assert.assertEquals("Webkit", ua.getEngine().toString()); Assert.assertEquals("Webkit", ua.getEngine().toString());
Assert.assertEquals("537.36", ua.getEngineVersion()); Assert.assertEquals("537.36", ua.getEngineVersion());
Assert.assertEquals("Windows 8.1 or Winsows Server 2012R2", ua.getOs().toString()); Assert.assertEquals("Windows 8.1 or Windows Server 2012R2", ua.getOs().toString());
Assert.assertEquals("6.3", ua.getOsVersion()); Assert.assertEquals("6.3", ua.getOsVersion());
Assert.assertEquals("Windows", ua.getPlatform().toString()); Assert.assertEquals("Windows", ua.getPlatform().toString());
Assert.assertFalse(ua.isMobile()); Assert.assertFalse(ua.isMobile());

View File

@ -9,7 +9,7 @@
<parent> <parent>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId> <artifactId>hutool-parent</artifactId>
<version>5.7.19</version> <version>5.7.20-SNAPSHOT</version>
</parent> </parent>
<artifactId>hutool-json</artifactId> <artifactId>hutool-json</artifactId>

View File

@ -95,7 +95,7 @@ public class JSONArray implements JSON, JSONGetter<Integer>, List<Object>, Rando
*/ */
public JSONArray(int initialCapacity, JSONConfig config) { public JSONArray(int initialCapacity, JSONConfig config) {
this.rawList = new ArrayList<>(initialCapacity); this.rawList = new ArrayList<>(initialCapacity);
this.config = ObjectUtil.defaultIfNull(config, JSONConfig.create()); this.config = ObjectUtil.defaultIfNull(config, JSONConfig::create);
} }
/** /**

View File

@ -9,6 +9,7 @@ import cn.hutool.core.util.StrUtil;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Date; import java.util.Date;
import java.util.List;
import java.util.Optional; import java.util.Optional;
/** /**
@ -113,6 +114,21 @@ public interface JSONGetter<K> extends OptNullBasicTypeFromObjectGetter<K> {
return (null == obj) ? null : obj.toBean(beanType); return (null == obj) ? null : obj.toBean(beanType);
} }
/**
* 从JSON中直接获取Bean的List列表<br>
* 先获取JSONArray对象然后转为Bean的List
*
* @param <T> Bean类型
* @param key KEY
* @param beanType Bean类型
* @return Bean的List如果值为null或者非JSONObject类型返回null
* @since 5.7.20
*/
default <T> List<T> getBeanList(K key, Class<T> beanType) {
final JSONArray jsonArray = getJSONArray(key);
return (null == jsonArray) ? null : jsonArray.toList(beanType);
}
@Override @Override
default Date getDate(K key, Date defaultValue) { default Date getDate(K key, Date defaultValue) {
// 默认转换 // 默认转换

View File

@ -9,7 +9,7 @@
<parent> <parent>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId> <artifactId>hutool-parent</artifactId>
<version>5.7.19</version> <version>5.7.20-SNAPSHOT</version>
</parent> </parent>
<artifactId>hutool-jwt</artifactId> <artifactId>hutool-jwt</artifactId>

View File

@ -9,7 +9,7 @@
<parent> <parent>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId> <artifactId>hutool-parent</artifactId>
<version>5.7.19</version> <version>5.7.20-SNAPSHOT</version>
</parent> </parent>
<artifactId>hutool-log</artifactId> <artifactId>hutool-log</artifactId>

View File

@ -9,7 +9,7 @@
<parent> <parent>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId> <artifactId>hutool-parent</artifactId>
<version>5.7.19</version> <version>5.7.20-SNAPSHOT</version>
</parent> </parent>
<artifactId>hutool-poi</artifactId> <artifactId>hutool-poi</artifactId>

View File

@ -376,7 +376,7 @@ public class CellUtil {
public static Cell getMergedRegionCell(Sheet sheet, int x, int y) { public static Cell getMergedRegionCell(Sheet sheet, int x, int y) {
return ObjectUtil.defaultIfNull( return ObjectUtil.defaultIfNull(
getCellIfMergedRegion(sheet, x, y), getCellIfMergedRegion(sheet, x, y),
SheetUtil.getCell(sheet, y, x)); () -> SheetUtil.getCell(sheet, y, x));
} }
/** /**

View File

@ -294,7 +294,7 @@ public class SheetDataSaxHandler extends DefaultHandler {
final int numFmtIndex = xssfCellStyle.getDataFormat(); final int numFmtIndex = xssfCellStyle.getDataFormat();
this.numFmtString = ObjectUtil.defaultIfNull( this.numFmtString = ObjectUtil.defaultIfNull(
xssfCellStyle.getDataFormatString(), xssfCellStyle.getDataFormatString(),
BuiltinFormats.getBuiltinFormat(numFmtIndex)); () -> BuiltinFormats.getBuiltinFormat(numFmtIndex));
if (CellDataType.NUMBER == this.cellDataType && ExcelSaxUtil.isDateFormat(numFmtIndex, numFmtString)) { if (CellDataType.NUMBER == this.cellDataType && ExcelSaxUtil.isDateFormat(numFmtIndex, numFmtString)) {
cellDataType = CellDataType.DATE; cellDataType = CellDataType.DATE;
} }

View File

@ -9,7 +9,7 @@
<parent> <parent>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId> <artifactId>hutool-parent</artifactId>
<version>5.7.19</version> <version>5.7.20-SNAPSHOT</version>
</parent> </parent>
<artifactId>hutool-script</artifactId> <artifactId>hutool-script</artifactId>

View File

@ -9,7 +9,7 @@
<parent> <parent>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId> <artifactId>hutool-parent</artifactId>
<version>5.7.19</version> <version>5.7.20-SNAPSHOT</version>
</parent> </parent>
<artifactId>hutool-setting</artifactId> <artifactId>hutool-setting</artifactId>

View File

@ -9,7 +9,7 @@
<parent> <parent>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId> <artifactId>hutool-parent</artifactId>
<version>5.7.19</version> <version>5.7.20-SNAPSHOT</version>
</parent> </parent>
<artifactId>hutool-socket</artifactId> <artifactId>hutool-socket</artifactId>

View File

@ -9,7 +9,7 @@
<parent> <parent>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId> <artifactId>hutool-parent</artifactId>
<version>5.7.19</version> <version>5.7.20-SNAPSHOT</version>
</parent> </parent>
<artifactId>hutool-system</artifactId> <artifactId>hutool-system</artifactId>

View File

@ -8,7 +8,7 @@
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId> <artifactId>hutool-parent</artifactId>
<version>5.7.19</version> <version>5.7.20-SNAPSHOT</version>
<name>hutool</name> <name>hutool</name>
<description>Hutool是一个小而全的Java工具类库通过静态方法封装降低相关API的学习成本提高工作效率使Java拥有函数式语言般的优雅让Java语言也可以“甜甜的”。</description> <description>Hutool是一个小而全的Java工具类库通过静态方法封装降低相关API的学习成本提高工作效率使Java拥有函数式语言般的优雅让Java语言也可以“甜甜的”。</description>
<url>https://github.com/dromara/hutool</url> <url>https://github.com/dromara/hutool</url>