mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-04-19 03:01:48 +08:00
Prepare release
This commit is contained in:
commit
19001e232f
31
CHANGELOG.md
31
CHANGELOG.md
@ -2,7 +2,38 @@
|
||||
# 🚀Changelog
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
# 5.7.21 (2022-02-14)
|
||||
|
||||
### 🐣新特性
|
||||
* 【extra 】 增加jetbrick模板支持
|
||||
* 【extra 】 EmojiUtil增加方法(pr#519@Gitee)
|
||||
* 【core 】 DateUtil 添加两个日期是否同一周方法(pr#516@Gitee)
|
||||
* 【db 】 新增条件组,用于处理复杂的where条件(pr#514@Gitee)
|
||||
* 【core 】 新增LocalDateTimeUtil.weekOfYear(issue#I4RWXC@Gitee)
|
||||
* 【core 】 Month增加toJdkMonth、getValueBaseOne
|
||||
* 【core 】 CsvWriter修改规则,去除末尾多余换行符(issue#I4RSQY@Gitee)
|
||||
* 【core 】 DateUtil增加rangeFunc和rangeConsume(issue#I4RSQY@Gitee)
|
||||
* 【core 】 DateTime增加setUseJdkToStringStyle方法
|
||||
* 【core 】 CharSequenceUtil增加replace重载(issue#2122@Github)
|
||||
* 【core 】 IntMap和LongMap使用位运算快速求解取余运算(pr#2123@Github)
|
||||
* 【core 】 新增通用builder类:GenericBuilder(pr#526@Gitee)
|
||||
* 【core 】 新增copySafely方法与mkdirsSafely方法(pr#527@Gitee)
|
||||
* 【core 】 新增MetroHash(pr#532@Gitee)
|
||||
* 【core 】 SpringUtil增加publishEvent重载(pr#2139@Github)
|
||||
* 【core 】 DateUtil增加rangeContains、rangeNotContains(pr#537@Gitee)
|
||||
* 【core 】 Resource增加isModified默认方法
|
||||
* 【core 】 增加VfsResource
|
||||
* 【json 】 JSONConfig增加setKeyComparator、setNatureKeyComparator方法,支持自定义排序(issue#I4RBZ4@Gitee)
|
||||
|
||||
### 🐞Bug修复
|
||||
* 【core 】 修复ChineseDate农历获取正月出现数组越界BUG(issue#2112@Github)
|
||||
* 【extra 】 修复EmojiUtil.toHtmlHex()方法(pr#519@Gitee)
|
||||
* 【system 】 修复CpuInfo.getUsed()方法(issue#2116@Github)
|
||||
* 【dfa 】 修复密集匹配和贪婪匹配冲突问题(issue#2126@Github)
|
||||
* 【db 】 修复c3p0丢失信息问题(issue#I4T7XZ@Gitee)
|
||||
* 【http 】 修复Action中HttpExchange没有关闭问题
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
# 5.7.20 (2022-01-20)
|
||||
|
||||
### 🐣新特性
|
||||
|
@ -12,7 +12,7 @@
|
||||
<a target="_blank" href="https://search.maven.org/artifact/cn.hutool/hutool-all">
|
||||
<img src="https://img.shields.io/maven-central/v/cn.hutool/hutool-all.svg?label=Maven%20Central" />
|
||||
</a>
|
||||
<a target="_blank" href="https://license.coscl.org.cn/MulanPSL2/">
|
||||
<a target="_blank" href="http://license.coscl.org.cn/MulanPSL2/index.html">
|
||||
<img src="https://img.shields.io/:license-MulanPSL2-blue.svg" />
|
||||
</a>
|
||||
<a target="_blank" href="https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html">
|
||||
@ -142,18 +142,18 @@ We provide the T-Shirt and Sweater with Hutool Logo, please visit the shop:
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.7.20</version>
|
||||
<version>5.7.21</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### 🍐Gradle
|
||||
```
|
||||
implementation 'cn.hutool:hutool-all:5.7.20'
|
||||
implementation 'cn.hutool:hutool-all:5.7.21'
|
||||
```
|
||||
|
||||
## 📥Download
|
||||
|
||||
- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.7.20/)
|
||||
- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.7.21/)
|
||||
|
||||
> 🔔️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.
|
||||
|
@ -12,7 +12,7 @@
|
||||
<a target="_blank" href="https://search.maven.org/artifact/cn.hutool/hutool-all">
|
||||
<img src="https://img.shields.io/maven-central/v/cn.hutool/hutool-all.svg?label=Maven%20Central" />
|
||||
</a>
|
||||
<a target="_blank" href="https://license.coscl.org.cn/MulanPSL2/">
|
||||
<a target="_blank" href="http://license.coscl.org.cn/MulanPSL2/index.html">
|
||||
<img src="https://img.shields.io/:license-MulanPSL2-blue.svg" />
|
||||
</a>
|
||||
<a target="_blank" href="https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html">
|
||||
@ -142,20 +142,20 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.7.20</version>
|
||||
<version>5.7.21</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### 🍐Gradle
|
||||
```
|
||||
implementation 'cn.hutool:hutool-all:5.7.20'
|
||||
implementation 'cn.hutool:hutool-all:5.7.21'
|
||||
```
|
||||
|
||||
### 📥下载jar
|
||||
|
||||
点击以下链接,下载`hutool-all-X.X.X.jar`即可:
|
||||
|
||||
- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.7.20/)
|
||||
- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.7.21/)
|
||||
|
||||
> 🔔️注意
|
||||
> Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。
|
||||
|
@ -7,5 +7,5 @@ echo ' / /_/ // / / // __// __ \ / __ \ / / '
|
||||
echo ' / __ // /_/ // /_ / /_/ // /_/ // / '
|
||||
echo '/_/ /_/ \____/ \__/ \____/ \____//_/ '
|
||||
echo ''
|
||||
echo '-----------http://hutool.cn/------------'
|
||||
echo '-----------https://hutool.cn/-----------'
|
||||
echo '========================================'
|
||||
|
@ -1 +1 @@
|
||||
5.7.20
|
||||
5.7.21
|
||||
|
@ -1 +1 @@
|
||||
var version = '5.7.20'
|
||||
var version = '5.7.21'
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.20</version>
|
||||
<version>5.7.21</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-all</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.20</version>
|
||||
<version>5.7.21</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-aop</artifactId>
|
||||
@ -19,7 +19,7 @@
|
||||
<properties>
|
||||
<!-- versions -->
|
||||
<cglib.version>3.3.0</cglib.version>
|
||||
<spring.version>5.3.14</spring.version>
|
||||
<spring.version>5.3.15</spring.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.20</version>
|
||||
<version>5.7.21</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-bloomFilter</artifactId>
|
||||
|
@ -32,21 +32,21 @@ public class IntMap implements BitMap, Serializable {
|
||||
@Override
|
||||
public void add(long i) {
|
||||
int r = (int) (i / BitMap.MACHINE32);
|
||||
int c = (int) (i % BitMap.MACHINE32);
|
||||
int c = (int) (i & (BitMap.MACHINE32 - 1));
|
||||
ints[r] = ints[r] | (1 << c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(long i) {
|
||||
int r = (int) (i / BitMap.MACHINE32);
|
||||
int c = (int) (i % BitMap.MACHINE32);
|
||||
int c = (int) (i & (BitMap.MACHINE32 - 1));
|
||||
return ((ints[r] >>> c) & 1) == 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(long i) {
|
||||
int r = (int) (i / BitMap.MACHINE32);
|
||||
int c = (int) (i % BitMap.MACHINE32);
|
||||
int c = (int) (i & (BitMap.MACHINE32 - 1));
|
||||
ints[r] &= ~(1 << c);
|
||||
}
|
||||
|
||||
|
@ -32,21 +32,21 @@ public class LongMap implements BitMap, Serializable {
|
||||
@Override
|
||||
public void add(long i) {
|
||||
int r = (int) (i / BitMap.MACHINE64);
|
||||
long c = i % BitMap.MACHINE64;
|
||||
long c = i & (BitMap.MACHINE64 - 1);
|
||||
longs[r] = longs[r] | (1L << c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(long i) {
|
||||
int r = (int) (i / BitMap.MACHINE64);
|
||||
long c = i % BitMap.MACHINE64;
|
||||
long c = i & (BitMap.MACHINE64 - 1);
|
||||
return ((longs[r] >>> c) & 1) == 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(long i) {
|
||||
int r = (int) (i / BitMap.MACHINE64);
|
||||
long c = i % BitMap.MACHINE64;
|
||||
long c = i & (BitMap.MACHINE64 - 1);
|
||||
longs[r] &= ~(1L << c);
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.20</version>
|
||||
<version>5.7.21</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-bom</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.20</version>
|
||||
<version>5.7.21</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-cache</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.20</version>
|
||||
<version>5.7.21</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-captcha</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.20</version>
|
||||
<version>5.7.21</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-core</artifactId>
|
||||
|
@ -6,8 +6,8 @@ import cn.hutool.core.lang.func.Func0;
|
||||
/**
|
||||
* Bean属性缓存<br>
|
||||
* 缓存用于防止多次反射造成的性能问题
|
||||
* @author Looly
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public enum BeanDescCache {
|
||||
INSTANCE;
|
||||
@ -16,12 +16,22 @@ public enum BeanDescCache {
|
||||
|
||||
/**
|
||||
* 获得属性名和{@link BeanDesc}Map映射
|
||||
*
|
||||
* @param beanClass Bean的类
|
||||
* @param supplier 对象不存在时创建对象的函数
|
||||
* @param supplier 对象不存在时创建对象的函数
|
||||
* @return 属性名和{@link BeanDesc}映射
|
||||
* @since 5.4.2
|
||||
*/
|
||||
public BeanDesc getBeanDesc(Class<?> beanClass, Func0<BeanDesc> supplier){
|
||||
public BeanDesc getBeanDesc(Class<?> beanClass, Func0<BeanDesc> supplier) {
|
||||
return bdCache.get(beanClass, supplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空全局的Bean属性缓存
|
||||
*
|
||||
* @since 5.7.21
|
||||
*/
|
||||
public void clear() {
|
||||
this.bdCache.clear();
|
||||
}
|
||||
}
|
||||
|
@ -56,6 +56,16 @@ public enum BeanInfoCache {
|
||||
getCache(ignoreCase).put(beanClass, fieldNamePropertyDescriptorMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空缓存
|
||||
*
|
||||
* @since 5.7.21
|
||||
*/
|
||||
public void clear() {
|
||||
this.pdCache.clear();
|
||||
this.ignoreCasePdCache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据是否忽略字段名的大小写,返回不用Cache对象
|
||||
*
|
||||
|
@ -0,0 +1,208 @@
|
||||
package cn.hutool.core.builder;
|
||||
|
||||
import cn.hutool.core.lang.func.Supplier1;
|
||||
import cn.hutool.core.lang.func.Supplier2;
|
||||
import cn.hutool.core.lang.func.Supplier3;
|
||||
import cn.hutool.core.lang.func.Supplier4;
|
||||
import cn.hutool.core.lang.func.Supplier5;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* <p>通用Builder</p>
|
||||
* 参考: <a href="https://blog.csdn.net/weixin_43935907/article/details/105003719">一看就会的java8通用Builder</a>
|
||||
* <p>使用方法如下:</p>
|
||||
* <pre>
|
||||
* Box box = GenericBuilder
|
||||
* .of(Box::new)
|
||||
* .with(Box::setId, 1024L)
|
||||
* .with(Box::setTitle, "Hello World!")
|
||||
* .with(Box::setLength, 9)
|
||||
* .with(Box::setWidth, 8)
|
||||
* .with(Box::setHeight, 7)
|
||||
* .build();
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
* <p> 我们也可以对已创建的对象进行修改:</p>
|
||||
* <pre>
|
||||
* Box boxModified = GenericBuilder
|
||||
* .of(() -> box)
|
||||
* .with(Box::setTitle, "Hello Friend!")
|
||||
* .with(Box::setLength, 3)
|
||||
* .with(Box::setWidth, 4)
|
||||
* .with(Box::setHeight, 5)
|
||||
* .build();
|
||||
* </pre>
|
||||
* <p> 我们还可以对这样调用有参构造,这对于创建一些在有参构造中包含初始化函数的对象是有意义的:</p>
|
||||
* <pre>
|
||||
* Box box1 = GenericBuilder
|
||||
* .of(Box::new, 2048L, "Hello Partner!", 222, 333, 444)
|
||||
* .with(Box::alis)
|
||||
* .build();
|
||||
* </pre>
|
||||
* <p>注意:本工具类支持调用的方法的参数数量不超过1个,更多的参数不利于阅读和维护。</p>
|
||||
*
|
||||
* @author TomXin
|
||||
* @since 5.7.21
|
||||
*/
|
||||
public class GenericBuilder<T> implements Builder<T> {
|
||||
|
||||
/**
|
||||
* 实例化器
|
||||
*/
|
||||
private final Supplier<T> instant;
|
||||
|
||||
/**
|
||||
* 修改器列表
|
||||
*/
|
||||
private final List<Consumer<T>> modifiers = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param instant 实例化器
|
||||
*/
|
||||
public GenericBuilder(Supplier<T> instant) {
|
||||
this.instant = instant;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过无参数实例化器创建GenericBuilder
|
||||
*
|
||||
* @param instant 实例化器
|
||||
* @param <T> 目标类型
|
||||
* @return GenericBuilder对象
|
||||
*/
|
||||
public static <T> GenericBuilder<T> of(Supplier<T> instant) {
|
||||
return new GenericBuilder<>(instant);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过1参数实例化器创建GenericBuilder
|
||||
*
|
||||
* @param instant 实例化器
|
||||
* @param p1 参数一
|
||||
* @param <T> 目标类型
|
||||
* @param <P1> 参数一类型
|
||||
* @return GenericBuilder对象
|
||||
*/
|
||||
public static <T, P1> GenericBuilder<T> of(Supplier1<T, P1> instant, P1 p1) {
|
||||
return of(instant.toSupplier(p1));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过2参数实例化器创建GenericBuilder
|
||||
*
|
||||
* @param instant 实例化器
|
||||
* @param p1 参数一
|
||||
* @param p2 参数二
|
||||
* @param <T> 目标类型
|
||||
* @param <P1> 参数一类型
|
||||
* @param <P2> 参数二类型
|
||||
* @return GenericBuilder对象
|
||||
*/
|
||||
public static <T, P1, P2> GenericBuilder<T> of(Supplier2<T, P1, P2> instant, P1 p1, P2 p2) {
|
||||
return of(instant.toSupplier(p1, p2));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过3参数实例化器创建GenericBuilder
|
||||
*
|
||||
* @param instant 实例化器
|
||||
* @param p1 参数一
|
||||
* @param p2 参数二
|
||||
* @param p3 参数三
|
||||
* @param <T> 目标类型
|
||||
* @param <P1> 参数一类型
|
||||
* @param <P2> 参数二类型
|
||||
* @param <P3> 参数三类型
|
||||
* @return GenericBuilder对象
|
||||
*/
|
||||
public static <T, P1, P2, P3> GenericBuilder<T> of(Supplier3<T, P1, P2, P3> instant, P1 p1, P2 p2, P3 p3) {
|
||||
return of(instant.toSupplier(p1, p2, p3));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过4参数实例化器创建GenericBuilder
|
||||
*
|
||||
* @param instant 实例化器
|
||||
* @param p1 参数一
|
||||
* @param p2 参数二
|
||||
* @param p3 参数三
|
||||
* @param p4 参数四
|
||||
* @param <T> 目标类型
|
||||
* @param <P1> 参数一类型
|
||||
* @param <P2> 参数二类型
|
||||
* @param <P3> 参数三类型
|
||||
* @param <P4> 参数四类型
|
||||
* @return GenericBuilder对象
|
||||
*/
|
||||
public static <T, P1, P2, P3, P4> GenericBuilder<T> of(Supplier4<T, P1, P2, P3, P4> instant, P1 p1, P2 p2, P3 p3, P4 p4) {
|
||||
return of(instant.toSupplier(p1, p2, p3, p4));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过5参数实例化器创建GenericBuilder
|
||||
*
|
||||
* @param instant 实例化器
|
||||
* @param p1 参数一
|
||||
* @param p2 参数二
|
||||
* @param p3 参数三
|
||||
* @param p4 参数四
|
||||
* @param p5 参数五
|
||||
* @param <T> 目标类型
|
||||
* @param <P1> 参数一类型
|
||||
* @param <P2> 参数二类型
|
||||
* @param <P3> 参数三类型
|
||||
* @param <P4> 参数四类型
|
||||
* @param <P5> 参数五类型
|
||||
* @return GenericBuilder对象
|
||||
*/
|
||||
public static <T, P1, P2, P3, P4, P5> GenericBuilder<T> of(Supplier5<T, P1, P2, P3, P4, P5> instant, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5) {
|
||||
return of(instant.toSupplier(p1, p2, p3, p4, p5));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 调用无参数方法
|
||||
*
|
||||
* @param consumer 无参数Consumer
|
||||
* @return GenericBuilder对象
|
||||
*/
|
||||
public GenericBuilder<T> with(Consumer<T> consumer) {
|
||||
modifiers.add(consumer);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 调用1参数方法
|
||||
*
|
||||
* @param <P1> 参数一类型
|
||||
* @param consumer 1参数Consumer,一般为Setter方法引用
|
||||
* @param p1 参数一
|
||||
* @return GenericBuilder对象
|
||||
*/
|
||||
public <P1> GenericBuilder<T> with(BiConsumer<T, P1> consumer, P1 p1) {
|
||||
modifiers.add(instant -> consumer.accept(instant, p1));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建
|
||||
*
|
||||
* @return 目标对象
|
||||
*/
|
||||
@Override
|
||||
public T build() {
|
||||
T value = instant.get();
|
||||
modifiers.forEach(modifier -> modifier.accept(value));
|
||||
modifiers.clear();
|
||||
return value;
|
||||
}
|
||||
}
|
@ -11,6 +11,18 @@ import java.util.function.Function;
|
||||
*/
|
||||
public class CompareUtil {
|
||||
|
||||
/**
|
||||
* 获取自然排序器,即默认排序器
|
||||
*
|
||||
* @param <E> 排序节点类型
|
||||
* @return 默认排序器
|
||||
* @since 5.7.21
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <E extends Comparable<? super E>> Comparator<E> naturalComparator() {
|
||||
return ComparableComparator.INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象比较,比较结果取决于comparator,如果被比较对象为null,传入的comparator对象应处理此情况<br>
|
||||
* 如果传入comparator为null,则使用默认规则比较(此时被比较对象必须实现Comparable接口)
|
||||
|
@ -135,7 +135,7 @@ public class ConverterRegistry implements Serializable {
|
||||
ServiceLoaderUtil.load(Converter.class).forEach(converter -> {
|
||||
try {
|
||||
Type type = TypeUtil.getTypeArgument(ClassUtil.getClass(converter));
|
||||
if(null != type){
|
||||
if (null != type) {
|
||||
putCustom(type, converter);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@ -266,7 +266,6 @@ public class ConverterRegistry implements Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 特殊类型转换,包括Collection、Map、强转、Array等
|
||||
final T result = convertSpecial(type, rowType, value, defaultValue);
|
||||
if (null != result) {
|
||||
|
@ -2,6 +2,7 @@ package cn.hutool.core.convert;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
/**
|
||||
@ -153,6 +154,27 @@ public class NumberChineseFormatter {
|
||||
return chineseStr.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 阿拉伯数字(支持正负整数)四舍五入后转换成中文节权位简洁计数单位,例如 -5_5555 =》 -5.56万
|
||||
*
|
||||
* @param amount 数字
|
||||
* @return 中文
|
||||
*/
|
||||
public static String formatSimple(long amount) {
|
||||
if (amount < 1_0000 && amount > -1_0000) {
|
||||
return String.valueOf(amount);
|
||||
}
|
||||
String res;
|
||||
if (amount < 1_0000_0000 && amount > -1_0000_0000) {
|
||||
res = NumberUtil.div(amount, 1_0000, 2) + "万";
|
||||
} else if (amount < 1_0000_0000_0000L && amount > -1_0000_0000_0000L) {
|
||||
res = NumberUtil.div(amount, 1_0000_0000, 2) + "亿";
|
||||
} else {
|
||||
res = NumberUtil.div(amount, 1_0000_0000_0000L, 2) + "万亿";
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化-999~999之间的数字<br>
|
||||
* 这个方法显示10~19以下的数字时使用"十一"而非"一十一"。
|
||||
|
@ -132,7 +132,7 @@ public class CalendarUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取秒级别的开始时间,即忽略毫秒部分
|
||||
* 修改秒级别的开始时间,即忽略毫秒部分
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
@ -143,7 +143,7 @@ public class CalendarUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取秒级别的结束时间,即毫秒设置为999
|
||||
* 修改秒级别的结束时间,即毫秒设置为999
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
@ -154,7 +154,7 @@ public class CalendarUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某小时的开始时间
|
||||
* 修改某小时的开始时间
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
@ -164,7 +164,7 @@ public class CalendarUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某小时的结束时间
|
||||
* 修改某小时的结束时间
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
@ -174,7 +174,7 @@ public class CalendarUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某分钟的开始时间
|
||||
* 修改某分钟的开始时间
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
@ -184,7 +184,7 @@ public class CalendarUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某分钟的结束时间
|
||||
* 修改某分钟的结束时间
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
@ -194,7 +194,7 @@ public class CalendarUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某天的开始时间
|
||||
* 修改某天的开始时间
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
@ -204,7 +204,7 @@ public class CalendarUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某天的结束时间
|
||||
* 修改某天的结束时间
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
@ -214,7 +214,7 @@ public class CalendarUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取给定日期当前周的开始时间,周一定为一周的开始时间
|
||||
* 修改给定日期当前周的开始时间,周一定为一周的开始时间
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
@ -224,7 +224,7 @@ public class CalendarUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取给定日期当前周的开始时间
|
||||
* 修改给定日期当前周的开始时间
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @param isMondayAsFirstDay 是否周一做为一周的第一天(false表示周日做为第一天)
|
||||
@ -238,7 +238,7 @@ public class CalendarUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某周的结束时间,周日定为一周的结束
|
||||
* 修改某周的结束时间,周日定为一周的结束
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
@ -248,7 +248,7 @@ public class CalendarUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某周的结束时间
|
||||
* 修改某周的结束时间
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @param isSundayAsLastDay 是否周日做为一周的最后一天(false表示周六做为最后一天)
|
||||
@ -261,7 +261,7 @@ public class CalendarUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某月的开始时间
|
||||
* 修改某月的开始时间
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
@ -271,7 +271,7 @@ public class CalendarUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某月的结束时间
|
||||
* 修改某月的结束时间
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
@ -281,7 +281,7 @@ public class CalendarUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某季度的开始时间
|
||||
* 修改某季度的开始时间
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
@ -313,7 +313,7 @@ public class CalendarUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某年的开始时间
|
||||
* 修改某年的开始时间
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
@ -323,7 +323,7 @@ public class CalendarUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某年的结束时间
|
||||
* 修改某年的结束时间
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
@ -348,6 +348,40 @@ public class CalendarUtil {
|
||||
cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA);
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较两个日期是否为同一周
|
||||
*
|
||||
* @param cal1 日期1
|
||||
* @param cal2 日期2
|
||||
* @param isMon 是否为周一。国内第一天为星期一,国外第一天为星期日
|
||||
* @return 是否为同一周
|
||||
* @since 5.7.21
|
||||
*/
|
||||
public static boolean isSameWeek(Calendar cal1, Calendar cal2, boolean isMon) {
|
||||
if (cal1 == null || cal2 == null) {
|
||||
throw new IllegalArgumentException("The date must not be null");
|
||||
}
|
||||
|
||||
// 防止比较前修改原始Calendar对象
|
||||
cal1 = (Calendar) cal1.clone();
|
||||
cal2 = (Calendar) cal2.clone();
|
||||
|
||||
// 把所传日期设置为其当前周的第一天
|
||||
// 比较设置后的两个日期是否是同一天:true 代表同一周
|
||||
if (isMon) {
|
||||
cal1.setFirstDayOfWeek(Calendar.MONDAY);
|
||||
cal1.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
|
||||
cal2.setFirstDayOfWeek(Calendar.MONDAY);
|
||||
cal2.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
|
||||
} else {
|
||||
cal1.setFirstDayOfWeek(Calendar.SUNDAY);
|
||||
cal1.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
|
||||
cal2.setFirstDayOfWeek(Calendar.SUNDAY);
|
||||
cal2.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
|
||||
}
|
||||
return isSameDay(cal1, cal2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较两个日期是否为同一月
|
||||
*
|
||||
|
@ -92,7 +92,7 @@ public class ChineseDate {
|
||||
offset -= daysOfMonth;
|
||||
}
|
||||
|
||||
this.isLeapMonth = month == (leapMonth + 1);
|
||||
this.isLeapMonth = leapMonth > 0 && (month == (leapMonth + 1));
|
||||
if (hasLeapMonth && false == this.isLeapMonth) {
|
||||
// 当前月份前有闰月,则月份显示要-1,除非当前月份就是润月
|
||||
month--;
|
||||
|
@ -24,13 +24,30 @@ import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* 包装java.util.Date
|
||||
* 包装{@link Date}<br>
|
||||
* 此类继承了{@link Date},并提供扩展方法,如时区等。<br>
|
||||
* 此类重写了父类的{@code toString()}方法,返回值为"yyyy-MM-dd HH:mm:ss"格式
|
||||
*
|
||||
* @author xiaoleilu
|
||||
*/
|
||||
public class DateTime extends Date {
|
||||
private static final long serialVersionUID = -5395712593979185936L;
|
||||
|
||||
private static boolean useJdkToStringStyle = false;
|
||||
|
||||
/**
|
||||
* 设置全局的,是否使用{@link Date}默认的toString()格式<br>
|
||||
* 如果为{@code true},则调用toString()时返回"EEE MMM dd HH:mm:ss zzz yyyy"格式,<br>
|
||||
* 如果为{@code false},则返回"yyyy-MM-dd HH:mm:ss",<br>
|
||||
* 默认为{@code false}
|
||||
*
|
||||
* @param customUseJdkToStringStyle 是否使用{@link Date}默认的toString()格式
|
||||
* @since 5.7.21
|
||||
*/
|
||||
public static void setUseJdkToStringStyle(boolean customUseJdkToStringStyle){
|
||||
useJdkToStringStyle = customUseJdkToStringStyle;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否可变对象
|
||||
*/
|
||||
@ -932,13 +949,18 @@ public class DateTime extends Date {
|
||||
// -------------------------------------------------------------------- toString start
|
||||
|
||||
/**
|
||||
* 转为"yyyy-MM-dd HH:mm:ss" 格式字符串<br>
|
||||
* 如果时区被设置,会转换为其时区对应的时间,否则转换为当前地点对应的时区
|
||||
* 转为字符串,如果时区被设置,会转换为其时区对应的时间,否则转换为当前地点对应的时区<br>
|
||||
* 可以调用{@link DateTime#setUseJdkToStringStyle(boolean)} 方法自定义默认的风格<br>
|
||||
* 如果{@link #useJdkToStringStyle}为{@code true},返回"EEE MMM dd HH:mm:ss zzz yyyy"格式,<br>
|
||||
* 如果为{@code false},则返回"yyyy-MM-dd HH:mm:ss"
|
||||
*
|
||||
* @return "yyyy-MM-dd HH:mm:ss" 格式字符串
|
||||
* @return 格式字符串
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
if(useJdkToStringStyle){
|
||||
return super.toString();
|
||||
}
|
||||
return toString(this.timeZone);
|
||||
}
|
||||
|
||||
|
@ -20,22 +20,18 @@ import java.time.LocalDateTime;
|
||||
import java.time.Year;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.TemporalAccessor;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 时间工具类
|
||||
*
|
||||
* @author xiaoleilu
|
||||
* @see LocalDateTimeUtil java8日志工具类
|
||||
* @see DatePattern 日期常用格式工具类
|
||||
*
|
||||
* @author xiaoleilu
|
||||
*/
|
||||
public class DateUtil extends CalendarUtil {
|
||||
|
||||
@ -1586,6 +1582,21 @@ public class DateUtil extends CalendarUtil {
|
||||
return CalendarUtil.isSameDay(calendar(date1), calendar(date2));
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较两个日期是否为同一周
|
||||
*
|
||||
* @param date1 日期1
|
||||
* @param date2 日期2
|
||||
* @param isMon 是否为周一。国内第一天为星期一,国外第一天为星期日
|
||||
* @return 是否为同一周
|
||||
*/
|
||||
public static boolean isSameWeek(final Date date1, final Date date2, boolean isMon) {
|
||||
if (date1 == null || date2 == null) {
|
||||
throw new IllegalArgumentException("The date must not be null");
|
||||
}
|
||||
return CalendarUtil.isSameWeek(calendar(date1), calendar(date2), isMon);
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较两个日期是否为同一月
|
||||
*
|
||||
@ -1870,6 +1881,74 @@ public class DateUtil extends CalendarUtil {
|
||||
return new DateRange(start, end, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 俩个时间区间取交集
|
||||
*
|
||||
* @param start 开始区间
|
||||
* @param end 结束区间
|
||||
* @return true 包含
|
||||
* @author handy
|
||||
* @since 5.7.21
|
||||
*/
|
||||
public static List<DateTime> rangeContains(DateRange start, DateRange end) {
|
||||
List<DateTime> startDateTimes = CollUtil.newArrayList((Iterable<DateTime>) start);
|
||||
List<DateTime> endDateTimes = CollUtil.newArrayList((Iterable<DateTime>) end);
|
||||
return startDateTimes.stream().filter(endDateTimes::contains).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 俩个时间区间取差集(end - start)
|
||||
*
|
||||
* @param start 开始区间
|
||||
* @param end 结束区间
|
||||
* @return true 包含
|
||||
* @author handy
|
||||
* @since 5.7.21
|
||||
*/
|
||||
public static List<DateTime> rangeNotContains(DateRange start, DateRange end) {
|
||||
List<DateTime> startDateTimes = CollUtil.newArrayList((Iterable<DateTime>) start);
|
||||
List<DateTime> endDateTimes = CollUtil.newArrayList((Iterable<DateTime>) end);
|
||||
return endDateTimes.stream().filter(item -> !startDateTimes.contains(item)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 按日期范围遍历,执行 function
|
||||
*
|
||||
* @param start 起始日期时间(包括)
|
||||
* @param end 结束日期时间
|
||||
* @param unit 步进单位
|
||||
* @param func 每次遍历要执行的 function
|
||||
* @param <T> Date经过函数处理结果类型
|
||||
* @return 结果列表
|
||||
* @since 5.7.21
|
||||
*/
|
||||
public static <T> List<T> rangeFunc(Date start, Date end, final DateField unit, Function<Date, T> func) {
|
||||
if (start == null || end == null || start.after(end)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
ArrayList<T> list = new ArrayList<>();
|
||||
for (DateTime date : range(start, end, unit)) {
|
||||
list.add(func.apply(date));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按日期范围遍历,执行 consumer
|
||||
*
|
||||
* @param start 起始日期时间(包括)
|
||||
* @param end 结束日期时间
|
||||
* @param unit 步进单位
|
||||
* @param consumer 每次遍历要执行的 consumer
|
||||
* @since 5.7.21
|
||||
*/
|
||||
public static void rangeConsume(Date start, Date end, final DateField unit, Consumer<Date> consumer) {
|
||||
if (start == null || end == null || start.after(end)) {
|
||||
return;
|
||||
}
|
||||
range(start, end, unit).forEach(consumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建日期范围生成器
|
||||
*
|
||||
@ -1878,7 +1957,7 @@ public class DateUtil extends CalendarUtil {
|
||||
* @param unit 步进单位
|
||||
* @return {@link DateRange}
|
||||
*/
|
||||
public static List<DateTime> rangeToList(Date start, Date end, final DateField unit) {
|
||||
public static List<DateTime> rangeToList(Date start, Date end, DateField unit) {
|
||||
return CollUtil.newArrayList((Iterable<DateTime>) range(start, end, unit));
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,7 @@ import java.time.temporal.ChronoUnit;
|
||||
import java.time.temporal.Temporal;
|
||||
import java.time.temporal.TemporalAccessor;
|
||||
import java.time.temporal.TemporalUnit;
|
||||
import java.time.temporal.WeekFields;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
@ -562,4 +563,21 @@ public class LocalDateTimeUtil {
|
||||
return startTime.isAfter(realEndTime) || endTime.isBefore(realStartTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得指定日期是所在年份的第几周,如:
|
||||
* <ul>
|
||||
* <li>如果一年的第一天是星期一,则第一周从第一天开始,没有零周</li>
|
||||
* <li>如果一年的第二天是星期一,则第一周从第二天开始,而第一天在零周</li>
|
||||
* <li>如果一年中的第4天是星期一,则第1周从第4周开始,第1至第3周在零周开始</li>
|
||||
* <li>如果一年中的第5天是星期一,则第二周从第5周开始,第1至第4周在第1周</li>
|
||||
* </ul>
|
||||
*
|
||||
*
|
||||
* @param date 日期({@link LocalDate} 或者 {@link LocalDateTime}等)
|
||||
* @return 所在年的第几周
|
||||
* @since 5.7.21
|
||||
*/
|
||||
public static int weekOfYear(TemporalAccessor date){
|
||||
return TemporalAccessorUtil.get(date, WeekFields.ISO.weekOfYear());
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package cn.hutool.core.date;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
|
||||
import java.util.Calendar;
|
||||
|
||||
/**
|
||||
@ -93,14 +95,27 @@ public enum Month {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取{@link Calendar}中的对应值
|
||||
* 获取{@link Calendar}中的对应值<br>
|
||||
* 此值从0开始,即0表示一月
|
||||
*
|
||||
* @return {@link Calendar}中的对应值
|
||||
* @return {@link Calendar}中的对应月份值,从0开始计数
|
||||
*/
|
||||
public int getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取月份值,此值与{@link java.time.Month}对应<br>
|
||||
* 此值从1开始,即1表示一月
|
||||
*
|
||||
* @return 月份值,对应{@link java.time.Month},从1开始计数
|
||||
* @since 5.7.21
|
||||
*/
|
||||
public int getValueBaseOne() {
|
||||
Assert.isFalse(this == UNDECIMBER, "Unsupported UNDECIMBER Field");
|
||||
return getValue() + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取此月份最后一天的值,不支持的月份(例如UNDECIMBER)返回-1
|
||||
*
|
||||
@ -165,17 +180,28 @@ public enum Month {
|
||||
|
||||
/**
|
||||
* 获得指定月的最后一天
|
||||
* @param month 月份,从0开始
|
||||
*
|
||||
* @param month 月份,从0开始
|
||||
* @param isLeapYear 是否为闰年,闰年只对二月有影响
|
||||
* @return 最后一天,可能为28,29,30,31
|
||||
* @since 5.4.7
|
||||
*/
|
||||
public static int getLastDay(int month, boolean isLeapYear){
|
||||
public static int getLastDay(int month, boolean isLeapYear) {
|
||||
int lastDay = DAYS_OF_MONTH[month];
|
||||
if (isLeapYear && Calendar.FEBRUARY == month){
|
||||
if (isLeapYear && Calendar.FEBRUARY == month) {
|
||||
// 二月
|
||||
lastDay += 1;
|
||||
}
|
||||
return lastDay;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为{@link java.time.Month}
|
||||
*
|
||||
* @return {@link java.time.Month}
|
||||
* @since 5.7.21
|
||||
*/
|
||||
public java.time.Month toJdkMonth() {
|
||||
return java.time.Month.of(getValueBaseOne());
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ public class CheckedUtil {
|
||||
* @param expression Lambda表达式
|
||||
* @param <P> 运行时传入的参数类型
|
||||
* @param <R> 最终返回的数据类型
|
||||
* @return cn.hutool.core.lang.func.Func
|
||||
* @return {@link FuncRt}
|
||||
*/
|
||||
public static <P, R> FuncRt<P, R> uncheck(Func<P, R> expression) {
|
||||
return uncheck(expression, new RuntimeException());
|
||||
@ -63,7 +63,7 @@ public class CheckedUtil {
|
||||
*
|
||||
* @param expression 运行时传入的参数类型
|
||||
* @param <R> 最终返回的数据类型
|
||||
* @return cn.hutool.core.lang.func.Func0
|
||||
* @return {@link Func0Rt}
|
||||
*/
|
||||
public static <R> Func0Rt<R> uncheck(Func0<R> expression) {
|
||||
return uncheck(expression, new RuntimeException());
|
||||
@ -76,7 +76,7 @@ public class CheckedUtil {
|
||||
* @param expression 运行时传入的参数类型
|
||||
* @param <P> 运行时传入的参数类型
|
||||
* @param <R> 最终返回的数据类型
|
||||
* @return cn.hutool.core.lang.func.Func1
|
||||
* @return {@link Func1Rt}
|
||||
*/
|
||||
public static <P, R> Func1Rt<P, R> uncheck(Func1<P, R> expression) {
|
||||
return uncheck(expression, new RuntimeException());
|
||||
@ -89,7 +89,7 @@ public class CheckedUtil {
|
||||
*
|
||||
* @param expression 运行时传入的参数类型
|
||||
* @param <P> 运行时传入的参数类型
|
||||
* @return cn.hutool.core.lang.func.VoidFunc
|
||||
* @return {@link VoidFuncRt}
|
||||
*/
|
||||
public static <P> VoidFuncRt<P> uncheck(VoidFunc<P> expression) {
|
||||
return uncheck(expression, new RuntimeException());
|
||||
@ -100,7 +100,7 @@ public class CheckedUtil {
|
||||
* 如此一来,代码中就不用显示的try-catch转化成运行时异常
|
||||
*
|
||||
* @param expression 运行时传入的参数类型
|
||||
* @return cn.hutool.core.lang.func.VoidFunc0
|
||||
* @return {@link VoidFunc0Rt}
|
||||
*/
|
||||
public static VoidFunc0Rt uncheck(VoidFunc0 expression) {
|
||||
return uncheck(expression, new RuntimeException());
|
||||
@ -112,7 +112,7 @@ public class CheckedUtil {
|
||||
*
|
||||
* @param expression 运行时传入的参数类型
|
||||
* @param <P> 运行时传入的参数类型
|
||||
* @return cn.hutool.core.lang.func.VoidFunc1
|
||||
* @return {@link VoidFunc1Rt}
|
||||
*/
|
||||
public static <P> VoidFunc1Rt<P> uncheck(VoidFunc1<P> expression) {
|
||||
return uncheck(expression, new RuntimeException());
|
||||
@ -127,7 +127,7 @@ public class CheckedUtil {
|
||||
* @param rte 期望抛出的运行时异常
|
||||
* @param <P> 运行时传入的参数类型
|
||||
* @param <R> 最终返回的数据类型
|
||||
* @return cn.hutool.core.lang.func.Func
|
||||
* @return {@link FuncRt}
|
||||
*/
|
||||
public static <P, R> FuncRt<P, R> uncheck(Func<P, R> expression, RuntimeException rte) {
|
||||
Objects.requireNonNull(expression, "expression can not be null");
|
||||
@ -152,7 +152,7 @@ public class CheckedUtil {
|
||||
* @param expression Lambda表达式
|
||||
* @param rte 期望抛出的运行时异常
|
||||
* @param <R> 最终返回的数据类型
|
||||
* @return cn.hutool.core.lang.func.Func0
|
||||
* @return {@link Func0Rt}
|
||||
*/
|
||||
public static <R> Func0Rt<R> uncheck(Func0<R> expression, RuntimeException rte) {
|
||||
Objects.requireNonNull(expression, "expression can not be null");
|
||||
@ -178,7 +178,7 @@ public class CheckedUtil {
|
||||
* @param rte 期望抛出的运行时异常
|
||||
* @param <P> 运行时传入的参数类型
|
||||
* @param <R> 最终返回的数据类型
|
||||
* @return cn.hutool.core.lang.func.Func1
|
||||
* @return {@link Func1Rt}
|
||||
*/
|
||||
public static <P, R> Func1Rt<P, R> uncheck(Func1<P, R> expression, RuntimeException rte) {
|
||||
Objects.requireNonNull(expression, "expression can not be null");
|
||||
@ -203,7 +203,7 @@ public class CheckedUtil {
|
||||
* @param expression Lambda表达式
|
||||
* @param rte 期望抛出的运行时异常
|
||||
* @param <P> 运行时传入的参数类型
|
||||
* @return cn.hutool.core.lang.func.VoidFunc
|
||||
* @return {@link VoidFuncRt}
|
||||
*/
|
||||
public static <P> VoidFuncRt<P> uncheck(VoidFunc<P> expression, RuntimeException rte) {
|
||||
Objects.requireNonNull(expression, "expression can not be null");
|
||||
@ -228,7 +228,7 @@ public class CheckedUtil {
|
||||
*
|
||||
* @param expression Lambda表达式
|
||||
* @param rte 期望抛出的运行时异常
|
||||
* @return cn.hutool.core.lang.func.VoidFunc0
|
||||
* @return {@link VoidFunc0Rt}
|
||||
*/
|
||||
public static VoidFunc0Rt uncheck(VoidFunc0 expression, RuntimeException rte) {
|
||||
Objects.requireNonNull(expression, "expression can not be null");
|
||||
@ -254,7 +254,7 @@ public class CheckedUtil {
|
||||
* @param expression Lambda表达式
|
||||
* @param rte 期望抛出的运行时异常
|
||||
* @param <P> 运行时传入的参数类型
|
||||
* @return cn.hutool.core.lang.func.VoidFunc1
|
||||
* @return {@link VoidFunc1Rt}
|
||||
*/
|
||||
public static <P> VoidFunc1Rt<P> uncheck(VoidFunc1<P> expression, RuntimeException rte) {
|
||||
Objects.requireNonNull(expression, "expression can not be null");
|
||||
|
@ -13,6 +13,7 @@ import cn.hutool.core.io.file.Tailer;
|
||||
import cn.hutool.core.io.resource.ResourceUtil;
|
||||
import cn.hutool.core.io.unit.DataSizeUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.CharUtil;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
@ -526,24 +527,39 @@ public class FileUtil extends PathUtil {
|
||||
/**
|
||||
* 计算目录或文件的总大小<br>
|
||||
* 当给定对象为文件时,直接调用 {@link File#length()}<br>
|
||||
* 当给定对象为目录时,遍历目录下的所有文件和目录,递归计算其大小,求和返回
|
||||
* 当给定对象为目录时,遍历目录下的所有文件和目录,递归计算其大小,求和返回<br>
|
||||
* 此方法不包括目录本身的占用空间大小。
|
||||
*
|
||||
* @param file 目录或文件,null或者文件不存在返回0
|
||||
* @return 总大小,bytes长度
|
||||
*/
|
||||
public static long size(File file) {
|
||||
return size(file, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算目录或文件的总大小<br>
|
||||
* 当给定对象为文件时,直接调用 {@link File#length()}<br>
|
||||
* 当给定对象为目录时,遍历目录下的所有文件和目录,递归计算其大小,求和返回
|
||||
*
|
||||
* @param file 目录或文件,null或者文件不存在返回0
|
||||
* @param includeDirSize 是否包括每层目录本身的大小
|
||||
* @return 总大小,bytes长度
|
||||
* @since 5.7.21
|
||||
*/
|
||||
public static long size(File file, boolean includeDirSize) {
|
||||
if (null == file || false == file.exists() || isSymlink(file)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (file.isDirectory()) {
|
||||
long size = 0L;
|
||||
long size = includeDirSize ? file.length() : 0;
|
||||
File[] subFiles = file.listFiles();
|
||||
if (ArrayUtil.isEmpty(subFiles)) {
|
||||
return 0L;// empty directory
|
||||
}
|
||||
for (File subFile : subFiles) {
|
||||
size += size(subFile);
|
||||
size += size(subFile, includeDirSize);
|
||||
}
|
||||
return size;
|
||||
} else {
|
||||
@ -811,7 +827,7 @@ public class FileUtil extends PathUtil {
|
||||
|
||||
/**
|
||||
* 创建文件夹,会递归自动创建其不存在的父文件夹,如果存在直接返回此文件夹<br>
|
||||
* 此方法不对File对象类型做判断,如果File不存在,无法判断其类型
|
||||
* 此方法不对File对象类型做判断,如果File不存在,无法判断其类型<br>
|
||||
*
|
||||
* @param dir 目录
|
||||
* @return 创建的目录
|
||||
@ -821,12 +837,48 @@ public class FileUtil extends PathUtil {
|
||||
return null;
|
||||
}
|
||||
if (false == dir.exists()) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
dir.mkdirs();
|
||||
mkdirsSafely(dir, 5, 1);
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全地级联创建目录 (确保并发环境下能创建成功)
|
||||
*
|
||||
* <pre>
|
||||
* 并发环境下,假设 test 目录不存在,如果线程A mkdirs "test/A" 目录,线程B mkdirs "test/B"目录,
|
||||
* 其中一个线程可能会失败,进而导致以下代码抛出 FileNotFoundException 异常
|
||||
*
|
||||
* file.getParentFile().mkdirs(); // 父目录正在被另一个线程创建中,返回 false
|
||||
* file.createNewFile(); // 抛出 IO 异常,因为该线程无法感知到父目录已被创建
|
||||
* </pre>
|
||||
*
|
||||
* @param dir 待创建的目录
|
||||
* @param tryCount 最大尝试次数
|
||||
* @param sleepMillis 线程等待的毫秒数
|
||||
* @return true表示创建成功,false表示创建失败
|
||||
* @since 5.7.21
|
||||
* @author z8g
|
||||
*/
|
||||
public static boolean mkdirsSafely(File dir, int tryCount, long sleepMillis) {
|
||||
if (dir == null) {
|
||||
return false;
|
||||
}
|
||||
if (dir.isDirectory()) {
|
||||
return true;
|
||||
}
|
||||
for (int i = 1; i <= tryCount; i++) { // 高并发场景下,可以看到 i 处于 1 ~ 3 之间
|
||||
// 如果文件已存在,也会返回 false,所以该值不能作为是否能创建的依据,因此不对其进行处理
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
dir.mkdirs();
|
||||
if (dir.exists()) {
|
||||
return true;
|
||||
}
|
||||
ThreadUtil.sleep(sleepMillis);
|
||||
}
|
||||
return dir.exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建临时文件<br>
|
||||
* 创建后的文件名为 prefix[Randon].tmp
|
||||
|
@ -87,12 +87,49 @@ public class NioUtil {
|
||||
Assert.notNull(outChannel, "Out channel is null!");
|
||||
|
||||
try {
|
||||
return inChannel.transferTo(0, inChannel.size(), outChannel);
|
||||
return copySafely(inChannel, outChannel);
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件拷贝实现
|
||||
*
|
||||
* <pre>
|
||||
* FileChannel#transferTo 或 FileChannel#transferFrom 的实现是平台相关的,需要确保低版本平台的兼容性
|
||||
* 例如 android 7以下平台在使用 ZipInputStream 解压文件的过程中,
|
||||
* 通过 FileChannel#transferFrom 传输到文件时,其返回值可能小于 totalBytes,不处理将导致文件内容缺失
|
||||
*
|
||||
* // 错误写法,dstChannel.transferFrom 返回值小于 zipEntry.getSize(),导致解压后文件内容缺失
|
||||
* try (InputStream srcStream = zipFile.getInputStream(zipEntry);
|
||||
* ReadableByteChannel srcChannel = Channels.newChannel(srcStream);
|
||||
* FileOutputStream fos = new FileOutputStream(saveFile);
|
||||
* FileChannel dstChannel = fos.getChannel()) {
|
||||
* dstChannel.transferFrom(srcChannel, 0, zipEntry.getSize());
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @param inChannel 输入通道
|
||||
* @param outChannel 输出通道
|
||||
* @return 输入通道的字节数
|
||||
* @throws IOException 发生IO错误
|
||||
* @link http://androidxref.com/6.0.1_r10/xref/libcore/luni/src/main/java/java/nio/FileChannelImpl.java
|
||||
* @link http://androidxref.com/7.0.0_r1/xref/libcore/ojluni/src/main/java/sun/nio/ch/FileChannelImpl.java
|
||||
* @link http://androidxref.com/7.0.0_r1/xref/libcore/ojluni/src/main/native/FileChannelImpl.c
|
||||
* @author z8g
|
||||
* @since 5.7.21
|
||||
*/
|
||||
private static long copySafely(FileChannel inChannel, FileChannel outChannel) throws IOException {
|
||||
final long totalBytes = inChannel.size();
|
||||
for (long pos = 0, remaining = totalBytes; remaining > 0; ) { // 确保文件内容不会缺失
|
||||
final long writeBytes = inChannel.transferTo(pos, remaining, outChannel); // 实际传输的字节数
|
||||
pos += writeBytes;
|
||||
remaining -= writeBytes;
|
||||
}
|
||||
return totalBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 拷贝流,使用NIO,不会关闭channel
|
||||
*
|
||||
|
@ -20,6 +20,7 @@ public class FileResource implements Resource, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final File file;
|
||||
private final long lastModified;
|
||||
private final String name;
|
||||
|
||||
// ----------------------------------------------------------------------- Constructor start
|
||||
@ -60,6 +61,7 @@ public class FileResource implements Resource, Serializable {
|
||||
public FileResource(File file, String fileName) {
|
||||
Assert.notNull(file, "File must be not null !");
|
||||
this.file = file;
|
||||
this.lastModified = file.lastModified();
|
||||
this.name = ObjectUtil.defaultIfNull(fileName, file::getName);
|
||||
}
|
||||
|
||||
@ -89,6 +91,11 @@ public class FileResource implements Resource, Serializable {
|
||||
return this.file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isModified() {
|
||||
return this.lastModified != file.lastModified();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回路径
|
||||
* @return 返回URL路径
|
||||
|
@ -63,6 +63,11 @@ public class MultiResource implements Resource, Iterable<Resource>, Iterator<Res
|
||||
return resources.get(cursor).getStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isModified() {
|
||||
return resources.get(cursor).isModified();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedReader getReader(Charset charset) {
|
||||
return resources.get(cursor).getReader(charset);
|
||||
|
@ -53,6 +53,17 @@ public interface Resource {
|
||||
*/
|
||||
InputStream getStream();
|
||||
|
||||
/**
|
||||
* 检查资源是否变更<br>
|
||||
* 一般用于文件类资源,检查文件是否被修改过。
|
||||
*
|
||||
* @return 是否变更
|
||||
* @since 5.7.21
|
||||
*/
|
||||
default boolean isModified(){
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将资源内容写出到流,不关闭输出流,但是关闭资源流
|
||||
*
|
||||
|
@ -7,6 +7,7 @@ import cn.hutool.core.util.URLUtil;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
@ -18,9 +19,19 @@ public class UrlResource implements Resource, Serializable{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
protected URL url;
|
||||
private long lastModified = 0;
|
||||
protected String name;
|
||||
|
||||
//-------------------------------------------------------------------------------------- Constructor start
|
||||
/**
|
||||
* 构造
|
||||
* @param uri URI
|
||||
* @since 5.7.21
|
||||
*/
|
||||
public UrlResource(URI uri) {
|
||||
this(URLUtil.url(uri), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
* @param url URL
|
||||
@ -36,6 +47,9 @@ public class UrlResource implements Resource, Serializable{
|
||||
*/
|
||||
public UrlResource(URL url, String name) {
|
||||
this.url = url;
|
||||
if(null != url && URLUtil.URL_PROTOCOL_FILE.equals(url.getProtocol())){
|
||||
this.lastModified = FileUtil.file(url).lastModified();
|
||||
}
|
||||
this.name = ObjectUtil.defaultIfNull(name, () -> (null != url ? FileUtil.getName(url.getPath()) : null));
|
||||
}
|
||||
|
||||
@ -68,6 +82,12 @@ public class UrlResource implements Resource, Serializable{
|
||||
return URLUtil.getStream(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isModified() {
|
||||
// lastModified == 0表示此资源非文件资源
|
||||
return (0 != this.lastModified) && this.lastModified != getFile().lastModified();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得File
|
||||
* @return {@link File}
|
||||
|
107
hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java
Executable file
107
hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java
Executable file
@ -0,0 +1,107 @@
|
||||
package cn.hutool.core.io.resource;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ClassLoaderUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* VFS资源封装<br>
|
||||
* 支持VFS 3.x on JBoss AS 6+,JBoss AS 7 and WildFly 8+<br>
|
||||
* 参考:org.springframework.core.io.VfsUtils
|
||||
*
|
||||
* @author looly, Spring
|
||||
* @since 5.7.21
|
||||
*/
|
||||
public class VfsResource implements Resource {
|
||||
private static final String VFS3_PKG = "org.jboss.vfs.";
|
||||
|
||||
private static final Method VIRTUAL_FILE_METHOD_EXISTS;
|
||||
private static final Method VIRTUAL_FILE_METHOD_GET_INPUT_STREAM;
|
||||
private static final Method VIRTUAL_FILE_METHOD_GET_SIZE;
|
||||
private static final Method VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED;
|
||||
private static final Method VIRTUAL_FILE_METHOD_TO_URL;
|
||||
private static final Method VIRTUAL_FILE_METHOD_GET_NAME;
|
||||
|
||||
static {
|
||||
Class<?> virtualFile = ClassLoaderUtil.loadClass(VFS3_PKG + "VirtualFile");
|
||||
try {
|
||||
VIRTUAL_FILE_METHOD_EXISTS = virtualFile.getMethod("exists");
|
||||
VIRTUAL_FILE_METHOD_GET_INPUT_STREAM = virtualFile.getMethod("openStream");
|
||||
VIRTUAL_FILE_METHOD_GET_SIZE = virtualFile.getMethod("getSize");
|
||||
VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED = virtualFile.getMethod("getLastModified");
|
||||
VIRTUAL_FILE_METHOD_TO_URL = virtualFile.getMethod("toURL");
|
||||
VIRTUAL_FILE_METHOD_GET_NAME = virtualFile.getMethod("getName");
|
||||
} catch (NoSuchMethodException ex) {
|
||||
throw new IllegalStateException("Could not detect JBoss VFS infrastructure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* org.jboss.vfs.VirtualFile实例对象
|
||||
*/
|
||||
private final Object virtualFile;
|
||||
private final long lastModified;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param resource org.jboss.vfs.VirtualFile实例对象
|
||||
*/
|
||||
public VfsResource(Object resource) {
|
||||
Assert.notNull(resource, "VirtualFile must not be null");
|
||||
this.virtualFile = resource;
|
||||
this.lastModified = getLastModified();
|
||||
}
|
||||
|
||||
/**
|
||||
* VFS文件是否存在
|
||||
*
|
||||
* @return 文件是否存在
|
||||
*/
|
||||
public boolean exists() {
|
||||
return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_EXISTS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getUrl() {
|
||||
return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_TO_URL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getStream() {
|
||||
return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_INPUT_STREAM);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isModified() {
|
||||
return this.lastModified != getLastModified();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得VFS文件最后修改时间
|
||||
*
|
||||
* @return 最后修改时间
|
||||
*/
|
||||
public long getLastModified() {
|
||||
return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取VFS文件大小
|
||||
*
|
||||
* @return VFS文件大小
|
||||
*/
|
||||
public long size() {
|
||||
return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_SIZE);
|
||||
}
|
||||
|
||||
}
|
32
hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier1.java
Executable file
32
hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier1.java
Executable file
@ -0,0 +1,32 @@
|
||||
package cn.hutool.core.lang.func;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* 1参数Supplier
|
||||
*
|
||||
* @param <T> 目标 类型
|
||||
* @param <P1> 参数一 类型
|
||||
* @author TomXin
|
||||
* @since 5.7.21
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface Supplier1<T, P1> {
|
||||
/**
|
||||
* 生成实例的方法
|
||||
*
|
||||
* @param p1 参数一
|
||||
* @return 目标对象
|
||||
*/
|
||||
T get(P1 p1);
|
||||
|
||||
/**
|
||||
* 将带有参数的Supplier转换为无参{@link Supplier}
|
||||
*
|
||||
* @param p1 参数1
|
||||
* @return {@link Supplier}
|
||||
*/
|
||||
default Supplier<T> toSupplier(P1 p1) {
|
||||
return () -> get(p1);
|
||||
}
|
||||
}
|
36
hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier2.java
Executable file
36
hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier2.java
Executable file
@ -0,0 +1,36 @@
|
||||
package cn.hutool.core.lang.func;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* 两个参数的Supplier
|
||||
*
|
||||
* @param <T> 目标 类型
|
||||
* @param <P1> 参数一 类型
|
||||
* @param <P2> 参数二 类型
|
||||
* @author TomXin
|
||||
* @since 5.7.21
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface Supplier2<T, P1, P2> {
|
||||
|
||||
/**
|
||||
* 生成实例的方法
|
||||
*
|
||||
* @param p1 参数一
|
||||
* @param p2 参数二
|
||||
* @return 目标对象
|
||||
*/
|
||||
T get(P1 p1, P2 p2);
|
||||
|
||||
/**
|
||||
* 将带有参数的Supplier转换为无参{@link Supplier}
|
||||
*
|
||||
* @param p1 参数1
|
||||
* @param p2 参数2
|
||||
* @return {@link Supplier}
|
||||
*/
|
||||
default Supplier<T> toSupplier(P1 p1, P2 p2) {
|
||||
return () -> get(p1, p2);
|
||||
}
|
||||
}
|
39
hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier3.java
Executable file
39
hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier3.java
Executable file
@ -0,0 +1,39 @@
|
||||
package cn.hutool.core.lang.func;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* 3参数Supplier
|
||||
*
|
||||
* @param <T> 目标类型
|
||||
* @param <P1> 参数一类型
|
||||
* @param <P2> 参数二类型
|
||||
* @param <P3> 参数三类型
|
||||
* @author TomXin
|
||||
* @since 5.7.21
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface Supplier3<T, P1, P2, P3> {
|
||||
|
||||
/**
|
||||
* 生成实例的方法
|
||||
*
|
||||
* @param p1 参数一
|
||||
* @param p2 参数二
|
||||
* @param p3 参数三
|
||||
* @return 目标对象
|
||||
*/
|
||||
T get(P1 p1, P2 p2, P3 p3);
|
||||
|
||||
/**
|
||||
* 将带有参数的Supplier转换为无参{@link Supplier}
|
||||
*
|
||||
* @param p1 参数1
|
||||
* @param p2 参数2
|
||||
* @param p3 参数3
|
||||
* @return {@link Supplier}
|
||||
*/
|
||||
default Supplier<T> toSupplier(P1 p1, P2 p2, P3 p3) {
|
||||
return () -> get(p1, p2, p3);
|
||||
}
|
||||
}
|
42
hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier4.java
Executable file
42
hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier4.java
Executable file
@ -0,0 +1,42 @@
|
||||
package cn.hutool.core.lang.func;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* 4参数Supplier
|
||||
*
|
||||
* @param <T> 目标 类型
|
||||
* @param <P1> 参数一 类型
|
||||
* @param <P2> 参数二 类型
|
||||
* @param <P3> 参数三 类型
|
||||
* @param <P4> 参数四 类型
|
||||
* @author TomXin
|
||||
* @since 5.7.21
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface Supplier4<T, P1, P2, P3, P4> {
|
||||
|
||||
/**
|
||||
* 生成实例的方法
|
||||
*
|
||||
* @param p1 参数一
|
||||
* @param p2 参数二
|
||||
* @param p3 参数三
|
||||
* @param p4 参数四
|
||||
* @return 目标对象
|
||||
*/
|
||||
T get(P1 p1, P2 p2, P3 p3, P4 p4);
|
||||
|
||||
/**
|
||||
* 将带有参数的Supplier转换为无参{@link Supplier}
|
||||
*
|
||||
* @param p1 参数1
|
||||
* @param p2 参数2
|
||||
* @param p3 参数3
|
||||
* @param p4 参数4
|
||||
* @return {@link Supplier}
|
||||
*/
|
||||
default Supplier<T> toSupplier(P1 p1, P2 p2, P3 p3, P4 p4) {
|
||||
return () -> get(p1, p2, p3, p4);
|
||||
}
|
||||
}
|
45
hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier5.java
Executable file
45
hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier5.java
Executable file
@ -0,0 +1,45 @@
|
||||
package cn.hutool.core.lang.func;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* 5参数Supplier
|
||||
*
|
||||
* @param <T> 目标 类型
|
||||
* @param <P1> 参数一 类型
|
||||
* @param <P2> 参数二 类型
|
||||
* @param <P3> 参数三 类型
|
||||
* @param <P4> 参数四 类型
|
||||
* @param <P5> 参数五 类型
|
||||
* @author TomXin
|
||||
* @since 5.7.21
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface Supplier5<T, P1, P2, P3, P4, P5> {
|
||||
|
||||
/**
|
||||
* 生成实例的方法
|
||||
*
|
||||
* @param p1 参数一
|
||||
* @param p2 参数二
|
||||
* @param p3 参数三
|
||||
* @param p4 参数四
|
||||
* @param p5 参数五
|
||||
* @return 目标对象
|
||||
*/
|
||||
T get(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5);
|
||||
|
||||
/**
|
||||
* 将带有参数的Supplier转换为无参{@link Supplier}
|
||||
*
|
||||
* @param p1 参数1
|
||||
* @param p2 参数2
|
||||
* @param p3 参数3
|
||||
* @param p4 参数4
|
||||
* @param p5 参数5
|
||||
* @return {@link Supplier}
|
||||
*/
|
||||
default Supplier<T> toSupplier(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5) {
|
||||
return () -> get(p1, p2, p3, p4, p5);
|
||||
}
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
package cn.hutool.core.lang.hash;
|
||||
|
||||
import cn.hutool.core.util.ByteUtil;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
@ -140,11 +142,11 @@ public class CityHash {
|
||||
len = (len - 1) & ~63;
|
||||
int pos = 0;
|
||||
do {
|
||||
x = rotate(x + y + v.getLowValue() + fetch64(data, pos + 8), 37) * k1;
|
||||
y = rotate(y + v.getHighValue() + fetch64(data, pos + 48), 42) * k1;
|
||||
x = rotate64(x + y + v.getLowValue() + fetch64(data, pos + 8), 37) * k1;
|
||||
y = rotate64(y + v.getHighValue() + fetch64(data, pos + 48), 42) * k1;
|
||||
x ^= w.getHighValue();
|
||||
y += v.getLowValue() + fetch64(data, pos + 40);
|
||||
z = rotate(z + w.getLowValue(), 33) * k1;
|
||||
z = rotate64(z + w.getLowValue(), 33) * k1;
|
||||
v = weakHashLen32WithSeeds(data, pos, v.getHighValue() * k1, x + w.getLowValue());
|
||||
w = weakHashLen32WithSeeds(data, pos + 32, z + w.getHighValue(), y + fetch64(data, pos + 16));
|
||||
// swap z,x value
|
||||
@ -221,19 +223,19 @@ public class CityHash {
|
||||
long x = seed.getLowValue();
|
||||
long y = seed.getHighValue();
|
||||
long z = len * k1;
|
||||
v.setLowValue(rotate(y ^ k1, 49) * k1 + fetch64(byteArray, start));
|
||||
v.setHighValue(rotate(v.getLowValue(), 42) * k1 + fetch64(byteArray, start + 8));
|
||||
w.setLowValue(rotate(y + z, 35) * k1 + x);
|
||||
w.setHighValue(rotate(x + fetch64(byteArray, start + 88), 53) * k1);
|
||||
v.setLowValue(rotate64(y ^ k1, 49) * k1 + fetch64(byteArray, start));
|
||||
v.setHighValue(rotate64(v.getLowValue(), 42) * k1 + fetch64(byteArray, start + 8));
|
||||
w.setLowValue(rotate64(y + z, 35) * k1 + x);
|
||||
w.setHighValue(rotate64(x + fetch64(byteArray, start + 88), 53) * k1);
|
||||
|
||||
// This is the same inner loop as CityHash64(), manually unrolled.
|
||||
int pos = start;
|
||||
do {
|
||||
x = rotate(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1;
|
||||
y = rotate(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1;
|
||||
x = rotate64(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1;
|
||||
y = rotate64(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1;
|
||||
x ^= w.getHighValue();
|
||||
y += v.getLowValue() + fetch64(byteArray, pos + 40);
|
||||
z = rotate(z + w.getLowValue(), 33) * k1;
|
||||
z = rotate64(z + w.getLowValue(), 33) * k1;
|
||||
v = weakHashLen32WithSeeds(byteArray, pos, v.getHighValue() * k1, x + w.getLowValue());
|
||||
w = weakHashLen32WithSeeds(byteArray, pos + 32, z + w.getHighValue(), y + fetch64(byteArray, pos + 16));
|
||||
|
||||
@ -241,11 +243,11 @@ public class CityHash {
|
||||
x = z;
|
||||
z = swapValue;
|
||||
pos += 64;
|
||||
x = rotate(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1;
|
||||
y = rotate(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1;
|
||||
x = rotate64(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1;
|
||||
y = rotate64(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1;
|
||||
x ^= w.getHighValue();
|
||||
y += v.getLowValue() + fetch64(byteArray, pos + 40);
|
||||
z = rotate(z + w.getLowValue(), 33) * k1;
|
||||
z = rotate64(z + w.getLowValue(), 33) * k1;
|
||||
v = weakHashLen32WithSeeds(byteArray, pos, v.getHighValue() * k1, x + w.getLowValue());
|
||||
w = weakHashLen32WithSeeds(byteArray, pos + 32, z + w.getHighValue(), y + fetch64(byteArray, pos + 16));
|
||||
swapValue = x;
|
||||
@ -254,16 +256,16 @@ public class CityHash {
|
||||
pos += 64;
|
||||
len -= 128;
|
||||
} while (len >= 128);
|
||||
x += rotate(v.getLowValue() + z, 49) * k0;
|
||||
y = y * k0 + rotate(w.getHighValue(), 37);
|
||||
z = z * k0 + rotate(w.getLowValue(), 27);
|
||||
x += rotate64(v.getLowValue() + z, 49) * k0;
|
||||
y = y * k0 + rotate64(w.getHighValue(), 37);
|
||||
z = z * k0 + rotate64(w.getLowValue(), 27);
|
||||
w.setLowValue(w.getLowValue() * 9);
|
||||
v.setLowValue(v.getLowValue() * k0);
|
||||
|
||||
// If 0 < len < 128, hash up to 4 chunks of 32 bytes each from the end of s.
|
||||
for (int tail_done = 0; tail_done < len; ) {
|
||||
tail_done += 32;
|
||||
y = rotate(x + y, 42) * k0 + v.getHighValue();
|
||||
y = rotate64(x + y, 42) * k0 + v.getHighValue();
|
||||
w.setLowValue(w.getLowValue() + fetch64(byteArray, pos + len - tail_done + 16));
|
||||
x = x * k0 + w.getLowValue();
|
||||
z += w.getHighValue() + fetch64(byteArray, pos + len - tail_done);
|
||||
@ -321,8 +323,8 @@ public class CityHash {
|
||||
long mul = k2 + len * 2L;
|
||||
long a = fetch64(byteArray, 0) + k2;
|
||||
long b = fetch64(byteArray, len - 8);
|
||||
long c = rotate(b, 37) * mul + a;
|
||||
long d = (rotate(a, 25) + b) * mul;
|
||||
long c = rotate64(b, 37) * mul + a;
|
||||
long d = (rotate64(a, 25) + b) * mul;
|
||||
return hashLen16(c, d, mul);
|
||||
}
|
||||
if (len >= 4) {
|
||||
@ -349,8 +351,8 @@ public class CityHash {
|
||||
long b = fetch64(byteArray, 8);
|
||||
long c = fetch64(byteArray, len - 8) * mul;
|
||||
long d = fetch64(byteArray, len - 16) * k2;
|
||||
return hashLen16(rotate(a + b, 43) + rotate(c, 30) + d,
|
||||
a + rotate(b + k2, 18) + c, mul);
|
||||
return hashLen16(rotate64(a + b, 43) + rotate64(c, 30) + d,
|
||||
a + rotate64(b + k2, 18) + c, mul);
|
||||
}
|
||||
|
||||
private static long hashLen33to64(byte[] byteArray) {
|
||||
@ -364,10 +366,10 @@ public class CityHash {
|
||||
long f = fetch64(byteArray, 24) * 9;
|
||||
long g = fetch64(byteArray, len - 8);
|
||||
long h = fetch64(byteArray, len - 16) * mul;
|
||||
long u = rotate(a + g, 43) + (rotate(b, 30) + c) * 9;
|
||||
long u = rotate64(a + g, 43) + (rotate64(b, 30) + c) * 9;
|
||||
long v = ((a + g) ^ d) + f + 1;
|
||||
long w = Long.reverseBytes((u + v) * mul) + h;
|
||||
long x = rotate(e + f, 42) + c;
|
||||
long x = rotate64(e + f, 42) + c;
|
||||
long y = (Long.reverseBytes((v + w) * mul) + g) * mul;
|
||||
long z = e + f + c;
|
||||
a = Long.reverseBytes((x + z) * mul + y) + b;
|
||||
@ -375,37 +377,15 @@ public class CityHash {
|
||||
return b + x;
|
||||
}
|
||||
|
||||
private static long loadUnaligned64(final byte[] byteArray, final int start) {
|
||||
long result = 0;
|
||||
OrderIter orderIter = new OrderIter(8);
|
||||
while (orderIter.hasNext()) {
|
||||
int next = orderIter.next();
|
||||
long value = (byteArray[next + start] & 0xffL) << (next * 8);
|
||||
result |= value;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int loadUnaligned32(final byte[] byteArray, final int start) {
|
||||
int result = 0;
|
||||
OrderIter orderIter = new OrderIter(4);
|
||||
while (orderIter.hasNext()) {
|
||||
int next = orderIter.next();
|
||||
int value = (byteArray[next + start] & 0xff) << (next * 8);
|
||||
result |= value;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static long fetch64(byte[] byteArray, final int start) {
|
||||
return loadUnaligned64(byteArray, start);
|
||||
private static long fetch64(byte[] byteArray, int start) {
|
||||
return ByteUtil.bytesToLong(byteArray, start, ByteUtil.CPU_ENDIAN);
|
||||
}
|
||||
|
||||
private static int fetch32(byte[] byteArray, final int start) {
|
||||
return loadUnaligned32(byteArray, start);
|
||||
return ByteUtil.bytesToInt(byteArray, start, ByteUtil.CPU_ENDIAN);
|
||||
}
|
||||
|
||||
private static long rotate(long val, int shift) {
|
||||
private static long rotate64(long val, int shift) {
|
||||
// Avoid shifting by 64: doing so yields an undefined result.
|
||||
return shift == 0 ? val : ((val >>> shift) | (val << (64 - shift)));
|
||||
}
|
||||
@ -465,11 +445,11 @@ public class CityHash {
|
||||
private static Number128 weakHashLen32WithSeeds(
|
||||
long w, long x, long y, long z, long a, long b) {
|
||||
a += w;
|
||||
b = rotate(b + a + z, 21);
|
||||
b = rotate64(b + a + z, 21);
|
||||
long c = a;
|
||||
a += x;
|
||||
a += y;
|
||||
b += rotate(a, 44);
|
||||
b += rotate64(a, 44);
|
||||
return new Number128(a + z, b + c);
|
||||
}
|
||||
|
||||
@ -515,24 +495,5 @@ public class CityHash {
|
||||
b = hashLen16(d, b);
|
||||
return new Number128(a ^ b, hashLen16(b, a));
|
||||
}
|
||||
|
||||
private static class OrderIter {
|
||||
private static final boolean IS_LITTLE_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian"));
|
||||
|
||||
private final int size;
|
||||
private int index;
|
||||
|
||||
OrderIter(int size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
boolean hasNext() {
|
||||
return index < size;
|
||||
}
|
||||
|
||||
int next() {
|
||||
return IS_LITTLE_ENDIAN ? index++ : (size - 1 - index++);
|
||||
}
|
||||
}
|
||||
//------------------------------------------------------------------------------------------------------- Private method end
|
||||
}
|
||||
|
@ -0,0 +1,217 @@
|
||||
package cn.hutool.core.lang.hash;
|
||||
|
||||
import cn.hutool.core.util.ByteUtil;
|
||||
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Apache 发布的MetroHash算法,是一组用于非加密用例的最先进的哈希函数。
|
||||
* 除了卓越的性能外,他们还以算法生成而著称。
|
||||
*
|
||||
* <p>
|
||||
* 官方实现:https://github.com/jandrewrogers/MetroHash
|
||||
* 官方文档:http://www.jandrewrogers.com/2015/05/27/metrohash/
|
||||
* Go语言实现:https://github.com/linvon/cuckoo-filter/blob/main/vendor/github.com/dgryski/go-metro/
|
||||
* @author li
|
||||
*/
|
||||
public class MetroHash {
|
||||
|
||||
/**
|
||||
* hash64 种子加盐
|
||||
*/
|
||||
private final static long k0_64 = 0xD6D018F5;
|
||||
private final static long k1_64 = 0xA2AA033B;
|
||||
private final static long k2_64 = 0x62992FC1;
|
||||
private final static long k3_64 = 0x30BC5B29;
|
||||
|
||||
/**
|
||||
* hash128 种子加盐
|
||||
*/
|
||||
private final static long k0_128 = 0xC83A91E1;
|
||||
private final static long k1_128 = 0x8648DBDB;
|
||||
private final static long k2_128 = 0x7BDEC03B;
|
||||
private final static long k3_128 = 0x2F5870A5;
|
||||
|
||||
public static long hash64(byte[] data) {
|
||||
return hash64(data, 1337);
|
||||
}
|
||||
|
||||
public static Number128 hash128(byte[] data) {
|
||||
return hash128(data, 1337);
|
||||
}
|
||||
|
||||
public static long hash64(byte[] data, long seed) {
|
||||
byte[] buffer = data;
|
||||
long hash = (seed + k2_64) * k0_64;
|
||||
|
||||
long v0, v1, v2, v3;
|
||||
v0 = hash;
|
||||
v1 = hash;
|
||||
v2 = hash;
|
||||
v3 = hash;
|
||||
|
||||
if (buffer.length >= 32) {
|
||||
|
||||
while (buffer.length >= 32) {
|
||||
v0 += littleEndian64(buffer, 0) * k0_64;
|
||||
v0 = rotateLeft64(v0, -29) + v2;
|
||||
v1 += littleEndian64(buffer, 8) * k1_64;
|
||||
v1 = rotateLeft64(v1, -29) + v3;
|
||||
v2 += littleEndian64(buffer, 24) * k2_64;
|
||||
v2 = rotateLeft64(v2, -29) + v0;
|
||||
v3 += littleEndian64(buffer, 32) * k3_64;
|
||||
v3 = rotateLeft64(v3, -29) + v1;
|
||||
buffer = Arrays.copyOfRange(buffer, 32, buffer.length);
|
||||
}
|
||||
|
||||
v2 ^= rotateLeft64(((v0 + v3) * k0_64) + v1, -37) * k1_64;
|
||||
v3 ^= rotateLeft64(((v1 + v2) * k1_64) + v0, -37) * k0_64;
|
||||
v0 ^= rotateLeft64(((v0 + v2) * k0_64) + v3, -37) * k1_64;
|
||||
v1 ^= rotateLeft64(((v1 + v3) * k1_64) + v2, -37) * k0_64;
|
||||
hash += v0 ^ v1;
|
||||
}
|
||||
|
||||
if (buffer.length >= 16) {
|
||||
v0 = hash + littleEndian64(buffer, 0) * k2_64;
|
||||
v0 = rotateLeft64(v0, -29) * k3_64;
|
||||
v1 = hash + littleEndian64(buffer, 8) * k2_64;
|
||||
v1 = rotateLeft64(v1, -29) * k3_64;
|
||||
v0 ^= rotateLeft64(v0 * k0_64, -21) + v1;
|
||||
v1 ^= rotateLeft64(v1 * k3_64, -21) + v0;
|
||||
hash += v1;
|
||||
buffer = Arrays.copyOfRange(buffer, 16, buffer.length);
|
||||
}
|
||||
|
||||
if (buffer.length >= 8) {
|
||||
hash += littleEndian64(buffer, 0) * k3_64;
|
||||
buffer = Arrays.copyOfRange(buffer, 8, buffer.length);
|
||||
hash ^= rotateLeft64(hash, -55) * k1_64;
|
||||
}
|
||||
|
||||
if (buffer.length >= 4) {
|
||||
hash += (long) littleEndian32(Arrays.copyOfRange(buffer, 0, 4)) * k3_64;
|
||||
hash ^= rotateLeft64(hash, -26) * k1_64;
|
||||
buffer = Arrays.copyOfRange(buffer, 4, buffer.length);
|
||||
}
|
||||
|
||||
if (buffer.length >= 2) {
|
||||
hash += (long) littleEndian16(Arrays.copyOfRange(buffer, 0, 2)) * k3_64;
|
||||
buffer = Arrays.copyOfRange(buffer, 2, buffer.length);
|
||||
hash ^= rotateLeft64(hash, -48) * k1_64;
|
||||
}
|
||||
|
||||
if (buffer.length >= 1) {
|
||||
hash += (long) buffer[0] * k3_64;
|
||||
hash ^= rotateLeft64(hash, -38) * k1_64;
|
||||
}
|
||||
|
||||
hash ^= rotateLeft64(hash, -28);
|
||||
hash *= k0_64;
|
||||
hash ^= rotateLeft64(hash, -29);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
public static Number128 hash128(byte[] data, long seed) {
|
||||
byte[] buffer = data;
|
||||
|
||||
long v0, v1, v2, v3;
|
||||
|
||||
v0 = (seed - k0_128) * k3_128;
|
||||
v1 = (seed + k1_128) * k2_128;
|
||||
|
||||
if (buffer.length >= 32) {
|
||||
v2 = (seed + k0_128) * k2_128;
|
||||
v3 = (seed - k1_128) * k3_128;
|
||||
|
||||
while (buffer.length >= 32) {
|
||||
v0 += littleEndian64(buffer, 0) * k0_128;
|
||||
buffer = Arrays.copyOfRange(buffer, 8, buffer.length);
|
||||
v0 = rotateRight(v0, 29) + v2;
|
||||
v1 += littleEndian64(buffer, 0) * k1_128;
|
||||
buffer = Arrays.copyOfRange(buffer, 8, buffer.length);
|
||||
v1 = rotateRight(v1, 29) + v3;
|
||||
v2 += littleEndian64(buffer, 0) * k2_128;
|
||||
buffer = Arrays.copyOfRange(buffer, 8, buffer.length);
|
||||
v2 = rotateRight(v2, 29) + v0;
|
||||
v3 = littleEndian64(buffer, 0) * k3_128;
|
||||
buffer = Arrays.copyOfRange(buffer, 8, buffer.length);
|
||||
v3 = rotateRight(v3, 29) + v1;
|
||||
}
|
||||
|
||||
v2 ^= rotateRight(((v0 + v3) * k0_128) + v1, 21) * k1_128;
|
||||
v3 ^= rotateRight(((v1 + v2) * k1_128) + v0, 21) * k0_128;
|
||||
v0 ^= rotateRight(((v0 + v2) * k0_128) + v3, 21) * k1_128;
|
||||
v1 ^= rotateRight(((v1 + v3) * k1_128) + v2, 21) * k0_128;
|
||||
}
|
||||
|
||||
if (buffer.length >= 16) {
|
||||
v0 += littleEndian64(buffer, 0) * k2_128;
|
||||
buffer = Arrays.copyOfRange(buffer, 8, buffer.length);
|
||||
v0 = rotateRight(v0, 33) * k3_128;
|
||||
v1 += littleEndian64(buffer, 0) * k2_128;
|
||||
buffer = Arrays.copyOfRange(buffer, 8, buffer.length);
|
||||
v1 = rotateRight(v1, 33) * k3_128;
|
||||
v0 ^= rotateRight((v0 * k2_128) + v1, 45) + k1_128;
|
||||
v1 ^= rotateRight((v1 * k3_128) + v0, 45) + k0_128;
|
||||
}
|
||||
|
||||
if (buffer.length >= 8) {
|
||||
v0 += littleEndian64(buffer, 0) * k2_128;
|
||||
buffer = Arrays.copyOfRange(buffer, 8, buffer.length);
|
||||
v0 = rotateRight(v0, 33) * k3_128;
|
||||
v0 ^= rotateRight((v0 * k2_128) + v1, 27) * k1_128;
|
||||
}
|
||||
|
||||
if (buffer.length >= 4) {
|
||||
v1 += (long) littleEndian32(buffer) * k2_128;
|
||||
buffer = Arrays.copyOfRange(buffer, 4, buffer.length);
|
||||
v1 = rotateRight(v1, 33) * k3_128;
|
||||
v1 ^= rotateRight((v1 * k3_128) + v0, 46) * k0_128;
|
||||
}
|
||||
|
||||
if (buffer.length >= 2) {
|
||||
v0 += (long) littleEndian16(buffer) * k2_128;
|
||||
buffer = Arrays.copyOfRange(buffer, 2, buffer.length);
|
||||
v0 = rotateRight(v0, 33) * k3_128;
|
||||
v0 ^= rotateRight((v0 * k2_128) * v1, 22) * k1_128;
|
||||
}
|
||||
|
||||
if (buffer.length >= 1) {
|
||||
v1 += (long) buffer[0] * k2_128;
|
||||
v1 = rotateRight(v1, 33) * k3_128;
|
||||
v1 ^= rotateRight((v1 * k3_128) + v0, 58) * k0_128;
|
||||
}
|
||||
|
||||
v0 += rotateRight((v0 * k0_128) + v1, 13);
|
||||
v1 += rotateRight((v1 * k1_128) + v0, 37);
|
||||
v0 += rotateRight((v0 * k2_128) + v1, 13);
|
||||
v1 += rotateRight((v1 * k3_128) + v0, 37);
|
||||
|
||||
return new Number128(v0, v1);
|
||||
}
|
||||
|
||||
|
||||
private static long littleEndian64(byte[] b, int start) {
|
||||
return ByteUtil.bytesToLong(b, start, ByteOrder.LITTLE_ENDIAN);
|
||||
}
|
||||
|
||||
private static int littleEndian32(byte[] b) {
|
||||
return (int) b[0] | (int) b[1] << 8 | (int) b[2] << 16 | (int) b[3] << 24;
|
||||
}
|
||||
|
||||
private static int littleEndian16(byte[] b) {
|
||||
return ByteUtil.bytesToShort(b, ByteOrder.LITTLE_ENDIAN);
|
||||
}
|
||||
|
||||
private static long rotateLeft64(long x, int k) {
|
||||
int n = 64;
|
||||
int s = k & (n - 1);
|
||||
return x << s | x >> (n - s);
|
||||
}
|
||||
|
||||
private static long rotateRight(long val, int shift) {
|
||||
return (val >> shift) | (val << (64 - shift));
|
||||
}
|
||||
}
|
@ -1,9 +1,11 @@
|
||||
package cn.hutool.core.lang.hash;
|
||||
|
||||
import cn.hutool.core.util.ByteUtil;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
@ -41,6 +43,7 @@ public class MurmurHash implements Serializable{
|
||||
|
||||
private static final int DEFAULT_SEED = 0;
|
||||
private static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8;
|
||||
private static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN;
|
||||
|
||||
/**
|
||||
* Murmur3 32-bit Hash值计算
|
||||
@ -76,11 +79,8 @@ public class MurmurHash implements Serializable{
|
||||
|
||||
// body
|
||||
for (int i = 0; i < nblocks; i++) {
|
||||
int i_4 = i << 2;
|
||||
int k = (data[i_4] & 0xff) //
|
||||
| ((data[i_4 + 1] & 0xff) << 8) //
|
||||
| ((data[i_4 + 2] & 0xff) << 16) //
|
||||
| ((data[i_4 + 3] & 0xff) << 24);
|
||||
int i4 = i << 2;
|
||||
int k = ByteUtil.bytesToInt(data, i4, DEFAULT_ORDER);
|
||||
|
||||
// mix functions
|
||||
k *= C1_32;
|
||||
@ -157,14 +157,7 @@ public class MurmurHash implements Serializable{
|
||||
// body
|
||||
for (int i = 0; i < nblocks; i++) {
|
||||
final int i8 = i << 3;
|
||||
long k = ((long) data[i8] & 0xff) //
|
||||
| (((long) data[i8 + 1] & 0xff) << 8) //
|
||||
| (((long) data[i8 + 2] & 0xff) << 16) //
|
||||
| (((long) data[i8 + 3] & 0xff) << 24) //
|
||||
| (((long) data[i8 + 4] & 0xff) << 32)//
|
||||
| (((long) data[i8 + 5] & 0xff) << 40) //
|
||||
| (((long) data[i8 + 6] & 0xff) << 48) //
|
||||
| (((long) data[i8 + 7] & 0xff) << 56);
|
||||
long k = ByteUtil.bytesToLong(data, i8, DEFAULT_ORDER);
|
||||
|
||||
// mix functions
|
||||
k *= C1;
|
||||
@ -241,23 +234,8 @@ public class MurmurHash implements Serializable{
|
||||
// body
|
||||
for (int i = 0; i < nblocks; i++) {
|
||||
final int i16 = i << 4;
|
||||
long k1 = ((long) data[i16] & 0xff) //
|
||||
| (((long) data[i16 + 1] & 0xff) << 8) //
|
||||
| (((long) data[i16 + 2] & 0xff) << 16) //
|
||||
| (((long) data[i16 + 3] & 0xff) << 24) //
|
||||
| (((long) data[i16 + 4] & 0xff) << 32) //
|
||||
| (((long) data[i16 + 5] & 0xff) << 40) //
|
||||
| (((long) data[i16 + 6] & 0xff) << 48) //
|
||||
| (((long) data[i16 + 7] & 0xff) << 56);
|
||||
|
||||
long k2 = ((long) data[i16 + 8] & 0xff) //
|
||||
| (((long) data[i16 + 9] & 0xff) << 8) //
|
||||
| (((long) data[i16 + 10] & 0xff) << 16) //
|
||||
| (((long) data[i16 + 11] & 0xff) << 24) //
|
||||
| (((long) data[i16 + 12] & 0xff) << 32) //
|
||||
| (((long) data[i16 + 13] & 0xff) << 40) //
|
||||
| (((long) data[i16 + 14] & 0xff) << 48) //
|
||||
| (((long) data[i16 + 15] & 0xff) << 56);
|
||||
long k1 = ByteUtil.bytesToLong(data, i16, DEFAULT_ORDER);
|
||||
long k2 = ByteUtil.bytesToLong(data, i16 + 8, DEFAULT_ORDER);
|
||||
|
||||
// mix functions for k1
|
||||
k1 *= C1;
|
||||
|
@ -6,7 +6,7 @@ package cn.hutool.core.lang.hash;
|
||||
* @author hexiufeng
|
||||
* @since 5.2.5
|
||||
*/
|
||||
public class Number128 extends Number{
|
||||
public class Number128 extends Number {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private long lowValue;
|
||||
@ -23,22 +23,47 @@ public class Number128 extends Number{
|
||||
this.highValue = highValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取低位值
|
||||
*
|
||||
* @return 地位值
|
||||
*/
|
||||
public long getLowValue() {
|
||||
return lowValue;
|
||||
}
|
||||
|
||||
public long getHighValue() {
|
||||
return highValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置低位值
|
||||
*
|
||||
* @param lowValue 低位值
|
||||
*/
|
||||
public void setLowValue(long lowValue) {
|
||||
this.lowValue = lowValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取高位值
|
||||
*
|
||||
* @return 高位值
|
||||
*/
|
||||
public long getHighValue() {
|
||||
return highValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置高位值
|
||||
*
|
||||
* @param hiValue 高位值
|
||||
*/
|
||||
public void setHighValue(long hiValue) {
|
||||
this.highValue = hiValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取高低位数组,long[0]:低位,long[1]:高位
|
||||
*
|
||||
* @return 高低位数组,long[0]:低位,long[1]:高位
|
||||
*/
|
||||
public long[] getLongArray() {
|
||||
return new long[]{lowValue, highValue};
|
||||
}
|
||||
|
@ -228,7 +228,6 @@ public class TreeBuilder<E> implements Builder<Tree<E>> {
|
||||
}
|
||||
|
||||
final Map<E, Tree<E>> eTreeMap = MapUtil.sortByValue(this.idTreeMap, false);
|
||||
List<Tree<E>> rootTreeList = CollUtil.newArrayList();
|
||||
E parentId;
|
||||
for (Tree<E> node : eTreeMap.values()) {
|
||||
if (null == node) {
|
||||
@ -237,7 +236,6 @@ public class TreeBuilder<E> implements Builder<Tree<E>> {
|
||||
parentId = node.getParentId();
|
||||
if (ObjectUtil.equals(this.root.getId(), parentId)) {
|
||||
this.root.addChildren(node);
|
||||
rootTreeList.add(node);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
73
hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java
Executable file
73
hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java
Executable file
@ -0,0 +1,73 @@
|
||||
package cn.hutool.core.map;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* 忽略大小写的{@link TreeMap}<br>
|
||||
* 对KEY忽略大小写,get("Value")和get("value")获得的值相同,put进入的值也会被覆盖
|
||||
*
|
||||
* @author Looly
|
||||
*
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
* @since 3.3.1
|
||||
*/
|
||||
public class CaseInsensitiveTreeMap<K, V> extends CustomKeyMap<K, V> {
|
||||
private static final long serialVersionUID = 4043263744224569870L;
|
||||
|
||||
// ------------------------------------------------------------------------- Constructor start
|
||||
/**
|
||||
* 构造
|
||||
*/
|
||||
public CaseInsensitiveTreeMap() {
|
||||
this((Comparator<? super K>) null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param m Map
|
||||
* @since 3.1.2
|
||||
*/
|
||||
public CaseInsensitiveTreeMap(Map<? extends K, ? extends V> m) {
|
||||
this();
|
||||
this.putAll(m);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param m Map
|
||||
* @since 3.1.2
|
||||
*/
|
||||
public CaseInsensitiveTreeMap(SortedMap<? extends K, ? extends V> m) {
|
||||
super(new TreeMap<K, V>(m));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param comparator 比较器,{@code null}表示使用默认比较器
|
||||
*/
|
||||
public CaseInsensitiveTreeMap(Comparator<? super K> comparator) {
|
||||
super(new TreeMap<>(comparator));
|
||||
}
|
||||
// ------------------------------------------------------------------------- Constructor end
|
||||
|
||||
/**
|
||||
* 将Key转为小写
|
||||
*
|
||||
* @param key KEY
|
||||
* @return 小写KEY
|
||||
*/
|
||||
@Override
|
||||
protected Object customKey(Object key) {
|
||||
if (key instanceof CharSequence) {
|
||||
key = key.toString().toLowerCase();
|
||||
}
|
||||
return key;
|
||||
}
|
||||
}
|
@ -8,12 +8,15 @@ import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.List;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BinaryOperator;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.stream.Collector;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 可变的汇聚操作{@link Collector} 相关工具封装
|
||||
@ -140,6 +143,18 @@ public class CollectorUtil {
|
||||
return groupingBy(classifier, HashMap::new, downstream);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提供对null值友好的groupingBy操作的{@link Collector}实现
|
||||
*
|
||||
* @param classifier 分组依据
|
||||
* @param <T> 实体类型
|
||||
* @param <K> 实体中的分组依据对应类型,也是Map中key的类型
|
||||
* @return {@link Collector}
|
||||
*/
|
||||
public static <T, K> Collector<T, ?, Map<K, List<T>>>
|
||||
groupingBy(Function<? super T, ? extends K> classifier) {
|
||||
return groupingBy(classifier, Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 对null友好的 toMap 操作的 {@link Collector}实现,默认使用HashMap
|
||||
|
@ -3601,6 +3601,46 @@ public class CharSequenceUtil {
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 替换指定字符串的指定区间内字符为指定字符串,字符串只重复一次<br>
|
||||
* 此方法使用{@link String#codePoints()}完成拆分替换
|
||||
*
|
||||
* @param str 字符串
|
||||
* @param startInclude 开始位置(包含)
|
||||
* @param endExclude 结束位置(不包含)
|
||||
* @param replacedStr 被替换的字符串
|
||||
* @return 替换后的字符串
|
||||
* @since 3.2.1
|
||||
*/
|
||||
public static String replace(CharSequence str, int startInclude, int endExclude, CharSequence replacedStr) {
|
||||
if (isEmpty(str)) {
|
||||
return str(str);
|
||||
}
|
||||
final String originalStr = str(str);
|
||||
int[] strCodePoints = originalStr.codePoints().toArray();
|
||||
final int strLength = strCodePoints.length;
|
||||
if (startInclude > strLength) {
|
||||
return originalStr;
|
||||
}
|
||||
if (endExclude > strLength) {
|
||||
endExclude = strLength;
|
||||
}
|
||||
if (startInclude > endExclude) {
|
||||
// 如果起始位置大于结束位置,不替换
|
||||
return originalStr;
|
||||
}
|
||||
|
||||
final StringBuilder stringBuilder = new StringBuilder();
|
||||
for (int i = 0; i < startInclude; i++) {
|
||||
stringBuilder.append(new String(strCodePoints, i, 1));
|
||||
}
|
||||
stringBuilder.append(replacedStr);
|
||||
for (int i = endExclude; i < strLength; i++) {
|
||||
stringBuilder.append(new String(strCodePoints, i, 1));
|
||||
}
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 替换所有正则匹配的文本,并使用自定义函数决定如何替换<br>
|
||||
* replaceFun可以通过{@link Matcher}提取出匹配到的内容的不同部分,然后经过重新处理、组装变成新的内容放回原位。
|
||||
|
@ -1,6 +1,7 @@
|
||||
package cn.hutool.core.text.csv;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.collection.ArrayIter;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
@ -45,6 +46,10 @@ public final class CsvWriter implements Closeable, Flushable, Serializable {
|
||||
* 是否处于新行开始
|
||||
*/
|
||||
private boolean newline = true;
|
||||
/**
|
||||
* 是否首行,即CSV开始的位置,当初始化时默认为true,一旦写入内容,为false
|
||||
*/
|
||||
private boolean isFirstLine = true;
|
||||
|
||||
// --------------------------------------------------------------------------------------------------- Constructor start
|
||||
|
||||
@ -183,13 +188,7 @@ public final class CsvWriter implements Closeable, Flushable, Serializable {
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public CsvWriter write(String[]... lines) throws IORuntimeException {
|
||||
if (ArrayUtil.isNotEmpty(lines)) {
|
||||
for (final String[] values : lines) {
|
||||
appendLine(values);
|
||||
}
|
||||
flush();
|
||||
}
|
||||
return this;
|
||||
return write(new ArrayIter<>(lines));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -320,9 +319,14 @@ public final class CsvWriter implements Closeable, Flushable, Serializable {
|
||||
public CsvWriter writeComment(String comment) {
|
||||
Assert.notNull(this.config.commentCharacter, "Comment is disable!");
|
||||
try {
|
||||
if(isFirstLine){
|
||||
// 首行不补换行符
|
||||
isFirstLine = false;
|
||||
}else {
|
||||
writer.write(config.lineDelimiter);
|
||||
}
|
||||
writer.write(this.config.commentCharacter);
|
||||
writer.write(comment);
|
||||
writer.write(config.lineDelimiter);
|
||||
newline = true;
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
@ -366,12 +370,17 @@ public final class CsvWriter implements Closeable, Flushable, Serializable {
|
||||
* @param fields 字段列表 ({@code null} 值会被做为空值追加)
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
private void doAppendLine(final String... fields) throws IOException {
|
||||
private void doAppendLine(String... fields) throws IOException {
|
||||
if (null != fields) {
|
||||
if(isFirstLine){
|
||||
// 首行不补换行符
|
||||
isFirstLine = false;
|
||||
}else {
|
||||
writer.write(config.lineDelimiter);
|
||||
}
|
||||
for (String field : fields) {
|
||||
appendField(field);
|
||||
}
|
||||
writer.write(config.lineDelimiter);
|
||||
newline = true;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
/**
|
||||
* 提供CSV文件读写的封装,入口为CsvUtil
|
||||
* 提供CSV文件读写的封装,入口为CsvUtil<br>
|
||||
* 规范见:https://datatracker.ietf.org/doc/html/rfc4180
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
|
@ -27,7 +27,11 @@ import java.util.concurrent.atomic.LongAdder;
|
||||
*/
|
||||
public class ByteUtil {
|
||||
|
||||
public static ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN;
|
||||
public static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN;
|
||||
/**
|
||||
* CPU的字节序
|
||||
*/
|
||||
public static final ByteOrder CPU_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian")) ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN;
|
||||
|
||||
/**
|
||||
* int转byte
|
||||
@ -130,16 +134,29 @@ public class ByteUtil {
|
||||
* @return int值
|
||||
*/
|
||||
public static int bytesToInt(byte[] bytes, ByteOrder byteOrder) {
|
||||
return bytesToInt(bytes, 0, byteOrder);
|
||||
}
|
||||
|
||||
/**
|
||||
* byte[]转int值<br>
|
||||
* 自定义端序
|
||||
*
|
||||
* @param bytes byte数组
|
||||
* @param byteOrder 端序
|
||||
* @return int值
|
||||
* @since 5.7.21
|
||||
*/
|
||||
public static int bytesToInt(byte[] bytes, int start, ByteOrder byteOrder) {
|
||||
if (ByteOrder.LITTLE_ENDIAN == byteOrder) {
|
||||
return bytes[0] & 0xFF | //
|
||||
(bytes[1] & 0xFF) << 8 | //
|
||||
(bytes[2] & 0xFF) << 16 | //
|
||||
(bytes[3] & 0xFF) << 24; //
|
||||
return bytes[start] & 0xFF | //
|
||||
(bytes[1 + start] & 0xFF) << 8 | //
|
||||
(bytes[2 + start] & 0xFF) << 16 | //
|
||||
(bytes[3 + start] & 0xFF) << 24; //
|
||||
} else {
|
||||
return bytes[3] & 0xFF | //
|
||||
(bytes[2] & 0xFF) << 8 | //
|
||||
(bytes[1] & 0xFF) << 16 | //
|
||||
(bytes[0] & 0xFF) << 24; //
|
||||
return bytes[3 + start] & 0xFF | //
|
||||
(bytes[2 + start] & 0xFF) << 8 | //
|
||||
(bytes[1 + start] & 0xFF) << 16 | //
|
||||
(bytes[start] & 0xFF) << 24; //
|
||||
}
|
||||
|
||||
}
|
||||
@ -243,16 +260,31 @@ public class ByteUtil {
|
||||
* @return long值
|
||||
*/
|
||||
public static long bytesToLong(byte[] bytes, ByteOrder byteOrder) {
|
||||
return bytesToLong(bytes, 0, byteOrder);
|
||||
}
|
||||
|
||||
/**
|
||||
* byte数组转long<br>
|
||||
* 自定义端序<br>
|
||||
* from: https://stackoverflow.com/questions/4485128/how-do-i-convert-long-to-byte-and-back-in-java
|
||||
*
|
||||
* @param bytes byte数组
|
||||
* @param start 计算数组开始位置
|
||||
* @param byteOrder 端序
|
||||
* @return long值
|
||||
* @since 5.7.21
|
||||
*/
|
||||
public static long bytesToLong(byte[] bytes, int start, ByteOrder byteOrder) {
|
||||
long values = 0;
|
||||
if (ByteOrder.LITTLE_ENDIAN == byteOrder) {
|
||||
for (int i = (Long.BYTES - 1); i >= 0; i--) {
|
||||
values <<= Byte.SIZE;
|
||||
values |= (bytes[i] & 0xff);
|
||||
values |= (bytes[i + start] & 0xff);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < Long.BYTES; i++) {
|
||||
values <<= Byte.SIZE;
|
||||
values |= (bytes[i] & 0xff);
|
||||
values |= (bytes[i + start] & 0xff);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package cn.hutool.core.util;
|
||||
|
||||
import cn.hutool.core.lang.hash.CityHash;
|
||||
import cn.hutool.core.lang.hash.MetroHash;
|
||||
import cn.hutool.core.lang.hash.MurmurHash;
|
||||
import cn.hutool.core.lang.hash.Number128;
|
||||
|
||||
@ -386,7 +387,7 @@ public class HashUtil {
|
||||
if (ucChar <= 'Z' && ucChar >= 'A') {
|
||||
ucChar = (char) (ucChar + 32);
|
||||
}
|
||||
hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216;
|
||||
hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216;
|
||||
}
|
||||
} else {
|
||||
for (i = 1; i <= 96; i++) {
|
||||
@ -394,7 +395,7 @@ public class HashUtil {
|
||||
if (ucChar <= 'Z' && ucChar >= 'A') {
|
||||
ucChar = (char) (ucChar + 32);
|
||||
}
|
||||
hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216;
|
||||
hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216;
|
||||
}
|
||||
}
|
||||
if (hash < 0) {
|
||||
@ -545,4 +546,46 @@ public class HashUtil {
|
||||
public static long[] cityHash128(byte[] data, Number128 seed) {
|
||||
return CityHash.hash128(data, seed).getLongArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* MetroHash 算法64-bit实现
|
||||
*
|
||||
* @param data 数据
|
||||
* @param seed 种子
|
||||
* @return hash值
|
||||
*/
|
||||
public static long metroHash64(byte[] data, long seed) {
|
||||
return MetroHash.hash64(data, seed);
|
||||
}
|
||||
|
||||
/**
|
||||
* MetroHash 算法64-bit实现
|
||||
*
|
||||
* @param data 数据
|
||||
* @return hash值
|
||||
*/
|
||||
public static long metroHash64(byte[] data) {
|
||||
return MetroHash.hash64(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* MetroHash 算法128-bit实现
|
||||
*
|
||||
* @param data 数据
|
||||
* @param seed 种子
|
||||
* @return hash值,long[0]:低位,long[1]:高位
|
||||
*/
|
||||
public static long[] metroHash128(byte[] data, long seed) {
|
||||
return MetroHash.hash128(data,seed).getLongArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* MetroHash 算法128-bit实现
|
||||
*
|
||||
* @param data 数据
|
||||
* @return hash值,long[0]:低位,long[1]:高位
|
||||
*/
|
||||
public static long[] metroHash128(byte[] data) {
|
||||
return MetroHash.hash128(data).getLongArray();
|
||||
}
|
||||
}
|
||||
|
@ -95,6 +95,26 @@ public class URLUtil extends URLEncodeUtil {
|
||||
*/
|
||||
public static final String WAR_URL_SEPARATOR = "*/";
|
||||
|
||||
/**
|
||||
* 将{@link URI}转换为{@link URL}
|
||||
*
|
||||
* @param uri {@link URI}
|
||||
* @return URL对象
|
||||
* @see URI#toURL()
|
||||
* @throws UtilException {@link MalformedURLException}包装,URI格式有问题时抛出
|
||||
* @since 5.7.21
|
||||
*/
|
||||
public static URL url(URI uri) throws UtilException{
|
||||
if(null == uri){
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return uri.toURL();
|
||||
} catch (MalformedURLException e) {
|
||||
throw new UtilException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过一个字符串形式的URL地址创建URL对象
|
||||
*
|
||||
@ -114,7 +134,9 @@ public class URLUtil extends URLEncodeUtil {
|
||||
* @since 4.1.1
|
||||
*/
|
||||
public static URL url(String url, URLStreamHandler handler) {
|
||||
Assert.notNull(url, "URL must not be null");
|
||||
if(null == url){
|
||||
return null;
|
||||
}
|
||||
|
||||
// 兼容Spring的ClassPath路径
|
||||
if (url.startsWith(CLASSPATH_URL_PREFIX)) {
|
||||
@ -142,6 +164,9 @@ public class URLUtil extends URLEncodeUtil {
|
||||
* @since 5.5.2
|
||||
*/
|
||||
public static URI getStringURI(CharSequence content) {
|
||||
if(null == content){
|
||||
return null;
|
||||
}
|
||||
final String contentStr = StrUtil.addPrefixIfNot(content, "string:///");
|
||||
return URI.create(contentStr);
|
||||
}
|
||||
@ -461,6 +486,7 @@ public class URLUtil extends URLEncodeUtil {
|
||||
* @since 3.0.9
|
||||
*/
|
||||
public static boolean isFileURL(URL url) {
|
||||
Assert.notNull(url, "URL must be not null");
|
||||
String protocol = url.getProtocol();
|
||||
return (URL_PROTOCOL_FILE.equals(protocol) || //
|
||||
URL_PROTOCOL_VFSFILE.equals(protocol) || //
|
||||
@ -474,6 +500,7 @@ public class URLUtil extends URLEncodeUtil {
|
||||
* @return 是否为jar包URL
|
||||
*/
|
||||
public static boolean isJarURL(URL url) {
|
||||
Assert.notNull(url, "URL must be not null");
|
||||
final String protocol = url.getProtocol();
|
||||
return (URL_PROTOCOL_JAR.equals(protocol) || //
|
||||
URL_PROTOCOL_ZIP.equals(protocol) || //
|
||||
@ -489,6 +516,7 @@ public class URLUtil extends URLEncodeUtil {
|
||||
* @since 4.1
|
||||
*/
|
||||
public static boolean isJarFileURL(URL url) {
|
||||
Assert.notNull(url, "URL must be not null");
|
||||
return (URL_PROTOCOL_FILE.equals(url.getProtocol()) && //
|
||||
url.getPath().toLowerCase().endsWith(FileUtil.JAR_FILE_EXT));
|
||||
}
|
||||
@ -501,7 +529,7 @@ public class URLUtil extends URLEncodeUtil {
|
||||
* @since 3.2.1
|
||||
*/
|
||||
public static InputStream getStream(URL url) {
|
||||
Assert.notNull(url);
|
||||
Assert.notNull(url, "URL must be not null");
|
||||
try {
|
||||
return url.openStream();
|
||||
} catch (IOException e) {
|
||||
|
@ -0,0 +1,93 @@
|
||||
package cn.hutool.core.builder;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* {@link GenericBuilder} 单元测试类
|
||||
*
|
||||
* @author TomXin
|
||||
*/
|
||||
public class GenericBuilderTest {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
Box box = GenericBuilder
|
||||
.of(Box::new)
|
||||
.with(Box::setId, 1024L)
|
||||
.with(Box::setTitle, "Hello World!")
|
||||
.with(Box::setLength, 9)
|
||||
.with(Box::setWidth, 8)
|
||||
.with(Box::setHeight, 7)
|
||||
.build();
|
||||
|
||||
Assert.assertEquals(1024L, box.getId().longValue());
|
||||
Assert.assertEquals("Hello World!", box.getTitle());
|
||||
Assert.assertEquals(9, box.getLength().intValue());
|
||||
Assert.assertEquals(8, box.getWidth().intValue());
|
||||
Assert.assertEquals(7, box.getHeight().intValue());
|
||||
|
||||
// 对象修改
|
||||
Box boxModified = GenericBuilder
|
||||
.of(() -> box)
|
||||
.with(Box::setTitle, "Hello Friend!")
|
||||
.with(Box::setLength, 3)
|
||||
.with(Box::setWidth, 4)
|
||||
.with(Box::setHeight, 5)
|
||||
.build();
|
||||
|
||||
Assert.assertEquals(1024L, boxModified.getId().longValue());
|
||||
Assert.assertEquals("Hello Friend!", box.getTitle());
|
||||
Assert.assertEquals(3, boxModified.getLength().intValue());
|
||||
Assert.assertEquals(4, boxModified.getWidth().intValue());
|
||||
Assert.assertEquals(5, boxModified.getHeight().intValue());
|
||||
|
||||
// 多参数构造
|
||||
Box box1 = GenericBuilder
|
||||
.of(Box::new, 2048L, "Hello Partner!", 222, 333, 444)
|
||||
.with(Box::alis)
|
||||
.build();
|
||||
|
||||
Assert.assertEquals(2048L, box1.getId().longValue());
|
||||
Assert.assertEquals("Hello Partner!", box1.getTitle());
|
||||
Assert.assertEquals(222, box1.getLength().intValue());
|
||||
Assert.assertEquals(333, box1.getWidth().intValue());
|
||||
Assert.assertEquals(444, box1.getHeight().intValue());
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@Accessors(chain = true)
|
||||
public static class Box {
|
||||
private Long id;
|
||||
private String title;
|
||||
private Integer length;
|
||||
private Integer width;
|
||||
private Integer height;
|
||||
private String titleAlias;
|
||||
|
||||
public Box() {
|
||||
}
|
||||
|
||||
public Box(Long id, String title, Integer length, Integer width, Integer height) {
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
this.length = length;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public void alis() {
|
||||
if (StrUtil.isNotBlank(this.title)) {
|
||||
this.titleAlias = "TomXin:\"" + title + "\"";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -19,7 +19,7 @@ import java.util.Map;
|
||||
public class ListUtilTest {
|
||||
|
||||
@Test
|
||||
public void splitTest(){
|
||||
public void splitTest() {
|
||||
List<List<Object>> lists = ListUtil.split(null, 3);
|
||||
Assert.assertEquals(ListUtil.empty(), lists);
|
||||
|
||||
@ -60,7 +60,7 @@ public class ListUtilTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void splitAvgTest(){
|
||||
public void splitAvgTest() {
|
||||
List<List<Object>> lists = ListUtil.splitAvg(null, 3);
|
||||
Assert.assertEquals(ListUtil.empty(), lists);
|
||||
|
||||
@ -80,13 +80,13 @@ public class ListUtilTest {
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void splitAvgNotZero(){
|
||||
public void splitAvgNotZero() {
|
||||
// limit不能小于等于0
|
||||
ListUtil.splitAvg(Arrays.asList(1, 2, 3, 4), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void editTest(){
|
||||
public void editTest() {
|
||||
List<String> a = ListUtil.toLinkedList("1", "2", "3");
|
||||
final List<String> filter = (List<String>) CollUtil.edit(a, str -> "edit" + str);
|
||||
Assert.assertEquals("edit1", filter.get(0));
|
||||
@ -104,7 +104,7 @@ public class ListUtilTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pageTest(){
|
||||
public void pageTest() {
|
||||
List<Integer> a = ListUtil.toLinkedList(1, 2, 3,4,5);
|
||||
|
||||
PageUtil.setFirstPageNo(1);
|
||||
@ -167,10 +167,13 @@ public class ListUtilTest {
|
||||
Assert.assertArrayEquals(new int[]{}, pageListData.get(0).stream().mapToInt(Integer::valueOf).toArray());
|
||||
Assert.assertArrayEquals(new int[]{3, 4}, pageListData.get(1).stream().mapToInt(Integer::valueOf).toArray());
|
||||
Assert.assertArrayEquals(new int[]{5}, pageListData.get(2).stream().mapToInt(Integer::valueOf).toArray());
|
||||
|
||||
// 恢复默认值,避免影响其他测试用例
|
||||
PageUtil.setFirstPageNo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subTest(){
|
||||
public void subTest() {
|
||||
final List<Integer> of = ListUtil.of(1, 2, 3, 4);
|
||||
final List<Integer> sub = ListUtil.sub(of, 2, 4);
|
||||
sub.remove(0);
|
||||
@ -181,10 +184,10 @@ public class ListUtilTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sortByPropertyTest(){
|
||||
public void sortByPropertyTest() {
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
class TestBean{
|
||||
class TestBean {
|
||||
private int order;
|
||||
private String name;
|
||||
}
|
||||
|
@ -1,15 +1,14 @@
|
||||
package cn.hutool.core.convert;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Date;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
|
||||
public class DateConvertTest {
|
||||
|
||||
@Test
|
||||
@ -28,7 +27,7 @@ public class DateConvertTest {
|
||||
int dateLong = -1497600000;
|
||||
Date value = Convert.toDate(dateLong);
|
||||
Assert.assertNotNull(value);
|
||||
Assert.assertEquals("Mon Dec 15 00:00:00 CST 1969", value.toString());
|
||||
Assert.assertEquals("Mon Dec 15 00:00:00 CST 1969", value.toString().replace("GMT+08:00", "CST"));
|
||||
|
||||
final java.sql.Date sqlDate = Convert.convert(java.sql.Date.class, dateLong);
|
||||
Assert.assertNotNull(sqlDate);
|
||||
|
@ -175,6 +175,26 @@ public class NumberChineseFormatterTest {
|
||||
Assert.assertEquals("零点零伍", f1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void formatSimpleTest() {
|
||||
String f1 = NumberChineseFormatter.formatSimple(1_2345);
|
||||
Assert.assertEquals("1.23万", f1);
|
||||
f1 = NumberChineseFormatter.formatSimple(-5_5555);
|
||||
Assert.assertEquals("-5.56万", f1);
|
||||
f1 = NumberChineseFormatter.formatSimple(1_2345_6789);
|
||||
Assert.assertEquals("1.23亿", f1);
|
||||
f1 = NumberChineseFormatter.formatSimple(-5_5555_5555);
|
||||
Assert.assertEquals("-5.56亿", f1);
|
||||
f1 = NumberChineseFormatter.formatSimple(1_2345_6789_1011L);
|
||||
Assert.assertEquals("1.23万亿", f1);
|
||||
f1 = NumberChineseFormatter.formatSimple(-5_5555_5555_5555L);
|
||||
Assert.assertEquals("-5.56万亿", f1);
|
||||
f1 = NumberChineseFormatter.formatSimple(123);
|
||||
Assert.assertEquals("123", f1);
|
||||
f1 = NumberChineseFormatter.formatSimple(-123);
|
||||
Assert.assertEquals("-123", f1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void digitToChineseTest() {
|
||||
String digitToChinese = Convert.digitToChinese(12_4124_1241_2421.12);
|
||||
|
@ -105,4 +105,12 @@ public class ChineseDateTest {
|
||||
Assert.assertEquals("戊申猴年 五月初五", c1.toString());
|
||||
Assert.assertEquals("戊申猴年 闰五月初五", c2.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getChineseMonthTest2(){
|
||||
//https://github.com/dromara/hutool/issues/2112
|
||||
ChineseDate springFestival = new ChineseDate(DateUtil.parseDate("2022-02-01"));
|
||||
final String chineseMonth = springFestival.getChineseMonth();
|
||||
Assert.assertEquals("一月", chineseMonth);
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package cn.hutool.core.date;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.date.BetweenFormatter.Level;
|
||||
import cn.hutool.core.date.format.FastDateFormat;
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
@ -694,6 +693,8 @@ public class DateUtilTest {
|
||||
String dateStr = "Wed Sep 16 11:26:23 CST 2009";
|
||||
|
||||
SimpleDateFormat sdf = new SimpleDateFormat(DatePattern.JDK_DATETIME_PATTERN, Locale.US);
|
||||
// Asia/Shanghai是以地区命名的地区标准时,在中国叫CST,因此如果解析CST时不使用"Asia/Shanghai"而使用"GMT+08:00",会导致相差一个小时
|
||||
sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
|
||||
final DateTime parse = DateUtil.parse(dateStr, sdf);
|
||||
|
||||
DateTime dateTime = DateUtil.parseCST(dateStr);
|
||||
@ -992,11 +993,13 @@ public class DateUtilTest {
|
||||
|
||||
@Test
|
||||
public void parseSingleMonthAndDayTest() {
|
||||
final DateTime parse = DateUtil.parse("2021-1-1");
|
||||
DateTime parse = DateUtil.parse("2021-1-1");
|
||||
Assert.assertNotNull(parse);
|
||||
Assert.assertEquals("2021-01-01 00:00:00", parse.toString());
|
||||
|
||||
Console.log(DateUtil.parse("2021-1-22 00:00:00"));
|
||||
parse = DateUtil.parse("2021-1-22 00:00:00");
|
||||
Assert.assertNotNull(parse);
|
||||
Assert.assertEquals("2021-01-22 00:00:00", parse.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -1004,4 +1007,17 @@ public class DateUtilTest {
|
||||
final DateTime parse = DateUtil.parse("2021-12-01", DatePattern.NORM_DATE_FORMATTER);
|
||||
Assert.assertEquals("2021-12-01 00:00:00", parse.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isSameWeekTest() {
|
||||
// 周六与周日比较
|
||||
final boolean isSameWeek = DateUtil.isSameWeek(DateTime.of("2022-01-01", "yyyy-MM-dd"), DateTime.of("2022-01-02", "yyyy-MM-dd"), true);
|
||||
Assert.assertTrue(isSameWeek);
|
||||
// 周日与周一比较
|
||||
final boolean isSameWeek1 = DateUtil.isSameWeek(DateTime.of("2022-01-02", "yyyy-MM-dd"), DateTime.of("2022-01-03", "yyyy-MM-dd"), false);
|
||||
Assert.assertTrue(isSameWeek1);
|
||||
// 跨月比较
|
||||
final boolean isSameWeek2 = DateUtil.isSameWeek(DateTime.of("2021-12-29", "yyyy-MM-dd"), DateTime.of("2022-01-01", "yyyy-MM-dd"), true);
|
||||
Assert.assertTrue(isSameWeek2);
|
||||
}
|
||||
}
|
||||
|
@ -197,4 +197,24 @@ public class LocalDateTimeUtilTest {
|
||||
Assert.assertTrue(LocalDateTimeUtil.isOverlap(oneStartTime2,oneEndTime2,realStartTime,realEndTime));
|
||||
Assert.assertFalse(LocalDateTimeUtil.isOverlap(oneStartTime3,oneEndTime3,realStartTime,realEndTime));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void weekOfYearTest(){
|
||||
LocalDate date1 = LocalDate.of(2021, 12, 31);
|
||||
final int weekOfYear1 = LocalDateTimeUtil.weekOfYear(date1);
|
||||
Assert.assertEquals(52, weekOfYear1);
|
||||
|
||||
final int weekOfYear2 = LocalDateTimeUtil.weekOfYear(date1.atStartOfDay());
|
||||
Assert.assertEquals(52, weekOfYear2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void weekOfYearTest2(){
|
||||
LocalDate date1 = LocalDate.of(2022, 1, 31);
|
||||
final int weekOfYear1 = LocalDateTimeUtil.weekOfYear(date1);
|
||||
Assert.assertEquals(5, weekOfYear1);
|
||||
|
||||
final int weekOfYear2 = LocalDateTimeUtil.weekOfYear(date1.atStartOfDay());
|
||||
Assert.assertEquals(5, weekOfYear2);
|
||||
}
|
||||
}
|
||||
|
@ -37,4 +37,15 @@ public class MonthTest {
|
||||
lastDay = Month.of(Calendar.DECEMBER).getLastDay(true);
|
||||
Assert.assertEquals(31, lastDay);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toJdkMonthTest(){
|
||||
final java.time.Month month = Month.AUGUST.toJdkMonth();
|
||||
Assert.assertEquals(java.time.Month.AUGUST, month);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void toJdkMonthTest2(){
|
||||
Month.UNDECIMBER.toJdkMonth();
|
||||
}
|
||||
}
|
||||
|
@ -21,11 +21,8 @@ public class CheckedUtilTest {
|
||||
|
||||
@Test
|
||||
public void sleepTest() {
|
||||
|
||||
VoidFunc0 func = () -> Thread.sleep(1000L);
|
||||
func.callWithRuntimeException();
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -39,7 +36,6 @@ public class CheckedUtilTest {
|
||||
} catch (Exception re) {
|
||||
Assert.assertTrue(re instanceof RuntimeException);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
|
@ -4,6 +4,7 @@ import cn.hutool.core.date.DateField;
|
||||
import cn.hutool.core.date.DateRange;
|
||||
import cn.hutool.core.date.DateTime;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
@ -12,8 +13,8 @@ import java.util.NoSuchElementException;
|
||||
|
||||
/**
|
||||
* {@link Range} 单元测试
|
||||
* @author Looly
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class RangeTest {
|
||||
|
||||
@ -36,6 +37,32 @@ public class RangeTest {
|
||||
Assert.assertFalse(range.hasNext());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dateRangeFuncTest() {
|
||||
DateTime start = DateUtil.parse("2021-01-01");
|
||||
DateTime end = DateUtil.parse("2021-01-03");
|
||||
|
||||
List<Integer> dayOfMonthList = DateUtil.rangeFunc(start, end, DateField.DAY_OF_YEAR, a -> DateTime.of(a).dayOfMonth());
|
||||
Assert.assertArrayEquals(dayOfMonthList.toArray(new Integer[]{}), new Integer[]{1, 2, 3});
|
||||
|
||||
List<Integer> dayOfMonthList2 = DateUtil.rangeFunc(null, null, DateField.DAY_OF_YEAR, a -> DateTime.of(a).dayOfMonth());
|
||||
Assert.assertArrayEquals(dayOfMonthList2.toArray(new Integer[]{}), new Integer[]{});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dateRangeConsumeTest() {
|
||||
DateTime start = DateUtil.parse("2021-01-01");
|
||||
DateTime end = DateUtil.parse("2021-01-03");
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
DateUtil.rangeConsume(start, end, DateField.DAY_OF_YEAR, a -> sb.append(DateTime.of(a).dayOfMonth()).append("#"));
|
||||
Assert.assertEquals(sb.toString(), "1#2#3#");
|
||||
|
||||
StringBuilder sb2 = new StringBuilder();
|
||||
DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb2.append(DateTime.of(a).dayOfMonth()).append("#"));
|
||||
Assert.assertEquals(sb2.toString(), StrUtil.EMPTY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dateRangeTest2() {
|
||||
DateTime start = DateUtil.parse("2021-01-31");
|
||||
@ -84,7 +111,7 @@ public class RangeTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rangeDayOfYearTest(){
|
||||
public void rangeDayOfYearTest() {
|
||||
DateTime start = DateUtil.parse("2017-01-01");
|
||||
DateTime end = DateUtil.parse("2017-01-05");
|
||||
|
||||
@ -109,4 +136,39 @@ public class RangeTest {
|
||||
Assert.assertEquals(DateUtil.parse("2017-01-01"), rangeToList.get(0));
|
||||
Assert.assertEquals(DateUtil.parse("2017-01-02"), rangeToList.get(1));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void rangeContains() {
|
||||
// 开始区间
|
||||
DateTime start = DateUtil.parse("2017-01-01");
|
||||
DateTime end = DateUtil.parse("2017-01-31");
|
||||
DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR);
|
||||
// 结束区间
|
||||
DateTime start1 = DateUtil.parse("2017-01-31");
|
||||
DateTime end1 = DateUtil.parse("2017-02-02");
|
||||
DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR);
|
||||
// 交集
|
||||
List<DateTime> dateTimes = DateUtil.rangeContains(startRange, endRange);
|
||||
Assert.assertEquals(1, dateTimes.size());
|
||||
Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rangeNotContains() {
|
||||
// 开始区间
|
||||
DateTime start = DateUtil.parse("2017-01-01");
|
||||
DateTime end = DateUtil.parse("2017-01-30");
|
||||
DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR);
|
||||
// 结束区间
|
||||
DateTime start1 = DateUtil.parse("2017-01-01");
|
||||
DateTime end1 = DateUtil.parse("2017-01-31");
|
||||
DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR);
|
||||
// 差集
|
||||
List<DateTime> dateTimes1 = DateUtil.rangeNotContains(startRange, endRange);
|
||||
|
||||
Assert.assertEquals(1, dateTimes1.size());
|
||||
Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes1.get(0));
|
||||
}
|
||||
|
||||
}
|
||||
|
36
hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java
Executable file
36
hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java
Executable file
@ -0,0 +1,36 @@
|
||||
package cn.hutool.core.lang.hash;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class CityHashTest {
|
||||
|
||||
@Test
|
||||
public void hash32Test() {
|
||||
int hv = CityHash.hash32(StrUtil.utf8Bytes("你"));
|
||||
Assert.assertEquals(1290029860, hv);
|
||||
|
||||
hv = CityHash.hash32(StrUtil.utf8Bytes("你好"));
|
||||
Assert.assertEquals(1374181357, hv);
|
||||
|
||||
hv = CityHash.hash32(StrUtil.utf8Bytes("见到你很高兴"));
|
||||
Assert.assertEquals(1475516842, hv);
|
||||
hv = CityHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件"));
|
||||
Assert.assertEquals(0x51020cae, hv);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hash64Test() {
|
||||
long hv = CityHash.hash64(StrUtil.utf8Bytes("你"));
|
||||
Assert.assertEquals(-4296898700418225525L, hv);
|
||||
|
||||
hv = CityHash.hash64(StrUtil.utf8Bytes("你好"));
|
||||
Assert.assertEquals(-4294276205456761303L, hv);
|
||||
|
||||
hv = CityHash.hash64(StrUtil.utf8Bytes("见到你很高兴"));
|
||||
Assert.assertEquals(272351505337503793L, hv);
|
||||
hv = CityHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件"));
|
||||
Assert.assertEquals(-8234735310919228703L, hv);
|
||||
}
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
package cn.hutool.core.lang.hash;
|
||||
|
||||
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.HexUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* https://gitee.com/dromara/hutool/pulls/532
|
||||
*/
|
||||
public class MetroHashTest {
|
||||
|
||||
@Test
|
||||
public void testEmpty() {
|
||||
Assert.assertEquals("31290877cceaea29", HexUtil.toHex(MetroHash.hash64(StrUtil.utf8Bytes(""), 0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void metroHash64Test() {
|
||||
byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8);
|
||||
final long hash64 = MetroHash.hash64(str);
|
||||
Assert.assertEquals(62920234463891865L, hash64);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void metroHash128Test() {
|
||||
byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8);
|
||||
final long[] hash128 = MetroHash.hash128(str).getLongArray();
|
||||
Assert.assertEquals(4956592424592439349L, hash128[0]);
|
||||
Assert.assertEquals(6301214698325086246L, hash128[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据量越大 MetroHash 优势越明显,
|
||||
*/
|
||||
@Test
|
||||
@Ignore
|
||||
public void bulkHashing64Test() {
|
||||
String[] strArray = getRandomStringArray();
|
||||
long startCity = System.currentTimeMillis();
|
||||
for (String s : strArray) {
|
||||
CityHash.hash64(s.getBytes());
|
||||
}
|
||||
long endCity = System.currentTimeMillis();
|
||||
|
||||
long startMetro = System.currentTimeMillis();
|
||||
for (String s : strArray) {
|
||||
MetroHash.hash64(StrUtil.utf8Bytes(s));
|
||||
}
|
||||
long endMetro = System.currentTimeMillis();
|
||||
|
||||
System.out.println("metroHash =============" + (endMetro - startMetro));
|
||||
System.out.println("cityHash =============" + (endCity - startCity));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 数据量越大 MetroHash 优势越明显,
|
||||
*/
|
||||
@Test
|
||||
@Ignore
|
||||
public void bulkHashing128Test() {
|
||||
String[] strArray = getRandomStringArray();
|
||||
long startCity = System.currentTimeMillis();
|
||||
for (String s : strArray) {
|
||||
CityHash.hash128(s.getBytes());
|
||||
}
|
||||
long endCity = System.currentTimeMillis();
|
||||
|
||||
long startMetro = System.currentTimeMillis();
|
||||
for (String s : strArray) {
|
||||
MetroHash.hash128(StrUtil.utf8Bytes(s));
|
||||
}
|
||||
long endMetro = System.currentTimeMillis();
|
||||
|
||||
System.out.println("metroHash =============" + (endMetro - startMetro));
|
||||
System.out.println("cityHash =============" + (endCity - startCity));
|
||||
}
|
||||
|
||||
|
||||
private static String[] getRandomStringArray() {
|
||||
String[] result = new String[10000000];
|
||||
int index = 0;
|
||||
while (index < 10000000) {
|
||||
result[index++] = RandomUtil.randomString(RandomUtil.randomInt(64));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
36
hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java
Executable file
36
hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java
Executable file
@ -0,0 +1,36 @@
|
||||
package cn.hutool.core.lang.hash;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class MurMurHashTest {
|
||||
|
||||
@Test
|
||||
public void hash32Test() {
|
||||
int hv = MurmurHash.hash32(StrUtil.utf8Bytes("你"));
|
||||
Assert.assertEquals(222142701, hv);
|
||||
|
||||
hv = MurmurHash.hash32(StrUtil.utf8Bytes("你好"));
|
||||
Assert.assertEquals(1188098267, hv);
|
||||
|
||||
hv = MurmurHash.hash32(StrUtil.utf8Bytes("见到你很高兴"));
|
||||
Assert.assertEquals(-1898490321, hv);
|
||||
hv = MurmurHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件"));
|
||||
Assert.assertEquals(-1713131054, hv);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hash64Test() {
|
||||
long hv = MurmurHash.hash64(StrUtil.utf8Bytes("你"));
|
||||
Assert.assertEquals(-1349759534971957051L, hv);
|
||||
|
||||
hv = MurmurHash.hash64(StrUtil.utf8Bytes("你好"));
|
||||
Assert.assertEquals(-7563732748897304996L, hv);
|
||||
|
||||
hv = MurmurHash.hash64(StrUtil.utf8Bytes("见到你很高兴"));
|
||||
Assert.assertEquals(-766658210119995316L, hv);
|
||||
hv = MurmurHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件"));
|
||||
Assert.assertEquals(-7469283059271653317L, hv);
|
||||
}
|
||||
}
|
@ -348,4 +348,19 @@ public class UrlBuilderTest {
|
||||
builder.setFragment(builder.getFragment() + "?timestamp=1640391380204");
|
||||
Assert.assertEquals("https://www.hutool.cn/#/a/b?timestamp=1640391380204", builder.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void paramWithPlusTest(){
|
||||
String url = "http://127.0.0.1/?" +
|
||||
"Expires=1642734164&" +
|
||||
"security-token=CAIS+AF1q6Ft5B2yfSjIr5fYEeju1b1ggpPee2KGpjlgQtdfl43urjz2IHtKdXRvBu8Xs" +
|
||||
"/4wnmxX7f4YlqB6T55OSAmcNZEoPwKpT4zmMeT7oMWQweEurv" +
|
||||
"/MQBqyaXPS2MvVfJ+OLrf0ceusbFbpjzJ6xaCAGxypQ12iN+/m6" +
|
||||
"/Ngdc9FHHPPD1x8CcxROxFppeIDKHLVLozNCBPxhXfKB0ca0WgVy0EHsPnvm5DNs0uH1AKjkbRM9r6ceMb0M5NeW75kSMqw0eBMca7M7TVd8RAi9t0t1" +
|
||||
"/IVpGiY4YDAWQYLv0rda7DOltFiMkpla7MmXqlft+hzcgeQY0pc" +
|
||||
"/RqAAYRYVCBiyuzAexSiDiJX1VqWljg4jYp1sdyv3HpV3sXVcf6VH6AN9ot5YNTw4JNO0aNpLpLm93rRMrOKIOsve+OmNyZ4HS7qHQKt1qp7HY1A" +
|
||||
"/wGhJstkAoGQt+CHSMwVdIx3bVT1+ZYnJdM/oIQ/90afw4EEEQaRE51Z0rQC7z8d";
|
||||
final String build = UrlBuilder.of(url).build();
|
||||
Assert.assertEquals(url, build);
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,13 @@ public class CharSequenceUtilTest {
|
||||
Assert.assertEquals(replace, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replaceByStrTest(){
|
||||
String replace = "SSM15930297701BeryAllen";
|
||||
String result = CharSequenceUtil.replace(replace, 5, 12, "***");
|
||||
Assert.assertEquals("SSM15***01BeryAllen", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addPrefixIfNotTest(){
|
||||
String str = "hutool";
|
||||
|
@ -12,4 +12,10 @@ public class NamingCaseTest {
|
||||
.set("customerNickV2", "customer_nick_v2")
|
||||
.forEach((key, value) -> Assert.assertEquals(value, NamingCase.toUnderlineCase(key)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toUnderLineCaseTest2(){
|
||||
final String wPRunOZTime = NamingCase.toUnderlineCase("wPRunOZTime");
|
||||
Assert.assertEquals("w_P_run_OZ_time", wPRunOZTime);
|
||||
}
|
||||
}
|
||||
|
@ -48,4 +48,16 @@ public class CsvParserTest {
|
||||
Assert.assertEquals("", row.getRawList().get(1));
|
||||
IoUtil.close(parser);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseEscapeTest(){
|
||||
// https://datatracker.ietf.org/doc/html/rfc4180#section-2
|
||||
// 第七条规则
|
||||
StringReader reader = StrUtil.getReader("\"b\"\"bb\"");
|
||||
CsvParser parser = new CsvParser(reader, null);
|
||||
CsvRow row = parser.nextRow();
|
||||
Assert.assertNotNull(row);
|
||||
Assert.assertEquals(1, row.size());
|
||||
Assert.assertEquals("b\"bb", row.get(0));
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,8 @@ public class CsvWriterTest {
|
||||
CharsetUtil.CHARSET_GBK, false, csvWriteConfig);
|
||||
|
||||
writer.writeHeaderLine("name", "gender", "address");
|
||||
writer.writeLine("张三", "男", "XX市XX区");
|
||||
writer.writeLine("李四", "男", "XX市XX区,01号");
|
||||
writer.close();
|
||||
}
|
||||
}
|
||||
|
@ -10,9 +10,16 @@ public class ByteUtilTest {
|
||||
@Test
|
||||
public void intAndBytesLittleEndianTest() {
|
||||
// 测试 int 转小端序 byte 数组
|
||||
int int1 = RandomUtil.randomInt();
|
||||
int int1 = RandomUtil.randomInt((Integer.MAX_VALUE));
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
buffer.putInt(int1);
|
||||
byte[] bytesIntFromBuffer = buffer.array();
|
||||
|
||||
byte[] bytesInt = ByteUtil.intToBytes(int1, ByteOrder.LITTLE_ENDIAN);
|
||||
Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt);
|
||||
|
||||
int int2 = ByteUtil.bytesToInt(bytesInt, ByteOrder.LITTLE_ENDIAN);
|
||||
Assert.assertEquals(int1, int2);
|
||||
|
||||
@ -28,8 +35,14 @@ public class ByteUtilTest {
|
||||
@Test
|
||||
public void intAndBytesBigEndianTest() {
|
||||
// 测试 int 转大端序 byte 数组
|
||||
int int2 = RandomUtil.randomInt();
|
||||
int int2 = RandomUtil.randomInt(Integer.MAX_VALUE);
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES);
|
||||
buffer.putInt(int2);
|
||||
byte[] bytesIntFromBuffer = buffer.array();
|
||||
|
||||
byte[] bytesInt = ByteUtil.intToBytes(int2, ByteOrder.BIG_ENDIAN);
|
||||
Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt);
|
||||
|
||||
// 测试大端序 byte 数组转 int
|
||||
int int3 = ByteUtil.bytesToInt(bytesInt, ByteOrder.BIG_ENDIAN);
|
||||
@ -39,9 +52,16 @@ public class ByteUtilTest {
|
||||
@Test
|
||||
public void longAndBytesLittleEndianTest() {
|
||||
// 测试 long 转 byte 数组
|
||||
long long1 = 2223;
|
||||
long long1 = RandomUtil.randomLong(Long.MAX_VALUE);
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
buffer.putLong(long1);
|
||||
byte[] bytesLongFromBuffer = buffer.array();
|
||||
|
||||
byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.LITTLE_ENDIAN);
|
||||
Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong);
|
||||
|
||||
long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.LITTLE_ENDIAN);
|
||||
Assert.assertEquals(long1, long2);
|
||||
|
||||
@ -57,11 +77,16 @@ public class ByteUtilTest {
|
||||
@Test
|
||||
public void longAndBytesBigEndianTest() {
|
||||
// 测试大端序 long 转 byte 数组
|
||||
long long1 = 2223;
|
||||
long long1 = RandomUtil.randomLong(Long.MAX_VALUE);
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
|
||||
buffer.putLong(long1);
|
||||
byte[] bytesLongFromBuffer = buffer.array();
|
||||
|
||||
byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.BIG_ENDIAN);
|
||||
long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN);
|
||||
Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong);
|
||||
|
||||
long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN);
|
||||
Assert.assertEquals(long1, long2);
|
||||
}
|
||||
|
||||
|
@ -5,14 +5,13 @@ import org.junit.Test;
|
||||
|
||||
/**
|
||||
* 分页单元测试
|
||||
* @author Looly
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class PageUtilTest {
|
||||
|
||||
@Test
|
||||
public void transToStartEndTest(){
|
||||
PageUtil.setFirstPageNo(0);
|
||||
public void transToStartEndTest() {
|
||||
int[] startEnd1 = PageUtil.transToStartEnd(0, 10);
|
||||
Assert.assertEquals(0, startEnd1[0]);
|
||||
Assert.assertEquals(10, startEnd1[1]);
|
||||
@ -23,7 +22,7 @@ public class PageUtilTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void totalPage(){
|
||||
public void totalPage() {
|
||||
int totalPage = PageUtil.totalPage(20, 3);
|
||||
Assert.assertEquals(7, totalPage);
|
||||
}
|
||||
@ -31,6 +30,6 @@ public class PageUtilTest {
|
||||
@Test
|
||||
public void rainbowTest() {
|
||||
int[] rainbow = PageUtil.rainbow(5, 20, 6);
|
||||
Assert.assertArrayEquals(new int[] {3, 4, 5, 6, 7, 8}, rainbow);
|
||||
Assert.assertArrayEquals(new int[]{3, 4, 5, 6, 7, 8}, rainbow);
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.20</version>
|
||||
<version>5.7.21</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-cron</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.20</version>
|
||||
<version>5.7.21</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-crypto</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.20</version>
|
||||
<version>5.7.21</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-db</artifactId>
|
||||
@ -27,7 +27,7 @@
|
||||
<sqlite.version>3.36.0.3</sqlite.version>
|
||||
<!-- 此处固定2.5.x,支持到JDK8 -->
|
||||
<hsqldb.version>2.5.2</hsqldb.version>
|
||||
<jedis.version>4.0.0</jedis.version>
|
||||
<jedis.version>4.1.1</jedis.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@ -81,7 +81,7 @@
|
||||
<dependency>
|
||||
<groupId>com.github.chris2018998</groupId>
|
||||
<artifactId>beecp</artifactId>
|
||||
<version>3.3.0</version>
|
||||
<version>3.3.1</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
@ -143,13 +143,13 @@
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.27</version>
|
||||
<version>8.0.28</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<version>42.3.1</version>
|
||||
<version>42.3.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@ -167,13 +167,13 @@
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<version>2.0.206</version>
|
||||
<version>2.1.210</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ru.yandex.clickhouse</groupId>
|
||||
<artifactId>clickhouse-jdbc</artifactId>
|
||||
<version>0.3.1</version>
|
||||
<version>0.3.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
@ -1,5 +1,6 @@
|
||||
package cn.hutool.db.ds.c3p0;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.db.DbRuntimeException;
|
||||
import cn.hutool.db.ds.AbstractDSFactory;
|
||||
@ -40,14 +41,6 @@ public class C3p0DSFactory extends AbstractDSFactory {
|
||||
@Override
|
||||
protected DataSource createDataSource(String jdbcUrl, String driver, String user, String pass, Setting poolSetting) {
|
||||
final ComboPooledDataSource ds = new ComboPooledDataSource();
|
||||
ds.setJdbcUrl(jdbcUrl);
|
||||
try {
|
||||
ds.setDriverClass(driver);
|
||||
} catch (PropertyVetoException e) {
|
||||
throw new DbRuntimeException(e);
|
||||
}
|
||||
ds.setUser(user);
|
||||
ds.setPassword(pass);
|
||||
|
||||
// remarks等特殊配置,since 5.3.8
|
||||
final Props connProps = new Props();
|
||||
@ -58,7 +51,18 @@ public class C3p0DSFactory extends AbstractDSFactory {
|
||||
connProps.setProperty(key, connValue);
|
||||
}
|
||||
}
|
||||
ds.setProperties(connProps);
|
||||
if(MapUtil.isNotEmpty(connProps)){
|
||||
ds.setProperties(connProps);
|
||||
}
|
||||
|
||||
ds.setJdbcUrl(jdbcUrl);
|
||||
try {
|
||||
ds.setDriverClass(driver);
|
||||
} catch (PropertyVetoException e) {
|
||||
throw new DbRuntimeException(e);
|
||||
}
|
||||
ds.setUser(user);
|
||||
ds.setPassword(pass);
|
||||
|
||||
// 注入属性
|
||||
poolSetting.toBean(ds);
|
||||
|
54
hutool-db/src/main/java/cn/hutool/db/sql/ConditionGroup.java
Normal file
54
hutool-db/src/main/java/cn/hutool/db/sql/ConditionGroup.java
Normal file
@ -0,0 +1,54 @@
|
||||
package cn.hutool.db.sql;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 条件组<br>
|
||||
* 用于构建复杂where条件
|
||||
*
|
||||
* @author tjh
|
||||
* @since 5.7.21
|
||||
*/
|
||||
public class ConditionGroup extends Condition {
|
||||
/**
|
||||
* 条件列表
|
||||
*/
|
||||
private Condition[] conditions;
|
||||
|
||||
/**
|
||||
* 追加条件
|
||||
*
|
||||
* @param conditions 条件列表
|
||||
*/
|
||||
public void addConditions(Condition... conditions) {
|
||||
if (null == this.conditions) {
|
||||
this.conditions = conditions;
|
||||
} else {
|
||||
this.conditions = ArrayUtil.addAll(this.conditions, conditions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将条件组转换为条件字符串,使用括号包裹,并回填占位符对应的参数值
|
||||
*
|
||||
* @param paramValues 参数列表,用于回填占位符对应参数值
|
||||
* @return 条件字符串
|
||||
*/
|
||||
@Override
|
||||
public String toString(List<Object> paramValues) {
|
||||
if (ArrayUtil.isEmpty(conditions)) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
|
||||
final StringBuilder conditionStrBuilder = StrUtil.builder();
|
||||
conditionStrBuilder.append("(");
|
||||
// 将组内的条件构造为SQL,因为toString,会进行递归,处理所有的条件组
|
||||
conditionStrBuilder.append(ConditionBuilder.of(this.conditions).build(paramValues));
|
||||
conditionStrBuilder.append(")");
|
||||
|
||||
return conditionStrBuilder.toString();
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ package cn.hutool.db;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.db.ds.DSFactory;
|
||||
import cn.hutool.db.ds.DataSourceWrapper;
|
||||
import cn.hutool.db.ds.bee.BeeDSFactory;
|
||||
import cn.hutool.db.ds.c3p0.C3p0DSFactory;
|
||||
import cn.hutool.db.ds.dbcp.DbcpDSFactory;
|
||||
@ -9,6 +10,7 @@ import cn.hutool.db.ds.druid.DruidDSFactory;
|
||||
import cn.hutool.db.ds.hikari.HikariDSFactory;
|
||||
import cn.hutool.db.ds.pooled.PooledDSFactory;
|
||||
import cn.hutool.db.ds.tomcat.TomcatDSFactory;
|
||||
import com.mchange.v2.c3p0.ComboPooledDataSource;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
@ -87,6 +89,15 @@ public class DsTest {
|
||||
Assert.assertTrue(CollUtil.isNotEmpty(all));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void c3p0DsUserAndPassTest() {
|
||||
// https://gitee.com/dromara/hutool/issues/I4T7XZ
|
||||
DSFactory.setCurrentDSFactory(new C3p0DSFactory());
|
||||
ComboPooledDataSource ds = (ComboPooledDataSource) ((DataSourceWrapper) DSFactory.get("mysql")).getRaw();
|
||||
Assert.assertEquals("root", ds.getUser());
|
||||
Assert.assertEquals("123456", ds.getPassword());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hutoolPoolTest() throws SQLException {
|
||||
DSFactory.setCurrentDSFactory(new PooledDSFactory());
|
||||
|
@ -0,0 +1,28 @@
|
||||
package cn.hutool.db.sql;
|
||||
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class ConditionGroupTest {
|
||||
@Test
|
||||
public void ConditionGroupToStringTest() {
|
||||
Condition condition1 = new Condition("a", "A");
|
||||
Condition condition2 = new Condition("b", "B");
|
||||
condition2.setLinkOperator(LogicalOperator.OR);
|
||||
Condition condition3 = new Condition("c", "C");
|
||||
Condition condition4 = new Condition("d", "D");
|
||||
|
||||
ConditionGroup cg = new ConditionGroup();
|
||||
cg.addConditions(condition1, condition2);
|
||||
|
||||
// 条件组嵌套情况
|
||||
ConditionGroup cg2 = new ConditionGroup();
|
||||
cg2.addConditions(cg, condition3);
|
||||
|
||||
final ConditionBuilder conditionBuilder = ConditionBuilder.of(cg2, condition4);
|
||||
|
||||
Assert.assertEquals("((a = ? OR b = ?) AND c = ?) AND d = ?", conditionBuilder.build());
|
||||
Assert.assertEquals(ListUtil.of("A", "B", "C", "D"), conditionBuilder.getParamValues());
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.20</version>
|
||||
<version>5.7.21</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-dfa</artifactId>
|
||||
@ -17,11 +17,6 @@
|
||||
<description>Hutool 基于DFA的关键词查找</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-json</artifactId>
|
||||
|
@ -195,11 +195,21 @@ public final class SensitiveUtil {
|
||||
*/
|
||||
public static <T> T sensitiveFilter(T bean, boolean isGreedMatch, SensitiveProcessor sensitiveProcessor) {
|
||||
String jsonText = JSONUtil.toJsonStr(bean);
|
||||
@SuppressWarnings("unchecked")
|
||||
final Class<T> c = (Class<T>) bean.getClass();
|
||||
@SuppressWarnings("unchecked") final Class<T> c = (Class<T>) bean.getClass();
|
||||
return JSONUtil.toBean(sensitiveFilter(jsonText, isGreedMatch, sensitiveProcessor), c);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理过滤文本中的敏感词,默认替换成*
|
||||
*
|
||||
* @param text 文本
|
||||
* @return 敏感词过滤处理后的文本
|
||||
* @since 5.7.21
|
||||
*/
|
||||
public static String sensitiveFilter(String text) {
|
||||
return sensitiveFilter(text, true, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理过滤文本中的敏感词,默认替换成*
|
||||
*
|
||||
@ -214,13 +224,14 @@ public final class SensitiveUtil {
|
||||
}
|
||||
|
||||
//敏感词过滤场景下,不需要密集匹配
|
||||
List<FoundWord> foundWordList = getFoundAllSensitive(text, false, isGreedMatch);
|
||||
List<FoundWord> foundWordList = getFoundAllSensitive(text, true, isGreedMatch);
|
||||
if (CollUtil.isEmpty(foundWordList)) {
|
||||
return text;
|
||||
}
|
||||
sensitiveProcessor = sensitiveProcessor == null ? new SensitiveProcessor() {
|
||||
} : sensitiveProcessor;
|
||||
Map<Integer, FoundWord> foundWordMap = new HashMap<>(foundWordList.size());
|
||||
|
||||
final Map<Integer, FoundWord> foundWordMap = new HashMap<>(foundWordList.size(), 1);
|
||||
foundWordList.forEach(foundWord -> foundWordMap.put(foundWord.getStartIndex(), foundWord));
|
||||
int length = text.length();
|
||||
StringBuilder textStringBuilder = new StringBuilder();
|
||||
|
@ -3,7 +3,6 @@ package cn.hutool.dfa;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.lang.Filter;
|
||||
import cn.hutool.core.text.StrBuilder;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@ -247,15 +246,15 @@ public class WordTree extends HashMap<Character, WordTree> {
|
||||
|
||||
List<FoundWord> foundWords = new ArrayList<>();
|
||||
WordTree current = this;
|
||||
int length = text.length();
|
||||
final int length = text.length();
|
||||
final Filter<Character> charFilter = this.charFilter;
|
||||
//存放查找到的字符缓存。完整出现一个词时加到findedWords中,否则清空
|
||||
final StrBuilder wordBuffer = StrUtil.strBuilder();
|
||||
final StrBuilder keyBuffer = StrUtil.strBuilder();
|
||||
final StringBuilder wordBuffer = StrUtil.builder();
|
||||
final StringBuilder keyBuffer = StrUtil.builder();
|
||||
char currentChar;
|
||||
for (int i = 0; i < length; i++) {
|
||||
wordBuffer.reset();
|
||||
keyBuffer.reset();
|
||||
wordBuffer.setLength(0);
|
||||
keyBuffer.setLength(0);
|
||||
for (int j = i; j < length; j++) {
|
||||
currentChar = text.charAt(j);
|
||||
// Console.log("i: {}, j: {}, currentChar: {}", i, j, currentChar);
|
||||
@ -284,6 +283,7 @@ public class WordTree extends HashMap<Character, WordTree> {
|
||||
if (false == isDensityMatch) {
|
||||
//如果非密度匹配,跳过匹配到的词
|
||||
i = j;
|
||||
break;
|
||||
}
|
||||
if (false == isGreedMatch) {
|
||||
//如果懒惰匹配(非贪婪匹配)。当遇到第一个结尾标记就结束本轮匹配
|
||||
|
@ -47,7 +47,7 @@ public class DfaTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* 贪婪匹配原则测试
|
||||
* 贪婪非密集匹配原则测试
|
||||
*/
|
||||
@Test
|
||||
public void greedMatchTest() {
|
||||
@ -56,15 +56,15 @@ public class DfaTest {
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------------------------------------
|
||||
// 情况三:匹配到最长关键词,跳过已经匹配的关键词
|
||||
// 匹配到【大】,由于到最长匹配,因此【大土豆】接着被匹配
|
||||
// 由于【大土豆】被匹配,【土豆】被跳过,由于【刚出锅】被匹配,【出锅】被跳过
|
||||
// 匹配到【大】,由于非密集匹配,因此从下一个字符开始查找,匹配到【土豆】接着被匹配
|
||||
// 由于【刚出锅】被匹配,由于非密集匹配,【出锅】被跳过
|
||||
List<String> matchAll = tree.matchAll(text, -1, false, true);
|
||||
Assert.assertEquals(matchAll, CollUtil.newArrayList("大", "大土^豆", "刚出锅"));
|
||||
Assert.assertEquals(matchAll, CollUtil.newArrayList("大", "土^豆", "刚出锅"));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 密集匹配原则(最短匹配)和贪婪匹配原则测试
|
||||
* 密集匹配原则(最长匹配)和贪婪匹配原则测试
|
||||
*/
|
||||
@Test
|
||||
public void densityAndGreedMatchTest() {
|
||||
@ -80,6 +80,29 @@ public class DfaTest {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void densityAndGreedMatchTest2(){
|
||||
WordTree tree = new WordTree();
|
||||
tree.addWord("赵");
|
||||
tree.addWord("赵阿");
|
||||
tree.addWord("赵阿三");
|
||||
|
||||
final List<FoundWord> result = tree.matchAllWords("赵阿三在做什么", -1, true, true);
|
||||
Assert.assertEquals(3, result.size());
|
||||
|
||||
Assert.assertEquals("赵", result.get(0).getWord());
|
||||
Assert.assertEquals(0, result.get(0).getStartIndex().intValue());
|
||||
Assert.assertEquals(0, result.get(0).getEndIndex().intValue());
|
||||
|
||||
Assert.assertEquals("赵阿", result.get(1).getWord());
|
||||
Assert.assertEquals(0, result.get(1).getStartIndex().intValue());
|
||||
Assert.assertEquals(1, result.get(1).getEndIndex().intValue());
|
||||
|
||||
Assert.assertEquals("赵阿三", result.get(2).getWord());
|
||||
Assert.assertEquals(0, result.get(2).getStartIndex().intValue());
|
||||
Assert.assertEquals(2, result.get(2).getEndIndex().intValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* 停顿词测试
|
||||
*/
|
||||
|
@ -1,5 +1,7 @@
|
||||
package cn.hutool.dfa;
|
||||
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import lombok.Data;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
@ -24,25 +26,17 @@ public class SensitiveUtilTest {
|
||||
Assert.assertEquals(bean.getStr(), "我有一颗$****,***的");
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class TestBean {
|
||||
private String str;
|
||||
private Integer num;
|
||||
|
||||
public String getStr() {
|
||||
return str;
|
||||
}
|
||||
|
||||
public void setStr(String str) {
|
||||
this.str = str;
|
||||
}
|
||||
|
||||
public Integer getNum() {
|
||||
return num;
|
||||
}
|
||||
|
||||
public void setNum(Integer num) {
|
||||
this.num = num;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void issue2126(){
|
||||
SensitiveUtil.init(ListUtil.of("赵", "赵阿", "赵阿三"));
|
||||
|
||||
String result = SensitiveUtil.sensitiveFilter("赵阿三在做什么。", true, null);
|
||||
Assert.assertEquals("***在做什么。", result);
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.20</version>
|
||||
<version>5.7.21</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-extra</artifactId>
|
||||
@ -19,11 +19,11 @@
|
||||
<properties>
|
||||
<!-- versions -->
|
||||
<velocity.version>2.3</velocity.version>
|
||||
<beetl.version>3.9.3.RELEASE</beetl.version>
|
||||
<beetl.version>3.10.0.RELEASE</beetl.version>
|
||||
<rythm.version>1.4.1</rythm.version>
|
||||
<freemarker.version>2.3.31</freemarker.version>
|
||||
<enjoy.version>4.9.16</enjoy.version>
|
||||
<thymeleaf.version>3.0.14.RELEASE</thymeleaf.version>
|
||||
<thymeleaf.version>3.0.15.RELEASE</thymeleaf.version>
|
||||
<mail.version>1.6.2</mail.version>
|
||||
<jsch.version>0.1.55</jsch.version>
|
||||
<sshj.version>0.32.0</sshj.version>
|
||||
@ -31,7 +31,7 @@
|
||||
<net.version>3.8.0</net.version>
|
||||
<emoji-java.version>5.1.1</emoji-java.version>
|
||||
<servlet-api.version>4.0.1</servlet-api.version>
|
||||
<spring-boot.version>2.6.2</spring-boot.version>
|
||||
<spring-boot.version>2.6.3</spring-boot.version>
|
||||
<cglib.version>3.3.0</cglib.version>
|
||||
</properties>
|
||||
|
||||
@ -119,6 +119,12 @@
|
||||
<version>2.6.0</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.subchen</groupId>
|
||||
<artifactId>jetbrick-template</artifactId>
|
||||
<version>2.1.10</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- 邮件 -->
|
||||
<dependency>
|
||||
@ -334,7 +340,7 @@
|
||||
<dependency>
|
||||
<groupId>com.github.houbb</groupId>
|
||||
<artifactId>pinyin</artifactId>
|
||||
<version>0.2.2</version>
|
||||
<version>0.3.1</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@ -440,7 +446,7 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-expression</artifactId>
|
||||
<version>5.3.14</version>
|
||||
<version>5.3.15</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
@ -126,11 +126,11 @@ public class EmojiUtil {
|
||||
* @return 替换后的字符串
|
||||
*/
|
||||
public static String toHtmlHex(String str) {
|
||||
return EmojiParser.parseToHtmlHexadecimal(str);
|
||||
return toHtml(str, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字符串中的Unicode Emoji字符转换为HTML表现形式
|
||||
* 将字符串中的Unicode Emoji字符转换为HTML表现形式(Hex方式)
|
||||
* <p>
|
||||
* 例如:<code>👦🏿</code> 转换为 <code>&#128102;</code>
|
||||
*
|
||||
@ -138,7 +138,24 @@ public class EmojiUtil {
|
||||
* @return 替换后的字符串
|
||||
*/
|
||||
public static String toHtml(String str) {
|
||||
return EmojiParser.parseToHtmlHexadecimal(str);
|
||||
return toHtml(str, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字符串中的Unicode Emoji字符转换为HTML表现形式,例如:
|
||||
* <pre>
|
||||
* 如果为hex形式,<code>👦🏿</code> 转换为 <code>&#x1f466;</code>
|
||||
* 否则,<code>👦🏿</code> 转换为 <code>&#128102;</code>
|
||||
* </pre>
|
||||
*
|
||||
* @param str 包含Emoji Unicode字符的字符串
|
||||
* @param isHex 是否hex形式
|
||||
* @return 替换后的字符串
|
||||
* @since 5.7.21
|
||||
*/
|
||||
public static String toHtml(String str, boolean isHex) {
|
||||
return isHex ? EmojiParser.parseToHtmlHexadecimal(str) :
|
||||
EmojiParser.parseToHtmlDecimal(str);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -259,7 +259,7 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA
|
||||
/**
|
||||
* 发布事件
|
||||
*
|
||||
* @param event the event to publish
|
||||
* @param event 待发布的事件,事件必须是{@link ApplicationEvent}的子类
|
||||
* @since 5.7.12
|
||||
*/
|
||||
public static void publishEvent(ApplicationEvent event) {
|
||||
@ -267,6 +267,19 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA
|
||||
applicationContext.publishEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布事件
|
||||
* Spring 4.2+ 版本事件可以不再是{@link ApplicationEvent}的子类
|
||||
*
|
||||
* @param event 待发布的事件
|
||||
* @since 5.7.21
|
||||
*/
|
||||
public static void publishEvent(Object event) {
|
||||
if (null != applicationContext) {
|
||||
applicationContext.publishEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -0,0 +1,109 @@
|
||||
package cn.hutool.extra.template.engine.jetbrick;
|
||||
|
||||
import cn.hutool.extra.template.Template;
|
||||
import cn.hutool.extra.template.TemplateConfig;
|
||||
import cn.hutool.extra.template.TemplateEngine;
|
||||
import jetbrick.template.JetEngine;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Jetbrick模板引擎封装<br>
|
||||
* 见:https://github.com/subchen/jetbrick-template-2x
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.7.21
|
||||
*/
|
||||
public class JetbrickEngine implements TemplateEngine {
|
||||
|
||||
private JetEngine engine;
|
||||
|
||||
// --------------------------------------------------------------------------------- Constructor start
|
||||
/**
|
||||
* 默认构造
|
||||
*/
|
||||
public JetbrickEngine() {}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param config 模板配置
|
||||
*/
|
||||
public JetbrickEngine(TemplateConfig config) {
|
||||
init(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param engine {@link JetEngine}
|
||||
*/
|
||||
public JetbrickEngine(JetEngine engine) {
|
||||
init(engine);
|
||||
}
|
||||
// --------------------------------------------------------------------------------- Constructor end
|
||||
|
||||
|
||||
@Override
|
||||
public TemplateEngine init(TemplateConfig config) {
|
||||
init(createEngine(config));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化引擎
|
||||
* @param engine 引擎
|
||||
*/
|
||||
private void init(JetEngine engine){
|
||||
this.engine = engine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Template getTemplate(String resource) {
|
||||
if(null == this.engine){
|
||||
init(TemplateConfig.DEFAULT);
|
||||
}
|
||||
return JetbrickTemplate.wrap(engine.getTemplate(resource));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建引擎
|
||||
*
|
||||
* @param config 模板配置
|
||||
* @return {@link JetEngine}
|
||||
*/
|
||||
private static JetEngine createEngine(TemplateConfig config) {
|
||||
if (null == config) {
|
||||
config = TemplateConfig.DEFAULT;
|
||||
}
|
||||
|
||||
Properties props = new Properties();
|
||||
props.setProperty("jetx.input.encoding", config.getCharsetStr());
|
||||
props.setProperty("jetx.output.encoding", config.getCharsetStr());
|
||||
props.setProperty("jetx.template.loaders", "$loader");
|
||||
|
||||
switch (config.getResourceMode()){
|
||||
case CLASSPATH:
|
||||
props.setProperty("$loader", "jetbrick.template.loader.ClasspathResourceLoader");
|
||||
props.setProperty("$loader.root", config.getPath());
|
||||
break;
|
||||
case FILE:
|
||||
props.setProperty("$loader", "jetbrick.template.loader.FileSystemResourceLoader");
|
||||
props.setProperty("$loader.root", config.getPath());
|
||||
break;
|
||||
case WEB_ROOT:
|
||||
props.setProperty("$loader", "jetbrick.template.loader.ServletResourceLoader");
|
||||
props.setProperty("$loader.root", config.getPath());
|
||||
break;
|
||||
case STRING:
|
||||
props.setProperty("$loader", "cn.hutool.extra.template.engine.jetbrick.loader.StringResourceLoader");
|
||||
props.setProperty("$loader.charset", config.getCharsetStr());
|
||||
break;
|
||||
default:
|
||||
// 默认
|
||||
return JetEngine.create();
|
||||
}
|
||||
|
||||
return JetEngine.create(props);
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package cn.hutool.extra.template.engine.jetbrick;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.lang.TypeReference;
|
||||
import cn.hutool.extra.template.AbstractTemplate;
|
||||
import jetbrick.template.JetTemplate;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.io.Writer;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Jetbrick模板实现<br>
|
||||
* 见:https://github.com/subchen/jetbrick-template-2x
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.7.21
|
||||
*/
|
||||
public class JetbrickTemplate extends AbstractTemplate implements Serializable{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final JetTemplate rawTemplate;
|
||||
|
||||
/**
|
||||
* 包装Jetbrick模板
|
||||
*
|
||||
* @param jetTemplate Jetbrick的模板对象 {@link JetTemplate }
|
||||
* @return JetbrickTemplate
|
||||
*/
|
||||
public static JetbrickTemplate wrap(JetTemplate jetTemplate) {
|
||||
return (null == jetTemplate) ? null : new JetbrickTemplate(jetTemplate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param jetTemplate Jetbrick的模板对象 {@link JetTemplate }
|
||||
*/
|
||||
public JetbrickTemplate(JetTemplate jetTemplate) {
|
||||
this.rawTemplate = jetTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(Map<?, ?> bindingMap, Writer writer) {
|
||||
final Map<String, Object> map = Convert.convert(new TypeReference<Map<String, Object>>() {}, bindingMap);
|
||||
rawTemplate.render(map, writer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(Map<?, ?> bindingMap, OutputStream out) {
|
||||
final Map<String, Object> map = Convert.convert(new TypeReference<Map<String, Object>>() {}, bindingMap);
|
||||
rawTemplate.render(map, out);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
package cn.hutool.extra.template.engine.jetbrick.loader;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import jetbrick.io.resource.AbstractResource;
|
||||
import jetbrick.io.resource.Resource;
|
||||
import jetbrick.io.resource.ResourceNotFoundException;
|
||||
import jetbrick.template.loader.AbstractResourceLoader;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* 字符串模板加载器
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.7.21
|
||||
*/
|
||||
public class StringResourceLoader extends AbstractResourceLoader {
|
||||
|
||||
private Charset charset;
|
||||
|
||||
@Override
|
||||
public Resource load(String name) {
|
||||
return new StringTemplateResource(name, charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置编码
|
||||
* @param charset 编码
|
||||
*/
|
||||
public void setCharset(Charset charset){
|
||||
this.charset = charset;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字符串模板
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
static class StringTemplateResource extends AbstractResource {
|
||||
|
||||
private final String content;
|
||||
private final Charset charset;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
* @param content 模板内容
|
||||
* @param charset 编码
|
||||
*/
|
||||
public StringTemplateResource(String content, Charset charset){
|
||||
this.content = content;
|
||||
this.charset = charset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream openStream() throws ResourceNotFoundException {
|
||||
return IoUtil.toStream(content, charset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getURL() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exist() {
|
||||
return StrUtil.isEmpty(content);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long lastModified() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
/**
|
||||
* jetbrick-template实现,模板引擎介绍见:https://github.com/subchen/jetbrick-template-2x
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
package cn.hutool.extra.template.engine.jetbrick;
|
@ -5,3 +5,4 @@ cn.hutool.extra.template.engine.rythm.RythmEngine
|
||||
cn.hutool.extra.template.engine.enjoy.EnjoyEngine
|
||||
cn.hutool.extra.template.engine.thymeleaf.ThymeleafEngine
|
||||
cn.hutool.extra.template.engine.wit.WitEngine
|
||||
cn.hutool.extra.template.engine.jetbrick.JetbrickEngine
|
||||
|
@ -94,4 +94,6 @@ public class QrCodeUtilTest {
|
||||
final BufferedImage image = QrCodeUtil.generate("content111", BarcodeFormat.PDF_417, QrConfig.create());
|
||||
Assert.assertNotNull(image);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
32
hutool-extra/src/test/java/cn/hutool/extra/template/JetbrickTest.java
Executable file
32
hutool-extra/src/test/java/cn/hutool/extra/template/JetbrickTest.java
Executable file
@ -0,0 +1,32 @@
|
||||
package cn.hutool.extra.template;
|
||||
|
||||
import cn.hutool.core.lang.Dict;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.template.engine.jetbrick.JetbrickEngine;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class JetbrickTest {
|
||||
|
||||
@Test
|
||||
public void jetbrickEngineTest() {
|
||||
//classpath模板
|
||||
TemplateConfig config = new TemplateConfig("templates", TemplateConfig.ResourceMode.CLASSPATH)
|
||||
.setCustomEngine(JetbrickEngine.class);
|
||||
TemplateEngine engine = TemplateUtil.createEngine(config);
|
||||
Template template = engine.getTemplate("jetbrick_test.jetx");
|
||||
String result = template.render(Dict.create().set("name", "hutool"));
|
||||
Assert.assertEquals("你好,hutool", StrUtil.trim(result));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jetbrickEngineWithStringTest() {
|
||||
// 字符串模板
|
||||
TemplateConfig config = new TemplateConfig("templates", TemplateConfig.ResourceMode.STRING)
|
||||
.setCustomEngine(JetbrickEngine.class);
|
||||
TemplateEngine engine = TemplateUtil.createEngine(config);
|
||||
Template template = engine.getTemplate("hello,${name}");
|
||||
String result = template.render(Dict.create().set("name", "hutool"));
|
||||
Assert.assertEquals("hello,hutool", StrUtil.trim(result));
|
||||
}
|
||||
}
|
@ -156,7 +156,7 @@ public class TemplateUtilTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void WitEngineTest() {
|
||||
public void witEngineTest() {
|
||||
//classpath模板
|
||||
TemplateConfig config = new TemplateConfig("templates", ResourceMode.CLASSPATH)
|
||||
.setCustomEngine(WitEngine.class);
|
||||
|
1
hutool-extra/src/test/resources/templates/jetbrick_test.jetx
Executable file
1
hutool-extra/src/test/resources/templates/jetbrick_test.jetx
Executable file
@ -0,0 +1 @@
|
||||
你好,${name}
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.20</version>
|
||||
<version>5.7.21</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-http</artifactId>
|
||||
|
@ -4,6 +4,7 @@ import cn.hutool.core.util.CharsetUtil;
|
||||
import com.sun.net.httpserver.HttpContext;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
@ -12,7 +13,7 @@ import java.nio.charset.Charset;
|
||||
* @author looly
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public class HttpServerBase {
|
||||
public class HttpServerBase implements Closeable {
|
||||
|
||||
final static Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8;
|
||||
|
||||
@ -45,4 +46,12 @@ public class HttpServerBase {
|
||||
public HttpContext getHttpContext() {
|
||||
return getHttpExchange().getHttpContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用{@link HttpExchange#close()},关闭请求流和响应流
|
||||
*/
|
||||
@Override
|
||||
public void close() {
|
||||
this.httpExchange.close();
|
||||
}
|
||||
}
|
||||
|
@ -33,5 +33,6 @@ public class ActionHandler implements HttpHandler {
|
||||
new HttpServerRequest(httpExchange),
|
||||
new HttpServerResponse(httpExchange)
|
||||
);
|
||||
httpExchange.close();
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user