mirror of
https://gitee.com/dromara/hutool.git
synced 2025-06-28 13:34:09 +08:00
Merge branch 'v5-dev' into v5-dev
This commit is contained in:
commit
78b75f4283
13
CHANGELOG.md
13
CHANGELOG.md
@ -1,6 +1,19 @@
|
|||||||
|
|
||||||
# 🚀Changelog
|
# 🚀Changelog
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------------------------------------
|
||||||
|
# 5.8.38(2025-04-17)
|
||||||
|
|
||||||
|
### 🐣新特性
|
||||||
|
* 【core 】 `PathUtil#del`增加null检查(pr#1331@Gitee)
|
||||||
|
* 【db 】 增加SAP HANA识别及方言(pr#3914@Github)
|
||||||
|
* 【crypto 】 增加`Argon2`类,实现Argon2算法(issue#3890@Github)
|
||||||
|
* 【core 】 `CharSequenceUtil`增加toLoweCase和toUpperCase方法(issue#IC0H2B@Gitee)
|
||||||
|
* 【core 】 增加分段锁实现`SegmentLock`(pr#1330@Gitee)
|
||||||
|
|
||||||
|
### 🐞Bug修复
|
||||||
|
* 【setting】 修复`Setting`autoLoad可能的加载为空的问题(issue#3919@Github)
|
||||||
|
|
||||||
-------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------
|
||||||
# 5.8.37(2025-03-31)
|
# 5.8.37(2025-03-31)
|
||||||
|
|
||||||
|
@ -133,18 +133,18 @@ Each module can be introduced individually, or all modules can be introduced by
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>cn.hutool</groupId>
|
<groupId>cn.hutool</groupId>
|
||||||
<artifactId>hutool-all</artifactId>
|
<artifactId>hutool-all</artifactId>
|
||||||
<version>5.8.37</version>
|
<version>5.8.38</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
|
|
||||||
### 🍐Gradle
|
### 🍐Gradle
|
||||||
```
|
```
|
||||||
implementation 'cn.hutool:hutool-all:5.8.37'
|
implementation 'cn.hutool:hutool-all:5.8.38'
|
||||||
```
|
```
|
||||||
|
|
||||||
## 📥Download
|
## 📥Download
|
||||||
|
|
||||||
- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.37/)
|
- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.38/)
|
||||||
|
|
||||||
> 🔔️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.
|
||||||
|
@ -123,20 +123,20 @@ Hutool = Hu + tool,是原公司项目底层代码剥离后的开源库,“Hu
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>cn.hutool</groupId>
|
<groupId>cn.hutool</groupId>
|
||||||
<artifactId>hutool-all</artifactId>
|
<artifactId>hutool-all</artifactId>
|
||||||
<version>5.8.37</version>
|
<version>5.8.38</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
|
|
||||||
### 🍐Gradle
|
### 🍐Gradle
|
||||||
```
|
```
|
||||||
implementation 'cn.hutool:hutool-all:5.8.37'
|
implementation 'cn.hutool:hutool-all:5.8.38'
|
||||||
```
|
```
|
||||||
|
|
||||||
### 📥下载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.8.37/)
|
- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.38/)
|
||||||
|
|
||||||
> 🔔️注意
|
> 🔔️注意
|
||||||
> Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。
|
> Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。
|
||||||
|
@ -1 +1 @@
|
|||||||
5.8.37
|
5.8.38
|
||||||
|
@ -1 +1 @@
|
|||||||
var version = '5.8.37'
|
var version = '5.8.38'
|
@ -9,7 +9,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>cn.hutool</groupId>
|
<groupId>cn.hutool</groupId>
|
||||||
<artifactId>hutool-parent</artifactId>
|
<artifactId>hutool-parent</artifactId>
|
||||||
<version>5.8.37</version>
|
<version>5.8.38-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hutool-all</artifactId>
|
<artifactId>hutool-all</artifactId>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>cn.hutool</groupId>
|
<groupId>cn.hutool</groupId>
|
||||||
<artifactId>hutool-parent</artifactId>
|
<artifactId>hutool-parent</artifactId>
|
||||||
<version>5.8.37</version>
|
<version>5.8.38-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hutool-aop</artifactId>
|
<artifactId>hutool-aop</artifactId>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>cn.hutool</groupId>
|
<groupId>cn.hutool</groupId>
|
||||||
<artifactId>hutool-parent</artifactId>
|
<artifactId>hutool-parent</artifactId>
|
||||||
<version>5.8.37</version>
|
<version>5.8.38-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hutool-bloomFilter</artifactId>
|
<artifactId>hutool-bloomFilter</artifactId>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>cn.hutool</groupId>
|
<groupId>cn.hutool</groupId>
|
||||||
<artifactId>hutool-parent</artifactId>
|
<artifactId>hutool-parent</artifactId>
|
||||||
<version>5.8.37</version>
|
<version>5.8.38-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hutool-bom</artifactId>
|
<artifactId>hutool-bom</artifactId>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>cn.hutool</groupId>
|
<groupId>cn.hutool</groupId>
|
||||||
<artifactId>hutool-parent</artifactId>
|
<artifactId>hutool-parent</artifactId>
|
||||||
<version>5.8.37</version>
|
<version>5.8.38-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hutool-cache</artifactId>
|
<artifactId>hutool-cache</artifactId>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>cn.hutool</groupId>
|
<groupId>cn.hutool</groupId>
|
||||||
<artifactId>hutool-parent</artifactId>
|
<artifactId>hutool-parent</artifactId>
|
||||||
<version>5.8.37</version>
|
<version>5.8.38-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hutool-captcha</artifactId>
|
<artifactId>hutool-captcha</artifactId>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>cn.hutool</groupId>
|
<groupId>cn.hutool</groupId>
|
||||||
<artifactId>hutool-parent</artifactId>
|
<artifactId>hutool-parent</artifactId>
|
||||||
<version>5.8.37</version>
|
<version>5.8.38-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hutool-core</artifactId>
|
<artifactId>hutool-core</artifactId>
|
||||||
|
@ -165,7 +165,7 @@ public class PathUtil {
|
|||||||
* @since 4.4.2
|
* @since 4.4.2
|
||||||
*/
|
*/
|
||||||
public static boolean del(Path path) throws IORuntimeException {
|
public static boolean del(Path path) throws IORuntimeException {
|
||||||
if (Files.notExists(path)) {
|
if (null == path || Files.notExists(path)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -435,7 +435,7 @@ public class CharSequenceUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否存都不为{@code null}或空对象或空白符的对象,通过{@link #hasBlank(CharSequence...)} 判断元素
|
* 是否全都不为{@code null}或空对象或空白符的对象,通过{@link #hasBlank(CharSequence...)} 判断元素
|
||||||
*
|
*
|
||||||
* @param args 被检查的对象,一个或者多个
|
* @param args 被检查的对象,一个或者多个
|
||||||
* @return 是否都不为空
|
* @return 是否都不为空
|
||||||
@ -4237,6 +4237,42 @@ public class CharSequenceUtil {
|
|||||||
|
|
||||||
// ------------------------------------------------------------------------ lower and upper
|
// ------------------------------------------------------------------------ lower and upper
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将字符串转为小写
|
||||||
|
*
|
||||||
|
* @param str 被转的字符串
|
||||||
|
* @return 转换后的字符串
|
||||||
|
* @see String#toLowerCase()
|
||||||
|
* @since 5.8.38
|
||||||
|
*/
|
||||||
|
public static String toLoweCase(final CharSequence str) {
|
||||||
|
if (null == str) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if(0 == str.length()){
|
||||||
|
return EMPTY;
|
||||||
|
}
|
||||||
|
return str.toString().toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将字符串转为大写
|
||||||
|
*
|
||||||
|
* @param str 被转的字符串
|
||||||
|
* @return 转换后的字符串
|
||||||
|
* @see String#toUpperCase()
|
||||||
|
* @since 5.8.38
|
||||||
|
*/
|
||||||
|
public static String toUpperCase(final CharSequence str) {
|
||||||
|
if (null == str) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if(0 == str.length()){
|
||||||
|
return EMPTY;
|
||||||
|
}
|
||||||
|
return str.toString().toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 原字符串首字母大写并在其首部添加指定字符串 例如:str=name, preString=get =》 return getName
|
* 原字符串首字母大写并在其首部添加指定字符串 例如:str=name, preString=get =》 return getName
|
||||||
*
|
*
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
package cn.hutool.core.thread.lock;
|
package cn.hutool.core.thread.lock;
|
||||||
|
|
||||||
|
import java.util.concurrent.Semaphore;
|
||||||
|
import java.util.concurrent.locks.Lock;
|
||||||
|
import java.util.concurrent.locks.ReadWriteLock;
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
import java.util.concurrent.locks.StampedLock;
|
import java.util.concurrent.locks.StampedLock;
|
||||||
|
|
||||||
@ -40,4 +43,101 @@ public class LockUtil {
|
|||||||
public static NoLock getNoLock(){
|
public static NoLock getNoLock(){
|
||||||
return NO_LOCK;
|
return NO_LOCK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建分段锁(强引用),使用 ReentrantLock
|
||||||
|
*
|
||||||
|
* @param segments 分段数量,必须大于 0
|
||||||
|
* @return 分段锁实例
|
||||||
|
*/
|
||||||
|
public static SegmentLock<Lock> createSegmentLock(int segments) {
|
||||||
|
return SegmentLock.lock(segments);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建分段读写锁(强引用),使用 ReentrantReadWriteLock
|
||||||
|
*
|
||||||
|
* @param segments 分段数量,必须大于 0
|
||||||
|
* @return 分段读写锁实例
|
||||||
|
*/
|
||||||
|
public static SegmentLock<ReadWriteLock> createSegmentReadWriteLock(int segments) {
|
||||||
|
return SegmentLock.readWriteLock(segments);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建分段信号量(强引用)
|
||||||
|
*
|
||||||
|
* @param segments 分段数量,必须大于 0
|
||||||
|
* @param permits 每个信号量的许可数
|
||||||
|
* @return 分段信号量实例
|
||||||
|
*/
|
||||||
|
public static SegmentLock<Semaphore> createSegmentSemaphore(int segments, int permits) {
|
||||||
|
return SegmentLock.semaphore(segments, permits);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建弱引用分段锁,使用 ReentrantLock,懒加载
|
||||||
|
*
|
||||||
|
* @param segments 分段数量,必须大于 0
|
||||||
|
* @return 弱引用分段锁实例
|
||||||
|
*/
|
||||||
|
public static SegmentLock<Lock> createLazySegmentLock(int segments) {
|
||||||
|
return SegmentLock.lazyWeakLock(segments);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据 key 获取分段锁(强引用)
|
||||||
|
*
|
||||||
|
* @param segments 分段数量,必须大于 0
|
||||||
|
* @param key 用于映射分段的 key
|
||||||
|
* @return 对应的 Lock 实例
|
||||||
|
*/
|
||||||
|
public static Lock getSegmentLock(int segments, Object key) {
|
||||||
|
return SegmentLock.lock(segments).get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据 key 获取分段读锁(强引用)
|
||||||
|
*
|
||||||
|
* @param segments 分段数量,必须大于 0
|
||||||
|
* @param key 用于映射分段的 key
|
||||||
|
* @return 对应的读锁实例
|
||||||
|
*/
|
||||||
|
public static Lock getSegmentReadLock(int segments, Object key) {
|
||||||
|
return SegmentLock.readWriteLock(segments).get(key).readLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据 key 获取分段写锁(强引用)
|
||||||
|
*
|
||||||
|
* @param segments 分段数量,必须大于 0
|
||||||
|
* @param key 用于映射分段的 key
|
||||||
|
* @return 对应的写锁实例
|
||||||
|
*/
|
||||||
|
public static Lock getSegmentWriteLock(int segments, Object key) {
|
||||||
|
return SegmentLock.readWriteLock(segments).get(key).writeLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据 key 获取分段信号量(强引用)
|
||||||
|
*
|
||||||
|
* @param segments 分段数量,必须大于 0
|
||||||
|
* @param permits 每个信号量的许可数
|
||||||
|
* @param key 用于映射分段的 key
|
||||||
|
* @return 对应的 Semaphore 实例
|
||||||
|
*/
|
||||||
|
public static Semaphore getSegmentSemaphore(int segments, int permits, Object key) {
|
||||||
|
return SegmentLock.semaphore(segments, permits).get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据 key 获取弱引用分段锁,懒加载
|
||||||
|
*
|
||||||
|
* @param segments 分段数量,必须大于 0
|
||||||
|
* @param key 用于映射分段的 key
|
||||||
|
* @return 对应的 Lock 实例
|
||||||
|
*/
|
||||||
|
public static Lock getLazySegmentLock(int segments, Object key) {
|
||||||
|
return SegmentLock.lazyWeakLock(segments).get(key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,511 @@
|
|||||||
|
package cn.hutool.core.thread.lock;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.lang.Assert;
|
||||||
|
|
||||||
|
import java.lang.ref.Reference;
|
||||||
|
import java.lang.ref.ReferenceQueue;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
import java.util.concurrent.Semaphore;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicReferenceArray;
|
||||||
|
import java.util.concurrent.locks.Condition;
|
||||||
|
import java.util.concurrent.locks.Lock;
|
||||||
|
import java.util.concurrent.locks.ReadWriteLock;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分段锁工具类,支持 Lock、Semaphore 和 ReadWriteLock 的分段实现。
|
||||||
|
* <p>
|
||||||
|
* 通过将锁分成多个段(segments),不同的操作可以并发使用不同的段,避免所有线程竞争同一把锁。
|
||||||
|
* 相等的 key 保证映射到同一段锁(如 key1.equals(key2) 时,get(key1) 和 get(key2) 返回相同对象)。
|
||||||
|
* 但不同 key 可能因哈希冲突映射到同一段,段数越少冲突概率越高。
|
||||||
|
* <p>
|
||||||
|
* 支持两种实现:
|
||||||
|
* <ul>
|
||||||
|
* <li>强引用:创建时初始化所有段,内存占用稳定。</li>
|
||||||
|
* <li>弱引用:懒加载,首次使用时创建段,未使用时可被垃圾回收,适合大量段但使用较少的场景。</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param <L> 锁类型
|
||||||
|
* @author Guava,dakuo
|
||||||
|
* @since 5.8.38
|
||||||
|
*/
|
||||||
|
public abstract class SegmentLock<L> {
|
||||||
|
|
||||||
|
/** 当段数大于此阈值时,使用 ConcurrentMap 替代大数组以节省内存(适用于懒加载场景) */
|
||||||
|
private static final int LARGE_LAZY_CUTOFF = 1024;
|
||||||
|
|
||||||
|
private SegmentLock() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据 key 获取对应的锁段,保证相同 key 返回相同对象。
|
||||||
|
*
|
||||||
|
* @param key 非空 key
|
||||||
|
* @return 对应的锁段
|
||||||
|
*/
|
||||||
|
public abstract L get(Object key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据索引获取锁段,索引范围为 [0, size())。
|
||||||
|
*
|
||||||
|
* @param index 索引
|
||||||
|
* @return 指定索引的锁段
|
||||||
|
*/
|
||||||
|
public abstract L getAt(int index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算 key 对应的段索引。
|
||||||
|
*
|
||||||
|
* @param key 非空 key
|
||||||
|
* @return 段索引
|
||||||
|
*/
|
||||||
|
abstract int indexFor(Object key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取总段数。
|
||||||
|
*
|
||||||
|
* @return 段数
|
||||||
|
*/
|
||||||
|
public abstract int size();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量获取多个 key 对应的锁段列表,按索引升序排列,避免死锁。
|
||||||
|
*
|
||||||
|
* @param keys 非空 key 集合
|
||||||
|
* @return 锁段列表(可能有重复)
|
||||||
|
*/
|
||||||
|
public Iterable<L> bulkGet(Iterable<?> keys) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
List<Object> result = (List<Object>) CollUtil.newArrayList(keys);
|
||||||
|
if (CollUtil.isEmpty(result)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
int[] stripes = new int[result.size()];
|
||||||
|
for (int i = 0; i < result.size(); i++) {
|
||||||
|
stripes[i] = indexFor(result.get(i));
|
||||||
|
}
|
||||||
|
Arrays.sort(stripes);
|
||||||
|
int previousStripe = stripes[0];
|
||||||
|
result.set(0, getAt(previousStripe));
|
||||||
|
for (int i = 1; i < result.size(); i++) {
|
||||||
|
int currentStripe = stripes[i];
|
||||||
|
if (currentStripe == previousStripe) {
|
||||||
|
result.set(i, result.get(i - 1));
|
||||||
|
} else {
|
||||||
|
result.set(i, getAt(currentStripe));
|
||||||
|
previousStripe = currentStripe;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
List<L> asStripes = (List<L>) result;
|
||||||
|
return Collections.unmodifiableList(asStripes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 静态工厂方法
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建强引用的分段锁,所有段在创建时初始化。
|
||||||
|
*
|
||||||
|
* @param stripes 段数
|
||||||
|
* @param supplier 锁提供者
|
||||||
|
* @param <L> 锁类型
|
||||||
|
* @return 分段锁实例
|
||||||
|
*/
|
||||||
|
public static <L> SegmentLock<L> custom(int stripes, Supplier<L> supplier) {
|
||||||
|
return new CompactSegmentLock<>(stripes, supplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建强引用的可重入锁分段实例。
|
||||||
|
*
|
||||||
|
* @param stripes 段数
|
||||||
|
* @return 分段锁实例
|
||||||
|
*/
|
||||||
|
public static SegmentLock<Lock> lock(int stripes) {
|
||||||
|
return custom(stripes, PaddedLock::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建弱引用的可重入锁分段实例,懒加载。
|
||||||
|
*
|
||||||
|
* @param stripes 段数
|
||||||
|
* @return 分段锁实例
|
||||||
|
*/
|
||||||
|
public static SegmentLock<Lock> lazyWeakLock(int stripes) {
|
||||||
|
return lazyWeakCustom(stripes, () -> new ReentrantLock(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建弱引用的分段锁,懒加载。
|
||||||
|
*
|
||||||
|
* @param stripes 段数
|
||||||
|
* @param supplier 锁提供者
|
||||||
|
* @param <L> 锁类型
|
||||||
|
* @return 分段锁实例
|
||||||
|
*/
|
||||||
|
private static <L> SegmentLock<L> lazyWeakCustom(int stripes, Supplier<L> supplier) {
|
||||||
|
return stripes < LARGE_LAZY_CUTOFF
|
||||||
|
? new SmallLazySegmentLock<>(stripes, supplier)
|
||||||
|
: new LargeLazySegmentLock<>(stripes, supplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建强引用的信号量分段实例。
|
||||||
|
*
|
||||||
|
* @param stripes 段数
|
||||||
|
* @param permits 每个信号量的许可数
|
||||||
|
* @return 分段信号量实例
|
||||||
|
*/
|
||||||
|
public static SegmentLock<Semaphore> semaphore(int stripes, int permits) {
|
||||||
|
return custom(stripes, () -> new PaddedSemaphore(permits));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建弱引用的信号量分段实例,懒加载。
|
||||||
|
*
|
||||||
|
* @param stripes 段数
|
||||||
|
* @param permits 每个信号量的许可数
|
||||||
|
* @return 分段信号量实例
|
||||||
|
*/
|
||||||
|
public static SegmentLock<Semaphore> lazyWeakSemaphore(int stripes, int permits) {
|
||||||
|
return lazyWeakCustom(stripes, () -> new Semaphore(permits, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建强引用的读写锁分段实例。
|
||||||
|
*
|
||||||
|
* @param stripes 段数
|
||||||
|
* @return 分段读写锁实例
|
||||||
|
*/
|
||||||
|
public static SegmentLock<ReadWriteLock> readWriteLock(int stripes) {
|
||||||
|
return custom(stripes, ReentrantReadWriteLock::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建弱引用的读写锁分段实例,懒加载。
|
||||||
|
*
|
||||||
|
* @param stripes 段数
|
||||||
|
* @return 分段读写锁实例
|
||||||
|
*/
|
||||||
|
public static SegmentLock<ReadWriteLock> lazyWeakReadWriteLock(int stripes) {
|
||||||
|
return lazyWeakCustom(stripes, WeakSafeReadWriteLock::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 内部实现类
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 弱引用安全的读写锁实现,确保读锁和写锁持有对自身的强引用。
|
||||||
|
*/
|
||||||
|
private static final class WeakSafeReadWriteLock implements ReadWriteLock {
|
||||||
|
private final ReadWriteLock delegate;
|
||||||
|
|
||||||
|
WeakSafeReadWriteLock() {
|
||||||
|
this.delegate = new ReentrantReadWriteLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Lock readLock() {
|
||||||
|
return new WeakSafeLock(delegate.readLock(), this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Lock writeLock() {
|
||||||
|
return new WeakSafeLock(delegate.writeLock(), this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 弱引用安全的锁包装类,确保持有强引用。
|
||||||
|
*/
|
||||||
|
private static final class WeakSafeLock implements Lock {
|
||||||
|
private final Lock delegate;
|
||||||
|
private final WeakSafeReadWriteLock strongReference;
|
||||||
|
|
||||||
|
WeakSafeLock(Lock delegate, WeakSafeReadWriteLock strongReference) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
this.strongReference = strongReference;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void lock() {
|
||||||
|
delegate.lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void lockInterruptibly() throws InterruptedException {
|
||||||
|
delegate.lockInterruptibly();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean tryLock() {
|
||||||
|
return delegate.tryLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean tryLock(long time, java.util.concurrent.TimeUnit unit) throws InterruptedException {
|
||||||
|
return delegate.tryLock(time, unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unlock() {
|
||||||
|
delegate.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Condition newCondition() {
|
||||||
|
return new WeakSafeCondition(delegate.newCondition(), strongReference);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 弱引用安全的条件包装类。
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("FieldCanBeLocal")
|
||||||
|
private static final class WeakSafeCondition implements Condition {
|
||||||
|
private final Condition delegate;
|
||||||
|
|
||||||
|
/** 防止垃圾回收 */
|
||||||
|
private final WeakSafeReadWriteLock strongReference;
|
||||||
|
|
||||||
|
WeakSafeCondition(Condition delegate, WeakSafeReadWriteLock strongReference) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
this.strongReference = strongReference;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void await() throws InterruptedException {
|
||||||
|
delegate.await();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void awaitUninterruptibly() {
|
||||||
|
delegate.awaitUninterruptibly();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long awaitNanos(long nanosTimeout) throws InterruptedException {
|
||||||
|
return delegate.awaitNanos(nanosTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean await(long time, TimeUnit unit) throws InterruptedException {
|
||||||
|
return delegate.await(time, unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean awaitUntil(Date deadline) throws InterruptedException {
|
||||||
|
return delegate.awaitUntil(deadline);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void signal() {
|
||||||
|
delegate.signal();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void signalAll() {
|
||||||
|
delegate.signalAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 抽象基类,确保段数为 2 的幂。
|
||||||
|
*/
|
||||||
|
private abstract static class PowerOfTwoSegmentLock<L> extends SegmentLock<L> {
|
||||||
|
final int mask;
|
||||||
|
|
||||||
|
PowerOfTwoSegmentLock(int stripes) {
|
||||||
|
Assert.isTrue(stripes > 0, "Segment count must be positive");
|
||||||
|
this.mask = stripes > Integer.MAX_VALUE / 2 ? ALL_SET : ceilToPowerOfTwo(stripes) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
final int indexFor(Object key) {
|
||||||
|
int hash = smear(key.hashCode());
|
||||||
|
return hash & mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final L get(Object key) {
|
||||||
|
return getAt(indexFor(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 强引用实现,使用固定数组存储段。
|
||||||
|
*/
|
||||||
|
private static class CompactSegmentLock<L> extends PowerOfTwoSegmentLock<L> {
|
||||||
|
private final Object[] array;
|
||||||
|
|
||||||
|
CompactSegmentLock(int stripes, Supplier<L> supplier) {
|
||||||
|
super(stripes);
|
||||||
|
Assert.isTrue(stripes <= Integer.MAX_VALUE / 2, "Segment count must be <= 2^30");
|
||||||
|
this.array = new Object[mask + 1];
|
||||||
|
for (int i = 0; i < array.length; i++) {
|
||||||
|
array[i] = supplier.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public L getAt(int index) {
|
||||||
|
if (index < 0 || index >= array.length) {
|
||||||
|
throw new IllegalArgumentException("Index " + index + " out of bounds for size " + array.length);
|
||||||
|
}
|
||||||
|
return (L) array[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return array.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小规模弱引用实现,使用 AtomicReferenceArray 存储段。
|
||||||
|
*/
|
||||||
|
private static class SmallLazySegmentLock<L> extends PowerOfTwoSegmentLock<L> {
|
||||||
|
final AtomicReferenceArray<ArrayReference<? extends L>> locks;
|
||||||
|
final Supplier<L> supplier;
|
||||||
|
final int size;
|
||||||
|
final ReferenceQueue<L> queue = new ReferenceQueue<>();
|
||||||
|
|
||||||
|
SmallLazySegmentLock(int stripes, Supplier<L> supplier) {
|
||||||
|
super(stripes);
|
||||||
|
this.size = (mask == ALL_SET) ? Integer.MAX_VALUE : mask + 1;
|
||||||
|
this.locks = new AtomicReferenceArray<>(size);
|
||||||
|
this.supplier = supplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public L getAt(int index) {
|
||||||
|
if (size != Integer.MAX_VALUE) {
|
||||||
|
Assert.isTrue(index >= 0 && index < size, "Index out of bounds");
|
||||||
|
}
|
||||||
|
ArrayReference<? extends L> existingRef = locks.get(index);
|
||||||
|
L existing = existingRef == null ? null : existingRef.get();
|
||||||
|
if (existing != null) {
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
L created = supplier.get();
|
||||||
|
ArrayReference<L> newRef = new ArrayReference<>(created, index, queue);
|
||||||
|
while (!locks.compareAndSet(index, existingRef, newRef)) {
|
||||||
|
existingRef = locks.get(index);
|
||||||
|
existing = existingRef == null ? null : existingRef.get();
|
||||||
|
if (existing != null) {
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drainQueue();
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drainQueue() {
|
||||||
|
Reference<? extends L> ref;
|
||||||
|
while ((ref = queue.poll()) != null) {
|
||||||
|
ArrayReference<? extends L> arrayRef = (ArrayReference<? extends L>) ref;
|
||||||
|
locks.compareAndSet(arrayRef.index, arrayRef, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ArrayReference<L> extends WeakReference<L> {
|
||||||
|
final int index;
|
||||||
|
|
||||||
|
ArrayReference(L referent, int index, ReferenceQueue<L> queue) {
|
||||||
|
super(referent, queue);
|
||||||
|
this.index = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 大规模弱引用实现,使用 ConcurrentMap 存储段。
|
||||||
|
*/
|
||||||
|
private static class LargeLazySegmentLock<L> extends PowerOfTwoSegmentLock<L> {
|
||||||
|
final ConcurrentMap<Integer, L> locks;
|
||||||
|
final Supplier<L> supplier;
|
||||||
|
final int size;
|
||||||
|
|
||||||
|
LargeLazySegmentLock(int stripes, Supplier<L> supplier) {
|
||||||
|
super(stripes);
|
||||||
|
this.size = (mask == ALL_SET) ? Integer.MAX_VALUE : mask + 1;
|
||||||
|
this.locks = new ConcurrentHashMap<>();
|
||||||
|
this.supplier = supplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public L getAt(int index) {
|
||||||
|
if (size != Integer.MAX_VALUE) {
|
||||||
|
Assert.isTrue(index >= 0 && index < size, "Index out of bounds");
|
||||||
|
}
|
||||||
|
L existing = locks.get(index);
|
||||||
|
if (existing != null) {
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
L created = supplier.get();
|
||||||
|
existing = locks.putIfAbsent(index, created);
|
||||||
|
return existing != null ? existing : created;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int ALL_SET = ~0;
|
||||||
|
|
||||||
|
private static int ceilToPowerOfTwo(int x) {
|
||||||
|
return 1 << (Integer.SIZE - Integer.numberOfLeadingZeros(x - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int smear(int hashCode) {
|
||||||
|
hashCode ^= (hashCode >>> 20) ^ (hashCode >>> 12);
|
||||||
|
return hashCode ^ (hashCode >>> 7) ^ (hashCode >>> 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 填充锁,避免缓存行干扰。
|
||||||
|
*/
|
||||||
|
private static class PaddedLock extends ReentrantLock {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
long unused1;
|
||||||
|
long unused2;
|
||||||
|
long unused3;
|
||||||
|
|
||||||
|
PaddedLock() {
|
||||||
|
super(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 填充信号量,避免缓存行干扰。
|
||||||
|
*/
|
||||||
|
private static class PaddedSemaphore extends Semaphore {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
long unused1;
|
||||||
|
long unused2;
|
||||||
|
long unused3;
|
||||||
|
|
||||||
|
PaddedSemaphore(int permits) {
|
||||||
|
super(permits, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1592,7 +1592,7 @@ public class ArrayUtil extends PrimitiveArrayUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否存都为{@code null}或空对象,通过{@link ObjectUtil#isEmpty(Object)} 判断元素
|
* 是否全都为{@code null}或空对象,通过{@link ObjectUtil#isEmpty(Object)} 判断元素
|
||||||
*
|
*
|
||||||
* @param args 被检查的对象,一个或者多个
|
* @param args 被检查的对象,一个或者多个
|
||||||
* @return 是否都为空
|
* @return 是否都为空
|
||||||
@ -1608,7 +1608,7 @@ public class ArrayUtil extends PrimitiveArrayUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否存都不为{@code null}或空对象,通过{@link ObjectUtil#isEmpty(Object)} 判断元素
|
* 是否全都不为{@code null}或空对象,通过{@link ObjectUtil#isEmpty(Object)} 判断元素
|
||||||
*
|
*
|
||||||
* @param args 被检查的对象,一个或者多个
|
* @param args 被检查的对象,一个或者多个
|
||||||
* @return 是否都不为空
|
* @return 是否都不为空
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
package cn.hutool.core.date;
|
||||||
|
|
||||||
|
import cn.hutool.core.lang.Console;
|
||||||
|
import org.junit.jupiter.api.Disabled;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
public class IssueIC00HGTest {
|
||||||
|
@Test
|
||||||
|
@Disabled
|
||||||
|
void dateToStringTest(){
|
||||||
|
Console.log(DateUtil.date().toString());
|
||||||
|
}
|
||||||
|
}
|
@ -66,7 +66,11 @@ public class FileUtilTest {
|
|||||||
final String parseSmbPath = FileUtil.getAbsolutePath(smbPath);
|
final String parseSmbPath = FileUtil.getAbsolutePath(smbPath);
|
||||||
assertEquals(smbPath, parseSmbPath);
|
assertEquals(smbPath, parseSmbPath);
|
||||||
assertTrue(FileUtil.isAbsolutePath(smbPath));
|
assertTrue(FileUtil.isAbsolutePath(smbPath));
|
||||||
assertTrue(Paths.get(smbPath).isAbsolute());
|
if(FileUtil.isWindows()){
|
||||||
|
// 在Windows下`\`路径是绝对路径,也表示SMB路径
|
||||||
|
// 但是在Linux下,`\`表示转义字符,并不被识别为路径
|
||||||
|
assertTrue(Paths.get(smbPath).isAbsolute());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -480,21 +484,26 @@ public class FileUtilTest {
|
|||||||
final List<String> list = ListUtil.of("text/javascript", "application/x-javascript");
|
final List<String> list = ListUtil.of("text/javascript", "application/x-javascript");
|
||||||
assertTrue(list.contains(mimeType));
|
assertTrue(list.contains(mimeType));
|
||||||
|
|
||||||
// office03
|
if(FileUtil.isWindows()){
|
||||||
mimeType = FileUtil.getMimeType("test.doc");
|
// Linux下的OpenJDK无法正确识别
|
||||||
assertEquals("application/msword", mimeType);
|
|
||||||
mimeType = FileUtil.getMimeType("test.xls");
|
// office03
|
||||||
assertEquals("application/vnd.ms-excel", mimeType);
|
mimeType = FileUtil.getMimeType("test.doc");
|
||||||
mimeType = FileUtil.getMimeType("test.ppt");
|
assertEquals("application/msword", mimeType);
|
||||||
assertEquals("application/vnd.ms-powerpoint", mimeType);
|
mimeType = FileUtil.getMimeType("test.xls");
|
||||||
|
assertEquals("application/vnd.ms-excel", mimeType);
|
||||||
|
mimeType = FileUtil.getMimeType("test.ppt");
|
||||||
|
assertEquals("application/vnd.ms-powerpoint", mimeType);
|
||||||
|
|
||||||
|
// office07+
|
||||||
|
mimeType = FileUtil.getMimeType("test.docx");
|
||||||
|
assertEquals("application/vnd.openxmlformats-officedocument.wordprocessingml.document", mimeType);
|
||||||
|
mimeType = FileUtil.getMimeType("test.xlsx");
|
||||||
|
assertEquals("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", mimeType);
|
||||||
|
mimeType = FileUtil.getMimeType("test.pptx");
|
||||||
|
assertEquals("application/vnd.openxmlformats-officedocument.presentationml.presentation", mimeType);
|
||||||
|
}
|
||||||
|
|
||||||
// office07+
|
|
||||||
mimeType = FileUtil.getMimeType("test.docx");
|
|
||||||
assertEquals("application/vnd.openxmlformats-officedocument.wordprocessingml.document", mimeType);
|
|
||||||
mimeType = FileUtil.getMimeType("test.xlsx");
|
|
||||||
assertEquals("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", mimeType);
|
|
||||||
mimeType = FileUtil.getMimeType("test.pptx");
|
|
||||||
assertEquals("application/vnd.openxmlformats-officedocument.presentationml.presentation", mimeType);
|
|
||||||
|
|
||||||
// pr#2617@Github
|
// pr#2617@Github
|
||||||
mimeType = FileUtil.getMimeType("test.wgt");
|
mimeType = FileUtil.getMimeType("test.wgt");
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
package cn.hutool.core.io.file;
|
package cn.hutool.core.io.file;
|
||||||
|
|
||||||
import cn.hutool.core.io.FileUtil;
|
import cn.hutool.core.io.FileUtil;
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.nio.file.StandardCopyOption;
|
import java.nio.file.StandardCopyOption;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
public class PathUtilTest {
|
public class PathUtilTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -82,7 +85,10 @@ public class PathUtilTest {
|
|||||||
@Test
|
@Test
|
||||||
public void issue3179Test() {
|
public void issue3179Test() {
|
||||||
final String mimeType = PathUtil.getMimeType(Paths.get("xxxx.jpg"));
|
final String mimeType = PathUtil.getMimeType(Paths.get("xxxx.jpg"));
|
||||||
assertEquals("image/jpeg", mimeType);
|
if(FileUtil.isWindows()){
|
||||||
|
// Linux下,OpenJDK可能报路径不存在
|
||||||
|
assertEquals("image/jpeg", mimeType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -93,4 +99,11 @@ public class PathUtilTest {
|
|||||||
public void moveTest2(){
|
public void moveTest2(){
|
||||||
PathUtil.move(Paths.get("D:\\project\\test1.txt"), Paths.get("D:\\project\\test2.txt"), false);
|
PathUtil.move(Paths.get("D:\\project\\test1.txt"), Paths.get("D:\\project\\test2.txt"), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Disabled
|
||||||
|
public void delNullDirTest() {
|
||||||
|
Path path = null;
|
||||||
|
assertTrue(PathUtil.del(path));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ public class SimpleCacheTest {
|
|||||||
@Test
|
@Test
|
||||||
public void getConcurrencyTest(){
|
public void getConcurrencyTest(){
|
||||||
final SimpleCache<String, String> cache = new SimpleCache<>();
|
final SimpleCache<String, String> cache = new SimpleCache<>();
|
||||||
final ConcurrencyTester tester = new ConcurrencyTester(9000);
|
final ConcurrencyTester tester = new ConcurrencyTester(500);
|
||||||
tester.test(()-> cache.get("aaa", ()-> {
|
tester.test(()-> cache.get("aaa", ()-> {
|
||||||
ThreadUtil.sleep(200);
|
ThreadUtil.sleep(200);
|
||||||
return "aaaValue";
|
return "aaaValue";
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
package cn.hutool.core.swing;
|
package cn.hutool.core.swing;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import cn.hutool.core.swing.clipboard.ClipboardUtil;
|
||||||
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import cn.hutool.core.swing.clipboard.ClipboardUtil;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 剪贴板工具类单元测试
|
* 剪贴板工具类单元测试
|
||||||
@ -14,6 +15,7 @@ import cn.hutool.core.swing.clipboard.ClipboardUtil;
|
|||||||
public class ClipboardUtilTest {
|
public class ClipboardUtilTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@Disabled
|
||||||
public void setAndGetStrTest() {
|
public void setAndGetStrTest() {
|
||||||
try {
|
try {
|
||||||
ClipboardUtil.setStr("test");
|
ClipboardUtil.setStr("test");
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package cn.hutool.core.text.csv;
|
package cn.hutool.core.text.csv;
|
||||||
|
|
||||||
import cn.hutool.core.util.CharUtil;
|
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
@ -19,10 +18,7 @@ public class Issue3705Test {
|
|||||||
csvWriter.flush();
|
csvWriter.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
String lineSeparator = new String(new char[]{CharUtil.CR, CharUtil.LF});
|
// CsvWriteConfig中默认为`\r\n`
|
||||||
|
Assertions.assertEquals("\"2024-08-20 14:24:35,\"\r\n最后一行", stringWriter.toString());
|
||||||
Assertions.assertEquals(
|
|
||||||
"\"2024-08-20 14:24:35,\"" + lineSeparator + "最后一行",
|
|
||||||
stringWriter.toString());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,209 @@
|
|||||||
|
package cn.hutool.core.thread;
|
||||||
|
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.thread.lock.SegmentLock;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.locks.Lock;
|
||||||
|
import java.util.concurrent.locks.ReadWriteLock;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SegmentLock 单元测试类
|
||||||
|
*/
|
||||||
|
public class SegmentLockTest {
|
||||||
|
|
||||||
|
private static final int SEGMENT_COUNT = 4;
|
||||||
|
private SegmentLock<Lock> strongLock;
|
||||||
|
private SegmentLock<Lock> weakLock;
|
||||||
|
private SegmentLock<Semaphore> semaphore;
|
||||||
|
private SegmentLock<ReadWriteLock> readWriteLock;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setUp() {
|
||||||
|
strongLock = SegmentLock.lock(SEGMENT_COUNT);
|
||||||
|
weakLock = SegmentLock.lazyWeakLock(SEGMENT_COUNT);
|
||||||
|
semaphore = SegmentLock.semaphore(SEGMENT_COUNT, 2);
|
||||||
|
readWriteLock = SegmentLock.readWriteLock(SEGMENT_COUNT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSize() {
|
||||||
|
assertEquals(SEGMENT_COUNT, strongLock.size());
|
||||||
|
assertEquals(SEGMENT_COUNT, weakLock.size());
|
||||||
|
assertEquals(SEGMENT_COUNT, semaphore.size());
|
||||||
|
assertEquals(SEGMENT_COUNT, readWriteLock.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("StringOperationCanBeSimplified")
|
||||||
|
@Test
|
||||||
|
public void testGetWithSameKey() {
|
||||||
|
// 相同 key 应返回相同锁
|
||||||
|
String key1 = "testKey";
|
||||||
|
String key2 = new String("testKey"); // equals 但不同对象
|
||||||
|
Lock lock1 = strongLock.get(key1);
|
||||||
|
Lock lock2 = strongLock.get(key2);
|
||||||
|
assertSame(lock1, lock2, "相同 key 应返回同一锁对象");
|
||||||
|
|
||||||
|
Lock weakLock1 = weakLock.get(key1);
|
||||||
|
Lock weakLock2 = weakLock.get(key2);
|
||||||
|
assertSame(weakLock1, weakLock2, "弱引用锁相同 key 应返回同一锁对象");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetAt() {
|
||||||
|
for (int i = 0; i < SEGMENT_COUNT; i++) {
|
||||||
|
Lock lock = strongLock.getAt(i);
|
||||||
|
assertNotNull(lock, "getAt 返回的锁不应为 null");
|
||||||
|
}
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> strongLock.getAt(SEGMENT_COUNT),
|
||||||
|
"超出段数的索引应抛出异常");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBulkGet() {
|
||||||
|
List<String> keys = CollUtil.newArrayList("key1", "key2", "key3");
|
||||||
|
Iterable<Lock> locks = strongLock.bulkGet(keys);
|
||||||
|
List<Lock> lockList = CollUtil.newArrayList(locks);
|
||||||
|
|
||||||
|
assertEquals(3, lockList.size(), "bulkGet 返回的锁数量应与 key 数量一致");
|
||||||
|
|
||||||
|
// 检查顺序性
|
||||||
|
int prevIndex = -1;
|
||||||
|
for (Lock lock : lockList) {
|
||||||
|
int index = findIndex(strongLock, lock);
|
||||||
|
assertTrue(index >= prevIndex, "bulkGet 返回的锁应按索引升序");
|
||||||
|
prevIndex = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLockConcurrency() throws InterruptedException {
|
||||||
|
int threadCount = SEGMENT_COUNT * 2;
|
||||||
|
CountDownLatch startLatch = new CountDownLatch(1);
|
||||||
|
CountDownLatch endLatch = new CountDownLatch(threadCount);
|
||||||
|
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
|
||||||
|
List<String> keys = new ArrayList<>();
|
||||||
|
for (int i = 0; i < threadCount; i++) {
|
||||||
|
keys.add("key" + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < threadCount; i++) {
|
||||||
|
final String key = keys.get(i);
|
||||||
|
executor.submit(() -> {
|
||||||
|
try {
|
||||||
|
startLatch.await();
|
||||||
|
Lock lock = strongLock.get(key);
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
Thread.sleep(100); // 模拟工作
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
} finally {
|
||||||
|
endLatch.countDown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
startLatch.countDown();
|
||||||
|
assertTrue(endLatch.await(2000, java.util.concurrent.TimeUnit.MILLISECONDS),
|
||||||
|
"并发锁测试应在 2 秒内完成");
|
||||||
|
executor.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSemaphore() {
|
||||||
|
Semaphore sem = semaphore.get("testKey");
|
||||||
|
assertEquals(2, sem.availablePermits(), "信号量初始许可应为 2");
|
||||||
|
|
||||||
|
sem.acquireUninterruptibly(2);
|
||||||
|
assertEquals(0, sem.availablePermits(), "获取所有许可后应为 0");
|
||||||
|
|
||||||
|
sem.release(1);
|
||||||
|
assertEquals(1, sem.availablePermits(), "释放一个许可后应为 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||||
|
@Test
|
||||||
|
public void testReadWriteLock() throws InterruptedException {
|
||||||
|
ReadWriteLock rwLock = readWriteLock.get("testKey");
|
||||||
|
Lock readLock = rwLock.readLock();
|
||||||
|
Lock writeLock = rwLock.writeLock();
|
||||||
|
|
||||||
|
// 测试读锁可重入
|
||||||
|
readLock.lock();
|
||||||
|
assertTrue(readLock.tryLock(), "读锁应允许多个线程同时持有");
|
||||||
|
readLock.unlock();
|
||||||
|
readLock.unlock();
|
||||||
|
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||||
|
AtomicBoolean readLockAcquired = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
writeLock.lock();
|
||||||
|
executor.submit(() -> {
|
||||||
|
readLockAcquired.set(readLock.tryLock());
|
||||||
|
latch.countDown();
|
||||||
|
});
|
||||||
|
|
||||||
|
latch.await(500, TimeUnit.MILLISECONDS);
|
||||||
|
assertFalse(readLockAcquired.get(), "写锁持有时读锁应失败");
|
||||||
|
writeLock.unlock();
|
||||||
|
|
||||||
|
executor.shutdown();
|
||||||
|
executor.awaitTermination(1, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWeakReferenceCleanup() throws InterruptedException {
|
||||||
|
SegmentLock<Lock> weakLockLarge = SegmentLock.lazyWeakLock(1024); // 超过 LARGE_LAZY_CUTOFF
|
||||||
|
Lock lock = weakLockLarge.get("testKey");
|
||||||
|
|
||||||
|
System.gc();
|
||||||
|
Thread.sleep(100);
|
||||||
|
|
||||||
|
// 弱引用锁未被其他引用,应仍可获取
|
||||||
|
Lock lockAgain = weakLockLarge.get("testKey");
|
||||||
|
assertSame(lock, lockAgain, "弱引用锁未被回收时应返回同一对象");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInvalidSegmentCount() {
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> SegmentLock.lock(0),
|
||||||
|
"段数为 0 应抛出异常");
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> SegmentLock.lock(-1),
|
||||||
|
"负段数应抛出异常");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHashDistribution() {
|
||||||
|
SegmentLock<Lock> lock = SegmentLock.lock(4);
|
||||||
|
int[] counts = new int[4];
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
int index = findIndex(lock, lock.get("key" + i));
|
||||||
|
counts[index]++;
|
||||||
|
}
|
||||||
|
for (int count : counts) {
|
||||||
|
assertTrue(count > 0, "每个段都应至少被分配到一个 key");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int findIndex(SegmentLock<Lock> lock, Lock target) {
|
||||||
|
for (int i = 0; i < lock.size(); i++) {
|
||||||
|
if (lock.getAt(i) == target) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
@ -144,43 +144,43 @@ public class NumberUtilTest {
|
|||||||
@Test
|
@Test
|
||||||
public void roundStrTest() {
|
public void roundStrTest() {
|
||||||
final String roundStr = NumberUtil.roundStr(2.647, 2);
|
final String roundStr = NumberUtil.roundStr(2.647, 2);
|
||||||
assertEquals(roundStr, "2.65");
|
assertEquals("2.65", roundStr);
|
||||||
|
|
||||||
final String roundStr1 = NumberUtil.roundStr(0, 10);
|
final String roundStr1 = NumberUtil.roundStr(0, 10);
|
||||||
assertEquals(roundStr1, "0.0000000000");
|
assertEquals("0.0000000000", roundStr1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void roundHalfEvenTest() {
|
public void roundHalfEvenTest() {
|
||||||
String roundStr = NumberUtil.roundHalfEven(4.245, 2).toString();
|
String roundStr = NumberUtil.roundHalfEven(4.245, 2).toString();
|
||||||
assertEquals(roundStr, "4.24");
|
assertEquals("4.24", roundStr);
|
||||||
roundStr = NumberUtil.roundHalfEven(4.2450, 2).toString();
|
roundStr = NumberUtil.roundHalfEven(4.2450, 2).toString();
|
||||||
assertEquals(roundStr, "4.24");
|
assertEquals("4.24", roundStr);
|
||||||
roundStr = NumberUtil.roundHalfEven(4.2451, 2).toString();
|
roundStr = NumberUtil.roundHalfEven(4.2451, 2).toString();
|
||||||
assertEquals(roundStr, "4.25");
|
assertEquals("4.25", roundStr);
|
||||||
roundStr = NumberUtil.roundHalfEven(4.2250, 2).toString();
|
roundStr = NumberUtil.roundHalfEven(4.2250, 2).toString();
|
||||||
assertEquals(roundStr, "4.22");
|
assertEquals("4.22", roundStr);
|
||||||
|
|
||||||
roundStr = NumberUtil.roundHalfEven(1.2050, 2).toString();
|
roundStr = NumberUtil.roundHalfEven(1.2050, 2).toString();
|
||||||
assertEquals(roundStr, "1.20");
|
assertEquals("1.20", roundStr);
|
||||||
roundStr = NumberUtil.roundHalfEven(1.2150, 2).toString();
|
roundStr = NumberUtil.roundHalfEven(1.2150, 2).toString();
|
||||||
assertEquals(roundStr, "1.22");
|
assertEquals("1.22", roundStr);
|
||||||
roundStr = NumberUtil.roundHalfEven(1.2250, 2).toString();
|
roundStr = NumberUtil.roundHalfEven(1.2250, 2).toString();
|
||||||
assertEquals(roundStr, "1.22");
|
assertEquals("1.22", roundStr);
|
||||||
roundStr = NumberUtil.roundHalfEven(1.2350, 2).toString();
|
roundStr = NumberUtil.roundHalfEven(1.2350, 2).toString();
|
||||||
assertEquals(roundStr, "1.24");
|
assertEquals("1.24", roundStr);
|
||||||
roundStr = NumberUtil.roundHalfEven(1.2450, 2).toString();
|
roundStr = NumberUtil.roundHalfEven(1.2450, 2).toString();
|
||||||
assertEquals(roundStr, "1.24");
|
assertEquals("1.24", roundStr);
|
||||||
roundStr = NumberUtil.roundHalfEven(1.2550, 2).toString();
|
roundStr = NumberUtil.roundHalfEven(1.2550, 2).toString();
|
||||||
assertEquals(roundStr, "1.26");
|
assertEquals("1.26", roundStr);
|
||||||
roundStr = NumberUtil.roundHalfEven(1.2650, 2).toString();
|
roundStr = NumberUtil.roundHalfEven(1.2650, 2).toString();
|
||||||
assertEquals(roundStr, "1.26");
|
assertEquals("1.26", roundStr);
|
||||||
roundStr = NumberUtil.roundHalfEven(1.2750, 2).toString();
|
roundStr = NumberUtil.roundHalfEven(1.2750, 2).toString();
|
||||||
assertEquals(roundStr, "1.28");
|
assertEquals("1.28", roundStr);
|
||||||
roundStr = NumberUtil.roundHalfEven(1.2850, 2).toString();
|
roundStr = NumberUtil.roundHalfEven(1.2850, 2).toString();
|
||||||
assertEquals(roundStr, "1.28");
|
assertEquals("1.28", roundStr);
|
||||||
roundStr = NumberUtil.roundHalfEven(1.2950, 2).toString();
|
roundStr = NumberUtil.roundHalfEven(1.2950, 2).toString();
|
||||||
assertEquals(roundStr, "1.30");
|
assertEquals("1.30", roundStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -673,4 +673,10 @@ public class NumberUtilTest {
|
|||||||
final double result = NumberUtil.add(v1, v2);
|
final double result = NumberUtil.add(v1, v2);
|
||||||
assertEquals(91007279.3545, result, 0);
|
assertEquals(91007279.3545, result, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void issueIC1MXETest(){
|
||||||
|
final boolean equals = NumberUtil.equals(104557543L, 104557544);
|
||||||
|
assertFalse(equals);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>cn.hutool</groupId>
|
<groupId>cn.hutool</groupId>
|
||||||
<artifactId>hutool-parent</artifactId>
|
<artifactId>hutool-parent</artifactId>
|
||||||
<version>5.8.37</version>
|
<version>5.8.38-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hutool-cron</artifactId>
|
<artifactId>hutool-cron</artifactId>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>cn.hutool</groupId>
|
<groupId>cn.hutool</groupId>
|
||||||
<artifactId>hutool-parent</artifactId>
|
<artifactId>hutool-parent</artifactId>
|
||||||
<version>5.8.37</version>
|
<version>5.8.38-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hutool-crypto</artifactId>
|
<artifactId>hutool-crypto</artifactId>
|
||||||
|
152
hutool-crypto/src/main/java/cn/hutool/crypto/digest/Argon2.java
Normal file
152
hutool-crypto/src/main/java/cn/hutool/crypto/digest/Argon2.java
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
package cn.hutool.crypto.digest;
|
||||||
|
|
||||||
|
import org.bouncycastle.crypto.generators.Argon2BytesGenerator;
|
||||||
|
import org.bouncycastle.crypto.params.Argon2Parameters;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Argon2加密实现
|
||||||
|
*
|
||||||
|
* @author changhr2013
|
||||||
|
* @author Looly
|
||||||
|
* @since 5.8.38
|
||||||
|
*/
|
||||||
|
public class Argon2 {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认hash长度
|
||||||
|
*/
|
||||||
|
public static final int DEFAULT_HASH_LENGTH = 32;
|
||||||
|
|
||||||
|
private int hashLength = DEFAULT_HASH_LENGTH;
|
||||||
|
private final Argon2Parameters.Builder paramsBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造,默认使用{@link Argon2Parameters#ARGON2_id}类型
|
||||||
|
*/
|
||||||
|
public Argon2(){
|
||||||
|
this(Argon2Parameters.ARGON2_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
*
|
||||||
|
* @param type {@link Argon2Parameters#ARGON2_d}、{@link Argon2Parameters#ARGON2_i}、{@link Argon2Parameters#ARGON2_id}
|
||||||
|
*/
|
||||||
|
public Argon2(int type){
|
||||||
|
this(new Argon2Parameters.Builder(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
*
|
||||||
|
* @param paramsBuilder 参数构造器
|
||||||
|
*/
|
||||||
|
public Argon2(Argon2Parameters.Builder paramsBuilder){
|
||||||
|
this.paramsBuilder = paramsBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置hash长度
|
||||||
|
*
|
||||||
|
* @param hashLength hash长度
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public Argon2 setHashLength(int hashLength){
|
||||||
|
this.hashLength = hashLength;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置版本
|
||||||
|
*
|
||||||
|
* @param version 版本
|
||||||
|
* @return this
|
||||||
|
* @see Argon2Parameters#ARGON2_VERSION_10
|
||||||
|
* @see Argon2Parameters#ARGON2_VERSION_13
|
||||||
|
*/
|
||||||
|
public Argon2 setVersion(int version){
|
||||||
|
this.paramsBuilder.withVersion(version);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置盐
|
||||||
|
*
|
||||||
|
* @param salt 盐
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public Argon2 setSalt(byte[] salt){
|
||||||
|
this.paramsBuilder.withSalt(salt);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置可选的密钥数据,用于增加哈希的复杂性
|
||||||
|
*
|
||||||
|
* @param secret 密钥
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public Argon2 setSecret(byte[] secret){
|
||||||
|
this.paramsBuilder.withSecret(secret);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param additional 附加数据
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public Argon2 setAdditional(byte[] additional){
|
||||||
|
this.paramsBuilder.withAdditional(additional);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置迭代次数<br>
|
||||||
|
* 迭代次数越多,生成哈希的时间就越长,破解哈希就越困难
|
||||||
|
*
|
||||||
|
* @param iterations 迭代次数
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public Argon2 setIterations(int iterations){
|
||||||
|
this.paramsBuilder.withIterations(iterations);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置内存,单位KB<br>
|
||||||
|
* 内存越大,生成哈希的时间就越长,破解哈希就越困难
|
||||||
|
*
|
||||||
|
* @param memoryAsKB 内存,单位KB
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public Argon2 setMemoryAsKB(int memoryAsKB){
|
||||||
|
this.paramsBuilder.withMemoryAsKB(memoryAsKB);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置并行度,即同时使用的核心数<br>
|
||||||
|
* 值越高,生成哈希的时间就越长,破解哈希就越困难
|
||||||
|
*
|
||||||
|
* @param parallelism 并行度
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public Argon2 setParallelism(int parallelism){
|
||||||
|
this.paramsBuilder.withParallelism(parallelism);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成hash值
|
||||||
|
*
|
||||||
|
* @param password 密码
|
||||||
|
* @return hash值
|
||||||
|
*/
|
||||||
|
public byte[] digest(char[] password){
|
||||||
|
final Argon2BytesGenerator generator = new Argon2BytesGenerator();
|
||||||
|
generator.init(paramsBuilder.build());
|
||||||
|
byte[] result = new byte[hashLength];
|
||||||
|
generator.generateBytes(password, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package cn.hutool.crypto.digest;
|
||||||
|
|
||||||
|
import cn.hutool.core.codec.Base64;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
public class Argon2Test {
|
||||||
|
@Test
|
||||||
|
public void argon2Test() {
|
||||||
|
Argon2 argon2 = new Argon2();
|
||||||
|
final byte[] digest = argon2.digest("123456".toCharArray());
|
||||||
|
Assertions.assertEquals("wVGMOdzf5EdKGANPeHjaUnaFEJA0BnAq6HcF2psFmFo=", Base64.encode(digest));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void argon2WithSaltTest() {
|
||||||
|
final Argon2 argon2 = new Argon2();
|
||||||
|
argon2.setSalt("123456".getBytes());
|
||||||
|
final byte[] digest = argon2.digest("123456".toCharArray());
|
||||||
|
Assertions.assertEquals("sEpbXTdMWra36JXPVxrZMm3xyoR5GkMlLhtW0Kwp9Ag=", Base64.encode(digest));
|
||||||
|
}
|
||||||
|
}
|
@ -9,7 +9,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>cn.hutool</groupId>
|
<groupId>cn.hutool</groupId>
|
||||||
<artifactId>hutool-parent</artifactId>
|
<artifactId>hutool-parent</artifactId>
|
||||||
<version>5.8.37</version>
|
<version>5.8.38-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hutool-db</artifactId>
|
<artifactId>hutool-db</artifactId>
|
||||||
@ -185,5 +185,11 @@
|
|||||||
<version>23.5.0.24.07</version>
|
<version>23.5.0.24.07</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.sap.cloud.db.jdbc</groupId>
|
||||||
|
<artifactId>ngdbc</artifactId>
|
||||||
|
<version>2.24.7</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
@ -62,6 +62,8 @@ public class DialectFactory implements DriverNamePool {
|
|||||||
return new PhoenixDialect();
|
return new PhoenixDialect();
|
||||||
} else if (DRIVER_DM7.equalsIgnoreCase(driverName)) {
|
} else if (DRIVER_DM7.equalsIgnoreCase(driverName)) {
|
||||||
return new DmDialect();
|
return new DmDialect();
|
||||||
|
} else if (DRIVER_HANA.equalsIgnoreCase(driverName)) {
|
||||||
|
return new HanaDialect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 无法识别可支持的数据库类型默认使用ANSI方言,可兼容大部分SQL语句
|
// 无法识别可支持的数据库类型默认使用ANSI方言,可兼容大部分SQL语句
|
||||||
@ -168,6 +170,9 @@ public class DialectFactory implements DriverNamePool {
|
|||||||
} else if (nameContainsProductInfo.contains("goldendb")) {
|
} else if (nameContainsProductInfo.contains("goldendb")) {
|
||||||
// GoldenDB
|
// GoldenDB
|
||||||
driver = DRIVER_GOLDENDB;
|
driver = DRIVER_GOLDENDB;
|
||||||
|
} else if (nameContainsProductInfo.contains("sap")) {
|
||||||
|
// sap hana
|
||||||
|
driver = DRIVER_HANA;
|
||||||
}
|
}
|
||||||
|
|
||||||
return driver;
|
return driver;
|
||||||
|
@ -9,7 +9,7 @@ import cn.hutool.core.util.StrUtil;
|
|||||||
* @author Looly
|
* @author Looly
|
||||||
*/
|
*/
|
||||||
public enum DialectName {
|
public enum DialectName {
|
||||||
ANSI, MYSQL, ORACLE, POSTGRESQL, SQLITE3, H2, SQLSERVER, SQLSERVER2012, PHOENIX, DM;
|
ANSI, MYSQL, ORACLE, POSTGRESQL, SQLITE3, H2, SQLSERVER, SQLSERVER2012, PHOENIX, DM, HANA;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否为指定数据库方言,检查时不分区大小写
|
* 是否为指定数据库方言,检查时不分区大小写
|
||||||
|
@ -120,4 +120,8 @@ public interface DriverNamePool {
|
|||||||
* JDBC 驱动 GoldenDB
|
* JDBC 驱动 GoldenDB
|
||||||
*/
|
*/
|
||||||
String DRIVER_GOLDENDB = "com.goldendb.jdbc.Driver";
|
String DRIVER_GOLDENDB = "com.goldendb.jdbc.Driver";
|
||||||
|
/**
|
||||||
|
* JDBC 驱动 Sap Hana
|
||||||
|
*/
|
||||||
|
String DRIVER_HANA = "com.sap.db.jdbc.Driver";
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,97 @@
|
|||||||
|
package cn.hutool.db.dialect.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.db.Entity;
|
||||||
|
import cn.hutool.db.Page;
|
||||||
|
import cn.hutool.db.StatementUtil;
|
||||||
|
import cn.hutool.db.dialect.DialectName;
|
||||||
|
import cn.hutool.db.sql.SqlBuilder;
|
||||||
|
import cn.hutool.db.sql.Wrapper;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hana数据库方言
|
||||||
|
*
|
||||||
|
* @author daoyou.dev
|
||||||
|
*/
|
||||||
|
public class HanaDialect extends AnsiSqlDialect {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
*/
|
||||||
|
public HanaDialect() {
|
||||||
|
wrapper = new Wrapper('"');
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String dialectName() {
|
||||||
|
return DialectName.HANA.name();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SqlBuilder wrapPageSql(SqlBuilder find, Page page) {
|
||||||
|
// SAP HANA 使用 OFFSET LIMIT 分页
|
||||||
|
return find.append(" LIMIT ").append(page.getPageSize())
|
||||||
|
.append(" OFFSET ").append(page.getStartPosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建用于upsert的{@link PreparedStatement}。
|
||||||
|
* SAP HANA 使用 MERGE INTO 语法来实现 UPSERT 操作。
|
||||||
|
* <p>
|
||||||
|
* 生成 SQL 语法为:
|
||||||
|
* <pre>
|
||||||
|
* MERGE INTO demo AS target
|
||||||
|
* USING (SELECT ? AS a, ? AS b, ? AS c FROM DUMMY) AS source
|
||||||
|
* ON target.id = source.id
|
||||||
|
* WHEN MATCHED THEN
|
||||||
|
* UPDATE SET target.a = source.a, target.b = source.b, target.c = source.c
|
||||||
|
* WHEN NOT MATCHED THEN
|
||||||
|
* INSERT (a, b, c) VALUES (source.a, source.b, source.c);
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param conn 数据库连接对象
|
||||||
|
* @param entity 数据实体类(包含表名)
|
||||||
|
* @param keys 主键字段数组,通常用于确定匹配条件(联合主键)
|
||||||
|
* @return PreparedStatement
|
||||||
|
* @throws SQLException SQL 执行异常
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public PreparedStatement psForUpsert(Connection conn, Entity entity, String... keys) throws SQLException {
|
||||||
|
SqlBuilder.validateEntity(entity);
|
||||||
|
final SqlBuilder builder = SqlBuilder.create(wrapper);
|
||||||
|
|
||||||
|
final List<String> columns = new ArrayList<>();
|
||||||
|
final List<Object> values = new ArrayList<>();
|
||||||
|
|
||||||
|
// 构建字段部分和参数占位符部分
|
||||||
|
entity.forEach((field, value) -> {
|
||||||
|
if (StrUtil.isNotBlank(field)) {
|
||||||
|
columns.add(wrapper != null ? wrapper.wrap(field) : field);
|
||||||
|
values.add(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
String tableName = entity.getTableName();
|
||||||
|
if (wrapper != null) {
|
||||||
|
tableName = wrapper.wrap(tableName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建 UPSERT 语句
|
||||||
|
builder.append("UPSERT ").append(tableName).append(" (");
|
||||||
|
builder.append(String.join(", ", columns));
|
||||||
|
builder.append(") VALUES (");
|
||||||
|
builder.append(String.join(", ", Collections.nCopies(columns.size(), "?")));
|
||||||
|
builder.append(") WITH PRIMARY KEY");
|
||||||
|
|
||||||
|
return StatementUtil.prepareStatement(conn, builder.toString(), values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
89
hutool-db/src/test/java/cn/hutool/db/HanaTest.java
Normal file
89
hutool-db/src/test/java/cn/hutool/db/HanaTest.java
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
package cn.hutool.db;
|
||||||
|
|
||||||
|
import cn.hutool.core.lang.Console;
|
||||||
|
import org.junit.jupiter.api.Disabled;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* hana操作单元测试
|
||||||
|
*
|
||||||
|
* @author daoyou.dev
|
||||||
|
*/
|
||||||
|
public class HanaTest {
|
||||||
|
//@BeforeAll
|
||||||
|
public static void createTable() throws SQLException {
|
||||||
|
Db db = Db.use("hana");
|
||||||
|
long count = db.count("SELECT * FROM SYS.TABLES WHERE TABLE_NAME = ? AND SCHEMA_NAME = CURRENT_SCHEMA", "user");
|
||||||
|
if (count > 0) {
|
||||||
|
db.execute("drop table \"user\"");
|
||||||
|
}
|
||||||
|
db.execute("CREATE COLUMN TABLE \"user\" (\"id\" INT NOT NULL, \"account\" VARCHAR(255), \"name\" VARCHAR(255), \"text\" VARCHAR(255), \"test1\" VARCHAR(255), \"pass\" VARCHAR(255), PRIMARY KEY (\"id\"))");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Disabled
|
||||||
|
public void insertTest() throws SQLException {
|
||||||
|
DbUtil.setReturnGeneratedKeyGlobal(false);
|
||||||
|
for (int id = 100; id < 200; id++) {
|
||||||
|
Db.use("hana").insert(Entity.create("user")//
|
||||||
|
.set("id", id)//
|
||||||
|
.set("name", "测试用户" + id)//
|
||||||
|
.set("text", "描述" + id)//
|
||||||
|
.set("test1", "t" + id)//
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事务测试<br>
|
||||||
|
* 更新三条信息,低2条后抛出异常,正常情况下三条都应该不变
|
||||||
|
*
|
||||||
|
* @throws SQLException SQL异常
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
@Disabled
|
||||||
|
public void txTest() throws SQLException {
|
||||||
|
Db.use("hana").tx(db -> {
|
||||||
|
int update = db.update(Entity.create("user").set("text", "描述100"), Entity.create().set("id", 100));
|
||||||
|
db.update(Entity.create("user").set("text", "描述101"), Entity.create().set("id", 101));
|
||||||
|
if (1 == update) {
|
||||||
|
// 手动指定异常,然后测试回滚触发
|
||||||
|
throw new RuntimeException("Error");
|
||||||
|
}
|
||||||
|
db.update(Entity.create("user").set("text", "描述102"), Entity.create().set("id", 102));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Disabled
|
||||||
|
public void pageTest() throws SQLException {
|
||||||
|
PageResult<Entity> result = Db.use("hana").page(Entity.create("\"user\""), new Page(2, 10));
|
||||||
|
for (Entity entity : result) {
|
||||||
|
Console.log(entity.get("id"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Disabled
|
||||||
|
public void getTimeStampTest() throws SQLException {
|
||||||
|
final List<Entity> all = Db.use("hana").findAll("user");
|
||||||
|
Console.log(all);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Disabled
|
||||||
|
public void upsertTest() throws SQLException {
|
||||||
|
DbUtil.setReturnGeneratedKeyGlobal(false);
|
||||||
|
Db db = Db.use("hana");
|
||||||
|
db.insert(Entity.create("user").set("id", 1).set("account", "ice").set("pass", "123456"));
|
||||||
|
db.upsert(Entity.create("user").set("id", 1).set("account", "daoyou").set("pass", "a123456").set("name", "道友"));
|
||||||
|
Entity user = db.get(Entity.create("user").set("id", 1));
|
||||||
|
System.out.println("user=======" + user.getStr("account") + "___" + user.getStr("pass"));
|
||||||
|
assertEquals("daoyou", user.getStr("account"));
|
||||||
|
}
|
||||||
|
}
|
@ -77,3 +77,9 @@ user = SYSDBA
|
|||||||
pass = 123456789
|
pass = 123456789
|
||||||
remarks = true
|
remarks = true
|
||||||
|
|
||||||
|
[hana]
|
||||||
|
url = jdbc:sap://127.0.0.1:30015/HAP_CONN?autoReconnect=true
|
||||||
|
user = DB
|
||||||
|
pass = 123456
|
||||||
|
remarks = true
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>cn.hutool</groupId>
|
<groupId>cn.hutool</groupId>
|
||||||
<artifactId>hutool-parent</artifactId>
|
<artifactId>hutool-parent</artifactId>
|
||||||
<version>5.8.37</version>
|
<version>5.8.38-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hutool-dfa</artifactId>
|
<artifactId>hutool-dfa</artifactId>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>cn.hutool</groupId>
|
<groupId>cn.hutool</groupId>
|
||||||
<artifactId>hutool-parent</artifactId>
|
<artifactId>hutool-parent</artifactId>
|
||||||
<version>5.8.37</version>
|
<version>5.8.38-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hutool-extra</artifactId>
|
<artifactId>hutool-extra</artifactId>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>cn.hutool</groupId>
|
<groupId>cn.hutool</groupId>
|
||||||
<artifactId>hutool-parent</artifactId>
|
<artifactId>hutool-parent</artifactId>
|
||||||
<version>5.8.37</version>
|
<version>5.8.38-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hutool-http</artifactId>
|
<artifactId>hutool-http</artifactId>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>cn.hutool</groupId>
|
<groupId>cn.hutool</groupId>
|
||||||
<artifactId>hutool-parent</artifactId>
|
<artifactId>hutool-parent</artifactId>
|
||||||
<version>5.8.37</version>
|
<version>5.8.38-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hutool-json</artifactId>
|
<artifactId>hutool-json</artifactId>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>cn.hutool</groupId>
|
<groupId>cn.hutool</groupId>
|
||||||
<artifactId>hutool-parent</artifactId>
|
<artifactId>hutool-parent</artifactId>
|
||||||
<version>5.8.37</version>
|
<version>5.8.38-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hutool-jwt</artifactId>
|
<artifactId>hutool-jwt</artifactId>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>cn.hutool</groupId>
|
<groupId>cn.hutool</groupId>
|
||||||
<artifactId>hutool-parent</artifactId>
|
<artifactId>hutool-parent</artifactId>
|
||||||
<version>5.8.37</version>
|
<version>5.8.38-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hutool-log</artifactId>
|
<artifactId>hutool-log</artifactId>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>cn.hutool</groupId>
|
<groupId>cn.hutool</groupId>
|
||||||
<artifactId>hutool-parent</artifactId>
|
<artifactId>hutool-parent</artifactId>
|
||||||
<version>5.8.37</version>
|
<version>5.8.38-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hutool-poi</artifactId>
|
<artifactId>hutool-poi</artifactId>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>cn.hutool</groupId>
|
<groupId>cn.hutool</groupId>
|
||||||
<artifactId>hutool-parent</artifactId>
|
<artifactId>hutool-parent</artifactId>
|
||||||
<version>5.8.37</version>
|
<version>5.8.38-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hutool-script</artifactId>
|
<artifactId>hutool-script</artifactId>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>cn.hutool</groupId>
|
<groupId>cn.hutool</groupId>
|
||||||
<artifactId>hutool-parent</artifactId>
|
<artifactId>hutool-parent</artifactId>
|
||||||
<version>5.8.37</version>
|
<version>5.8.38-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hutool-setting</artifactId>
|
<artifactId>hutool-setting</artifactId>
|
||||||
|
@ -4,14 +4,11 @@ import cn.hutool.core.collection.CollUtil;
|
|||||||
import cn.hutool.core.convert.Convert;
|
import cn.hutool.core.convert.Convert;
|
||||||
import cn.hutool.core.io.FileUtil;
|
import cn.hutool.core.io.FileUtil;
|
||||||
import cn.hutool.core.io.IoUtil;
|
import cn.hutool.core.io.IoUtil;
|
||||||
import cn.hutool.core.io.resource.ClassPathResource;
|
import cn.hutool.core.io.resource.*;
|
||||||
import cn.hutool.core.io.resource.FileResource;
|
|
||||||
import cn.hutool.core.io.resource.Resource;
|
|
||||||
import cn.hutool.core.io.resource.ResourceUtil;
|
|
||||||
import cn.hutool.core.io.resource.UrlResource;
|
|
||||||
import cn.hutool.core.io.watch.SimpleWatcher;
|
import cn.hutool.core.io.watch.SimpleWatcher;
|
||||||
import cn.hutool.core.io.watch.WatchMonitor;
|
import cn.hutool.core.io.watch.WatchMonitor;
|
||||||
import cn.hutool.core.io.watch.WatchUtil;
|
import cn.hutool.core.io.watch.WatchUtil;
|
||||||
|
import cn.hutool.core.io.watch.watchers.DelayWatcher;
|
||||||
import cn.hutool.core.lang.Assert;
|
import cn.hutool.core.lang.Assert;
|
||||||
import cn.hutool.core.util.CharUtil;
|
import cn.hutool.core.util.CharUtil;
|
||||||
import cn.hutool.core.util.CharsetUtil;
|
import cn.hutool.core.util.CharsetUtil;
|
||||||
@ -24,12 +21,7 @@ import java.net.URL;
|
|||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.WatchEvent;
|
import java.nio.file.WatchEvent;
|
||||||
import java.util.Collection;
|
import java.util.*;
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -229,7 +221,7 @@ public class Setting extends AbsSetting implements Map<String, String> {
|
|||||||
// 先关闭之前的监听
|
// 先关闭之前的监听
|
||||||
this.watchMonitor.close();
|
this.watchMonitor.close();
|
||||||
}
|
}
|
||||||
this.watchMonitor = WatchUtil.createModify(resource.getUrl(), new SimpleWatcher() {
|
this.watchMonitor = WatchUtil.createModify(resource.getUrl(), new DelayWatcher(new SimpleWatcher() {
|
||||||
@Override
|
@Override
|
||||||
public void onModify(WatchEvent<?> event, Path currentPath) {
|
public void onModify(WatchEvent<?> event, Path currentPath) {
|
||||||
boolean success = load();
|
boolean success = load();
|
||||||
@ -238,7 +230,8 @@ public class Setting extends AbsSetting implements Map<String, String> {
|
|||||||
callback.accept(success);
|
callback.accept(success);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}, 600));
|
||||||
|
|
||||||
this.watchMonitor.start();
|
this.watchMonitor.start();
|
||||||
StaticLog.debug("Auto load for [{}] listenning...", this.resource.getUrl());
|
StaticLog.debug("Auto load for [{}] listenning...", this.resource.getUrl());
|
||||||
} else {
|
} else {
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>cn.hutool</groupId>
|
<groupId>cn.hutool</groupId>
|
||||||
<artifactId>hutool-parent</artifactId>
|
<artifactId>hutool-parent</artifactId>
|
||||||
<version>5.8.37</version>
|
<version>5.8.38-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hutool-socket</artifactId>
|
<artifactId>hutool-socket</artifactId>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>cn.hutool</groupId>
|
<groupId>cn.hutool</groupId>
|
||||||
<artifactId>hutool-parent</artifactId>
|
<artifactId>hutool-parent</artifactId>
|
||||||
<version>5.8.37</version>
|
<version>5.8.38-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hutool-system</artifactId>
|
<artifactId>hutool-system</artifactId>
|
||||||
|
8
pom.xml
8
pom.xml
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<groupId>cn.hutool</groupId>
|
<groupId>cn.hutool</groupId>
|
||||||
<artifactId>hutool-parent</artifactId>
|
<artifactId>hutool-parent</artifactId>
|
||||||
<version>5.8.37</version>
|
<version>5.8.38-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/chinabugotech/hutool</url>
|
<url>https://github.com/chinabugotech/hutool</url>
|
||||||
@ -112,6 +112,12 @@
|
|||||||
<compilerArgument>-Xlint:unchecked</compilerArgument>
|
<compilerArgument>-Xlint:unchecked</compilerArgument>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<!-- 单元测试 -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>3.5.3</version>
|
||||||
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-jar-plugin</artifactId>
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
Loading…
Reference in New Issue
Block a user