Merge branch 'dromara:v5-master' into v5-master

This commit is contained in:
gongxuanzhang
2022-03-09 17:03:38 +08:00
committed by GitHub
200 changed files with 5394 additions and 684 deletions

View File

@@ -2,7 +2,67 @@
# 🚀Changelog
-------------------------------------------------------------------------------------------------------------
# 5.7.22 (2022-03-01)
### 🐣新特性
* 【poi 】 ExcelUtil.readBySax增加对POI-5.2.0的兼容性issue#I4TJF4@gitee
* 【extra 】 Ftp增加构造issue#I4TKXP@gitee
* 【core 】 GenericBuilder支持Map构建pr#540@Github
* 【json 】 新增TemporalAccessorSerializer
* 【core 】 使多个xxxBuilder实现Builder接口扩展CheckedUtilpr#545@Gitee
* 【core 】 CheckedUtil删除第二个参数为RuntimeException的方法
* 【core 】 FileUtil增加getTotalLines方法
* 【db 】 MetaUtil增加getTableMeta重载issue#2157@Github
* 【http 】 增加HttpGlobalConfig.setDecodeUrlissue#I4U8YQ@Gitee
* 【core 】 增加Base58pr#2162@Github
* 【core 】 增加AntPathMatcherissue#I4T7K5@Gitee
* 【core 】 StrJoiner修改toString策略调用不再修改Appendable
* 【core 】 StrJoiner增加length和merge方法
* 【core 】 CRC16增加getHexValue方法issue#I4VO3U@Gitee
### 🐞Bug修复
* 【cache 】 修复ReentrantCache.toString方法线程不安全问题issue#2140@Github
* 【core 】 修复SystemPropsUtil.getInt返回long问题pr#546@Gitee
* 【crypto 】 修复SM2.getD前导0问题pr#2149@Github
* 【core 】 修复ChineseDate在1970年之前农历差一天问题issue#I4UTPK@Gitee
* 【core 】 修复CoordinateUtil精准问题及转换bugpr#551@Gitee
* 【json 】 修复JSONObject解析XML后没有返回的bugissue#2160@Github
* 【extra 】 修复GanymedUtil错误信息读取位置问题issue#I4VDZ2@Gitee
-------------------------------------------------------------------------------------------------------------
# 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.weekOfYearissue#I4RWXC@Gitee
* 【core 】 Month增加toJdkMonth、getValueBaseOne
* 【core 】 CsvWriter修改规则去除末尾多余换行符issue#I4RSQY@Gitee
* 【core 】 DateUtil增加rangeFunc和rangeConsumeissue#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农历获取正月出现数组越界BUGissue#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没有关闭问题
* 【http 】 修复Action中HttpExchange没有关闭问题
-------------------------------------------------------------------------------------------------------------
# 5.7.20 (2022-01-20)
### 🐣新特性

View File

@@ -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.22</version>
</dependency>
```
### 🍐Gradle
```
implementation 'cn.hutool:hutool-all:5.7.20'
implementation 'cn.hutool:hutool-all:5.7.22'
```
## 📥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.22/)
> 🔔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.

View File

@@ -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.22</version>
</dependency>
```
### 🍐Gradle
```
implementation 'cn.hutool:hutool-all:5.7.20'
implementation 'cn.hutool:hutool-all:5.7.22'
```
### 📥下载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.22/)
> 🔔️注意
> Hutool 5.x支持JDK8+对Android平台没有测试不能保证所有工具类或工具方法可用。

View File

@@ -7,5 +7,5 @@ echo ' / /_/ // / / // __// __ \ / __ \ / / '
echo ' / __ // /_/ // /_ / /_/ // /_/ // / '
echo '/_/ /_/ \____/ \__/ \____/ \____//_/ '
echo ''
echo '-----------http://hutool.cn/------------'
echo '-----------https://hutool.cn/-----------'
echo '========================================'

View File

@@ -1 +1 @@
5.7.20
5.7.22

View File

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

View File

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

View File

@@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.7.20</version>
<version>5.7.22</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.16</spring.version>
</properties>
<dependencies>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -18,7 +18,7 @@ public abstract class ReentrantCache<K, V> extends AbstractCache<K, V> {
private static final long serialVersionUID = 1L;
// 一些特殊缓存例如使用了LinkedHashMap的缓存由于get方法也会改变Map的结构导致无法使用读写锁
// 最优的解决方案是使用Guava的ConcurrentLinkedHashMap此处使用简化的互斥锁
// TODO 最优的解决方案是使用Guava的ConcurrentLinkedHashMap此处使用简化的互斥锁
protected final ReentrantLock lock = new ReentrantLock();
@Override
@@ -115,6 +115,16 @@ public abstract class ReentrantCache<K, V> extends AbstractCache<K, V> {
}
}
@Override
public String toString() {
lock.lock();
try {
return super.toString();
} finally {
lock.unlock();
}
}
/**
* 移除key对应的对象
*

View File

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

View File

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

View File

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

View File

@@ -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对象
*

View File

@@ -0,0 +1,234 @@
package cn.hutool.core.builder;
import cn.hutool.core.lang.func.Consumer3;
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(() -&gt; 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> 还可能这样构建Map对象</p>
* {@code
* HashMap<String, String> colorMap = GenericBuilder
* .of(HashMap<String,String>::new)
* .with(Map::put, "red", "#FF0000")
* .with(Map::put, "yellow", "#FFFF00")
* .with(Map::put, "blue", "#0000FF")
* .build();
* }
*
* <p>注意本工具类支持调用的构造方法的参数数量不超过5个一般方法的参数数量不超过2个更多的参数不利于阅读和维护。</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 consumer 1参数Consumer
* @param p1 参数一
* @param <P1> 参数一类型
* @return GenericBuilder对象
*/
public <P1> GenericBuilder<T> with(BiConsumer<T, P1> consumer, P1 p1) {
modifiers.add(instant -> consumer.accept(instant, p1));
return this;
}
/**
* 调用2参数方法
*
* @param consumer 2参数Consumer
* @param p1 参数一
* @param p2 参数二
* @param <P1> 参数一类型
* @param <P2> 参数二类型
* @return GenericBuilder对象
*/
public <P1, P2> GenericBuilder<T> with(Consumer3<T, P1, P2> consumer, P1 p1, P2 p2) {
modifiers.add(instant -> consumer.accept(instant, p1, p2));
return this;
}
/**
* 构建
*
* @return 目标对象
*/
@Override
public T build() {
T value = instant.get();
modifiers.forEach(modifier -> modifier.accept(value));
modifiers.clear();
return value;
}
}

View File

@@ -0,0 +1,152 @@
package cn.hutool.core.codec;
import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.exceptions.ValidateException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
/**
* Base58工具类提供Base58的编码和解码方案<br>
* 参考: https://github.com/Anujraval24/Base58Encoding<br>
* 规范见https://en.bitcoin.it/wiki/Base58Check_encoding
*
* @author lin looly
* @since 5.7.22
*/
public class Base58 {
private static final int CHECKSUM_SIZE = 4;
// -------------------------------------------------------------------- encode
/**
* Base58编码<br>
* 包含版本位和校验位
*
* @param version 编码版本,{@code null}表示不包含版本位
* @param data 被编码的数组,添加校验和。
* @return 编码后的字符串
*/
public static String encodeChecked(Integer version, byte[] data) {
return encode(addChecksum(version, data));
}
/**
* Base58编码
*
* @param data 被编码的数据,不带校验和。
* @return 编码后的字符串
*/
public static String encode(byte[] data) {
return Base58Codec.INSTANCE.encode(data);
}
// -------------------------------------------------------------------- decode
/**
* Base58解码<br>
* 解码包含标志位验证和版本呢位去除
*
* @param encoded 被解码的base58字符串
* @return 解码后的bytes
* @throws ValidateException 标志位验证错误抛出此异常
*/
public static byte[] decodeChecked(CharSequence encoded) throws ValidateException {
try {
return decodeChecked(encoded, true);
} catch (ValidateException ignore) {
return decodeChecked(encoded, false);
}
}
/**
* Base58解码<br>
* 解码包含标志位验证和版本呢位去除
*
* @param encoded 被解码的base58字符串
* @param withVersion 是否包含版本位
* @return 解码后的bytes
* @throws ValidateException 标志位验证错误抛出此异常
*/
public static byte[] decodeChecked(CharSequence encoded, boolean withVersion) throws ValidateException {
byte[] valueWithChecksum = decode(encoded);
return verifyAndRemoveChecksum(valueWithChecksum, withVersion);
}
/**
* Base58解码
*
* @param encoded 被编码的base58字符串
* @return 解码后的bytes
*/
public static byte[] decode(CharSequence encoded) {
return Base58Codec.INSTANCE.decode(encoded);
}
/**
* 验证并去除验证位和版本位
*
* @param data 编码的数据
* @param withVersion 是否包含版本位
* @return 载荷数据
*/
private static byte[] verifyAndRemoveChecksum(byte[] data, boolean withVersion) {
final byte[] payload = Arrays.copyOfRange(data, withVersion ? 1 : 0, data.length - CHECKSUM_SIZE);
final byte[] checksum = Arrays.copyOfRange(data, data.length - CHECKSUM_SIZE, data.length);
final byte[] expectedChecksum = checksum(payload);
if (false == Arrays.equals(checksum, expectedChecksum)) {
throw new ValidateException("Base58 checksum is invalid");
}
return payload;
}
/**
* 数据 + 校验码
*
* @param version 版本,{@code null}表示不添加版本位
* @param payload Base58数据不含校验码
* @return Base58数据
*/
private static byte[] addChecksum(Integer version, byte[] payload) {
final byte[] addressBytes;
if (null != version) {
addressBytes = new byte[1 + payload.length + CHECKSUM_SIZE];
addressBytes[0] = (byte) version.intValue();
System.arraycopy(payload, 0, addressBytes, 1, payload.length);
} else {
addressBytes = new byte[payload.length + CHECKSUM_SIZE];
System.arraycopy(payload, 0, addressBytes, 0, payload.length);
}
final byte[] checksum = checksum(payload);
System.arraycopy(checksum, 0, addressBytes, addressBytes.length - CHECKSUM_SIZE, CHECKSUM_SIZE);
return addressBytes;
}
/**
* 获取校验码<br>
* 计算规则为对数据进行两次sha256计算然后取{@link #CHECKSUM_SIZE}长度
*
* @param data 数据
* @return 校验码
*/
private static byte[] checksum(byte[] data) {
byte[] hash = hash256(hash256(data));
return Arrays.copyOfRange(hash, 0, CHECKSUM_SIZE);
}
/**
* 计算数据的SHA-256值
*
* @param data 数据
* @return sha-256值
*/
private static byte[] hash256(byte[] data) {
try {
return MessageDigest.getInstance("SHA-256").digest(data);
} catch (NoSuchAlgorithmException e) {
throw new UtilException(e);
}
}
}

View File

@@ -0,0 +1,142 @@
package cn.hutool.core.codec;
import cn.hutool.core.util.StrUtil;
import java.util.Arrays;
/**
* Base58编码器<br>
* 此编码器不包括校验码、版本等信息
*
* @author lin looly
* @since 5.7.22
*/
public class Base58Codec implements Encoder<byte[], String>, Decoder<CharSequence, byte[]> {
public static Base58Codec INSTANCE = new Base58Codec();
private final char[] alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray();
private final char ENCODED_ZERO = alphabet[0];
private final int[] lookup = initLookup();
/**
* Base58编码
*
* @param data 被编码的数据,不带校验和。
* @return 编码后的字符串
*/
@Override
public String encode(byte[] data) {
if (null == data) {
return null;
}
if (data.length == 0) {
return StrUtil.EMPTY;
}
// 计算开头0的个数
int zeroCount = 0;
while (zeroCount < data.length && data[zeroCount] == 0) {
++zeroCount;
}
// 将256位编码转换为58位编码
data = Arrays.copyOf(data, data.length); // since we modify it in-place
final char[] encoded = new char[data.length * 2]; // upper bound
int outputStart = encoded.length;
for (int inputStart = zeroCount; inputStart < data.length; ) {
encoded[--outputStart] = alphabet[divmod(data, inputStart, 256, 58)];
if (data[inputStart] == 0) {
++inputStart; // optimization - skip leading zeros
}
}
// Preserve exactly as many leading encoded zeros in output as there were leading zeros in input.
while (outputStart < encoded.length && encoded[outputStart] == ENCODED_ZERO) {
++outputStart;
}
while (--zeroCount >= 0) {
encoded[--outputStart] = ENCODED_ZERO;
}
// Return encoded string (including encoded leading zeros).
return new String(encoded, outputStart, encoded.length - outputStart);
}
/**
* 解码给定的Base58字符串
*
* @param encoded Base58编码字符串
* @return 解码后的bytes
* @throws IllegalArgumentException 非标准Base58字符串
*/
@Override
public byte[] decode(CharSequence encoded) throws IllegalArgumentException {
if (encoded.length() == 0) {
return new byte[0];
}
// Convert the base58-encoded ASCII chars to a base58 byte sequence (base58 digits).
final byte[] input58 = new byte[encoded.length()];
for (int i = 0; i < encoded.length(); ++i) {
char c = encoded.charAt(i);
int digit = c < 128 ? lookup[c] : -1;
if (digit < 0) {
throw new IllegalArgumentException(StrUtil.format("Invalid char '{}' at [{}]", c, i));
}
input58[i] = (byte) digit;
}
// Count leading zeros.
int zeros = 0;
while (zeros < input58.length && input58[zeros] == 0) {
++zeros;
}
// Convert base-58 digits to base-256 digits.
byte[] decoded = new byte[encoded.length()];
int outputStart = decoded.length;
for (int inputStart = zeros; inputStart < input58.length; ) {
decoded[--outputStart] = divmod(input58, inputStart, 58, 256);
if (input58[inputStart] == 0) {
++inputStart; // optimization - skip leading zeros
}
}
// Ignore extra leading zeroes that were added during the calculation.
while (outputStart < decoded.length && decoded[outputStart] == 0) {
++outputStart;
}
// Return decoded data (including original number of leading zeros).
return Arrays.copyOfRange(decoded, outputStart - zeros, decoded.length);
}
/**
* 初始化字符序号查找表
*
* @return 字符序号查找表
*/
private int[] initLookup() {
final int[] lookup = new int['z' + 1];
Arrays.fill(lookup, -1);
for (int i = 0; i < alphabet.length; i++)
lookup[alphabet[i]] = i;
return lookup;
}
/**
* Divides a number, represented as an array of bytes each containing a single digit
* in the specified base, by the given divisor. The given number is modified in-place
* to contain the quotient, and the return value is the remainder.
*
* @param number the number to divide
* @param firstDigit the index within the array of the first non-zero digit
* (this is used for optimization by skipping the leading zeros)
* @param base the base in which the number's digits are represented (up to 256)
* @param divisor the number to divide by (up to 256)
* @return the remainder of the division operation
*/
private static byte divmod(byte[] number, int firstDigit, int base, int divisor) {
// this is just long division which accounts for the base of the input digits
int remainder = 0;
for (int i = firstDigit; i < number.length; i++) {
int digit = (int) number[i] & 0xFF;
int temp = remainder * base + digit;
number[i] = (byte) (temp / divisor);
remainder = temp % divisor;
}
return (byte) remainder;
}
}

View File

@@ -12,7 +12,7 @@ import java.io.Serializable;
* @author Looly, Sebastian Ruhleder, sebastian@seruco.io
* @since 4.5.9
*/
public class Base62Codec implements Serializable{
public class Base62Codec implements Encoder<byte[], byte[]>, Decoder<byte[], byte[]>, Serializable{
private static final long serialVersionUID = 1L;
private static final int STANDARD_BASE = 256;
@@ -86,6 +86,7 @@ public class Base62Codec implements Serializable{
* @param message 被编码的消息
* @return Base62内容
*/
@Override
public byte[] encode(byte[] message) {
final byte[] indices = convert(message, STANDARD_BASE, TARGET_BASE);
return translate(indices, alphabet);
@@ -97,6 +98,7 @@ public class Base62Codec implements Serializable{
* @param encoded Base62内容
* @return 消息
*/
@Override
public byte[] decode(byte[] encoded) {
final byte[] prepared = translate(encoded, lookup);
return convert(prepared, TARGET_BASE, STANDARD_BASE);

View File

@@ -18,15 +18,6 @@ public class Base64Decoder {
private static final byte PADDING = -2;
/** Base64解码表共128位-1表示非base64字符-2表示padding */
// private static final byte[] DECODE_TABLE2 = {
// -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
// -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
// -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
// 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1,
// -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
// 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
// -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
// 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 };
private static final byte[] DECODE_TABLE = {
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00-0f

View File

@@ -11,7 +11,7 @@ import java.nio.charset.Charset;
* @author looly
* @since 3.2.0
*/
public class Base64Encoder {
public class Base64Encoder{
private static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8;
/** 标准编码表 */

View File

@@ -0,0 +1,20 @@
package cn.hutool.core.codec;
/**
* 解码接口
*
* @param <T> 被解码的数据类型
* @param <R> 解码后的数据类型
* @author looly
* @since 5.7.22
*/
public interface Decoder<T, R> {
/**
* 执行解码
*
* @param data 被解码的数据
* @return 解码后的数据
*/
R decode(T data);
}

View File

@@ -0,0 +1,20 @@
package cn.hutool.core.codec;
/**
* 编码接口
*
* @param <T> 被编码的数据类型
* @param <R> 编码后的数据类型
* @author looly
* @since 5.7.22
*/
public interface Encoder<T, R> {
/**
* 执行编码
*
* @param encoded 被编码的数据
* @return 编码后的数据
*/
R encode(T encoded);
}

View File

@@ -600,18 +600,18 @@ public class IterUtil {
/**
* 返回{@link Iterator}中第一个匹配规则的值
*
* @param <T> 数组元素类型
* @param iterator {@link Iterator}
* @param matcher 匹配接口,实现此接口自定义匹配规则
* @param <T> 数组元素类型
* @param iterator {@link Iterator}
* @param matcher 匹配接口,实现此接口自定义匹配规则
* @return 匹配元素,如果不存在匹配元素或{@link Iterator}为空,返回 {@code null}
* @since 5.7.5
*/
public static <T> T firstMatch(Iterator<T> iterator, Matcher<T> matcher) {
Assert.notNull(matcher, "Matcher must be not null !");
if (null != iterator) {
while(iterator.hasNext()){
while (iterator.hasNext()) {
final T next = iterator.next();
if(matcher.match(next)){
if (matcher.match(next)) {
return next;
}
}
@@ -718,7 +718,7 @@ public class IterUtil {
*
* @param <E> 集合元素类型
* @param iter 集合
* @param filter 过滤器接口
* @param filter 过滤器接口,删除{@link Filter#accept(Object)}为{@code false}的元素
* @return 编辑后的集合
* @since 4.6.5
*/
@@ -735,6 +735,29 @@ public class IterUtil {
return iter;
}
/**
* 过滤{@link Iterator}并将过滤后满足条件的元素添加到List中
*
* @param <E> 元素类型
* @param iter {@link Iterator}
* @param filter 过滤器,保留{@link Filter#accept(Object)}为{@code true}的元素
* @return ArrayList
* @since 5.7.22
*/
public static <E> List<E> filterToList(Iterator<E> iter, Filter<E> filter) {
final List<E> result = new ArrayList<>();
if (null != iter) {
E ele;
while (iter.hasNext()) {
ele = iter.next();
if (null == filter || filter.accept(ele)) {
result.add(ele);
}
}
}
return result;
}
/**
* Iterator转换为Map转换规则为<br>
* 按照keyFunc函数规则根据元素对象生成Key元素作为值

View File

@@ -608,7 +608,7 @@ public class ListUtil {
public static <T> void swapTo(List<T> list, T element, Integer targetIndex) {
if (CollUtil.isNotEmpty(list)) {
final int index = list.indexOf(element);
if (index > 0) {
if (index >= 0) {
Collections.swap(list, index, targetIndex);
}
}
@@ -627,7 +627,7 @@ public class ListUtil {
public static <T> void swapElement(List<T> list, T element, T targetElement) {
if (CollUtil.isNotEmpty(list)) {
final int targetIndex = list.indexOf(targetElement);
if (targetIndex > 0) {
if (targetIndex >= 0) {
swapTo(list, element, targetIndex);
}
}

View File

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

View File

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

View File

@@ -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以下的数字时使用"十一"而非"一十一"。

View File

@@ -93,7 +93,7 @@ public class TemporalAccessorConverter extends AbstractConverter<TemporalAccesso
} else if (value instanceof Date) {
final DateTime dateTime = DateUtil.date((Date) value);
return parseFromInstant(dateTime.toInstant(), dateTime.getZoneId());
}else if (value instanceof Calendar) {
} else if (value instanceof Calendar) {
final Calendar calendar = (Calendar) value;
return parseFromInstant(calendar.toInstant(), calendar.getTimeZone().toZoneId());
} else {
@@ -108,7 +108,7 @@ public class TemporalAccessorConverter extends AbstractConverter<TemporalAccesso
* @return 日期对象
*/
private TemporalAccessor parseFromCharSequence(CharSequence value) {
if(StrUtil.isBlank(value)){
if (StrUtil.isBlank(value)) {
return null;
}
@@ -144,13 +144,13 @@ public class TemporalAccessorConverter extends AbstractConverter<TemporalAccesso
*/
private TemporalAccessor parseFromTemporalAccessor(TemporalAccessor temporalAccessor) {
TemporalAccessor result = null;
if(temporalAccessor instanceof LocalDateTime){
if (temporalAccessor instanceof LocalDateTime) {
result = parseFromLocalDateTime((LocalDateTime) temporalAccessor);
} else if(temporalAccessor instanceof ZonedDateTime){
} else if (temporalAccessor instanceof ZonedDateTime) {
result = parseFromZonedDateTime((ZonedDateTime) temporalAccessor);
}
if(null == result){
if (null == result) {
result = parseFromInstant(DateUtil.toInstant(temporalAccessor), null);
}
@@ -164,22 +164,22 @@ public class TemporalAccessorConverter extends AbstractConverter<TemporalAccesso
* @return java.time中的对象
*/
private TemporalAccessor parseFromLocalDateTime(LocalDateTime localDateTime) {
if(Instant.class.equals(this.targetType)){
if (Instant.class.equals(this.targetType)) {
return DateUtil.toInstant(localDateTime);
}
if(LocalDate.class.equals(this.targetType)){
if (LocalDate.class.equals(this.targetType)) {
return localDateTime.toLocalDate();
}
if(LocalTime.class.equals(this.targetType)){
if (LocalTime.class.equals(this.targetType)) {
return localDateTime.toLocalTime();
}
if(ZonedDateTime.class.equals(this.targetType)){
if (ZonedDateTime.class.equals(this.targetType)) {
return localDateTime.atZone(ZoneId.systemDefault());
}
if(OffsetDateTime.class.equals(this.targetType)){
if (OffsetDateTime.class.equals(this.targetType)) {
return localDateTime.atZone(ZoneId.systemDefault()).toOffsetDateTime();
}
if(OffsetTime.class.equals(this.targetType)){
if (OffsetTime.class.equals(this.targetType)) {
return localDateTime.atZone(ZoneId.systemDefault()).toOffsetDateTime().toOffsetTime();
}
@@ -193,22 +193,22 @@ public class TemporalAccessorConverter extends AbstractConverter<TemporalAccesso
* @return java.time中的对象
*/
private TemporalAccessor parseFromZonedDateTime(ZonedDateTime zonedDateTime) {
if(Instant.class.equals(this.targetType)){
if (Instant.class.equals(this.targetType)) {
return DateUtil.toInstant(zonedDateTime);
}
if(LocalDateTime.class.equals(this.targetType)){
if (LocalDateTime.class.equals(this.targetType)) {
return zonedDateTime.toLocalDateTime();
}
if(LocalDate.class.equals(this.targetType)){
if (LocalDate.class.equals(this.targetType)) {
return zonedDateTime.toLocalDate();
}
if(LocalTime.class.equals(this.targetType)){
if (LocalTime.class.equals(this.targetType)) {
return zonedDateTime.toLocalTime();
}
if(OffsetDateTime.class.equals(this.targetType)){
if (OffsetDateTime.class.equals(this.targetType)) {
return zonedDateTime.toOffsetDateTime();
}
if(OffsetTime.class.equals(this.targetType)){
if (OffsetTime.class.equals(this.targetType)) {
return zonedDateTime.toOffsetDateTime().toOffsetTime();
}
@@ -219,11 +219,11 @@ public class TemporalAccessorConverter extends AbstractConverter<TemporalAccesso
* 将TemporalAccessor型时间戳转换为java.time中的对象
*
* @param instant {@link Instant}对象
* @param zoneId 时区IDnull表示当前系统默认的时区
* @param zoneId 时区IDnull表示当前系统默认的时区
* @return java.time中的对象
*/
private TemporalAccessor parseFromInstant(Instant instant, ZoneId zoneId) {
if(Instant.class.equals(this.targetType)){
if (Instant.class.equals(this.targetType)) {
return instant;
}

View File

@@ -50,13 +50,25 @@ public class CalendarUtil {
}
/**
* 转换为Calendar对象
* 转换为Calendar对象,使用当前默认时区
*
* @param millis 时间戳
* @return Calendar对象
*/
public static Calendar calendar(long millis) {
final Calendar cal = Calendar.getInstance();
return calendar(millis, TimeZone.getDefault());
}
/**
* 转换为Calendar对象
*
* @param millis 时间戳
* @param timeZone 时区
* @return Calendar对象
* @since 5.7.22
*/
public static Calendar calendar(long millis, TimeZone timeZone) {
final Calendar cal = Calendar.getInstance(timeZone);
cal.setTimeInMillis(millis);
return cal;
}
@@ -132,7 +144,7 @@ public class CalendarUtil {
}
/**
* 获取秒级别的开始时间,即忽略毫秒部分
* 修改秒级别的开始时间,即忽略毫秒部分
*
* @param calendar 日期 {@link Calendar}
* @return {@link Calendar}
@@ -143,7 +155,7 @@ public class CalendarUtil {
}
/**
* 获取秒级别的结束时间即毫秒设置为999
* 修改秒级别的结束时间即毫秒设置为999
*
* @param calendar 日期 {@link Calendar}
* @return {@link Calendar}
@@ -154,7 +166,7 @@ public class CalendarUtil {
}
/**
* 获取某小时的开始时间
* 修改某小时的开始时间
*
* @param calendar 日期 {@link Calendar}
* @return {@link Calendar}
@@ -164,7 +176,7 @@ public class CalendarUtil {
}
/**
* 获取某小时的结束时间
* 修改某小时的结束时间
*
* @param calendar 日期 {@link Calendar}
* @return {@link Calendar}
@@ -174,7 +186,7 @@ public class CalendarUtil {
}
/**
* 获取某分钟的开始时间
* 修改某分钟的开始时间
*
* @param calendar 日期 {@link Calendar}
* @return {@link Calendar}
@@ -184,7 +196,7 @@ public class CalendarUtil {
}
/**
* 获取某分钟的结束时间
* 修改某分钟的结束时间
*
* @param calendar 日期 {@link Calendar}
* @return {@link Calendar}
@@ -194,7 +206,7 @@ public class CalendarUtil {
}
/**
* 获取某天的开始时间
* 修改某天的开始时间
*
* @param calendar 日期 {@link Calendar}
* @return {@link Calendar}
@@ -204,7 +216,7 @@ public class CalendarUtil {
}
/**
* 获取某天的结束时间
* 修改某天的结束时间
*
* @param calendar 日期 {@link Calendar}
* @return {@link Calendar}
@@ -214,7 +226,7 @@ public class CalendarUtil {
}
/**
* 获取给定日期当前周的开始时间,周一定为一周的开始时间
* 修改给定日期当前周的开始时间,周一定为一周的开始时间
*
* @param calendar 日期 {@link Calendar}
* @return {@link Calendar}
@@ -224,7 +236,7 @@ public class CalendarUtil {
}
/**
* 获取给定日期当前周的开始时间
* 修改给定日期当前周的开始时间
*
* @param calendar 日期 {@link Calendar}
* @param isMondayAsFirstDay 是否周一做为一周的第一天false表示周日做为第一天
@@ -238,7 +250,7 @@ public class CalendarUtil {
}
/**
* 获取某周的结束时间,周日定为一周的结束
* 修改某周的结束时间,周日定为一周的结束
*
* @param calendar 日期 {@link Calendar}
* @return {@link Calendar}
@@ -248,7 +260,7 @@ public class CalendarUtil {
}
/**
* 获取某周的结束时间
* 修改某周的结束时间
*
* @param calendar 日期 {@link Calendar}
* @param isSundayAsLastDay 是否周日做为一周的最后一天false表示周六做为最后一天
@@ -261,7 +273,7 @@ public class CalendarUtil {
}
/**
* 获取某月的开始时间
* 修改某月的开始时间
*
* @param calendar 日期 {@link Calendar}
* @return {@link Calendar}
@@ -271,7 +283,7 @@ public class CalendarUtil {
}
/**
* 获取某月的结束时间
* 修改某月的结束时间
*
* @param calendar 日期 {@link Calendar}
* @return {@link Calendar}
@@ -281,7 +293,7 @@ public class CalendarUtil {
}
/**
* 获取某季度的开始时间
* 修改某季度的开始时间
*
* @param calendar 日期 {@link Calendar}
* @return {@link Calendar}
@@ -313,7 +325,7 @@ public class CalendarUtil {
}
/**
* 获取某年的开始时间
* 修改某年的开始时间
*
* @param calendar 日期 {@link Calendar}
* @return {@link Calendar}
@@ -323,7 +335,7 @@ public class CalendarUtil {
}
/**
* 获取某年的结束时间
* 修改某年的结束时间
*
* @param calendar 日期 {@link Calendar}
* @return {@link Calendar}
@@ -348,6 +360,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);
}
/**
* 比较两个日期是否为同一月
*

View File

@@ -8,6 +8,7 @@ import cn.hutool.core.date.chinese.LunarInfo;
import cn.hutool.core.date.chinese.SolarTerms;
import cn.hutool.core.util.StrUtil;
import java.time.LocalDate;
import java.util.Calendar;
import java.util.Date;
@@ -47,14 +48,24 @@ public class ChineseDate {
* @param date 公历日期
*/
public ChineseDate(Date date) {
this(LocalDateTimeUtil.ofDate(date.toInstant()));
}
/**
* 通过公历日期构造
*
* @param localDate 公历日期
* @since 5.7.22
*/
public ChineseDate(LocalDate localDate) {
// 公历
final DateTime dt = DateUtil.beginOfDay(date);
gyear = dt.year();
gmonthBase1 = dt.month() + 1;
gday = dt.dayOfMonth();
gyear = localDate.getYear();
gmonthBase1 = localDate.getMonthValue();
gday = localDate.getDayOfMonth();
// 求出和1900年1月31日相差的天数
int offset = (int) ((dt.getTime() / DateUnit.DAY.getMillis()) - LunarInfo.BASE_DAY);
int offset = (int) (localDate.toEpochDay() - LunarInfo.BASE_DAY);
// 计算农历年份
// 用offset减去每农历年的天数计算当天是农历第几天offset是当年的第几天
int daysOfYear;
@@ -92,7 +103,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--;

View File

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

View File

@@ -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);
}
/**
* 比较两个日期是否为同一月
*
@@ -1871,19 +1882,87 @@ public class DateUtil extends CalendarUtil {
}
/**
* 创建日期范围生成器
* 俩个时间区间取交集
*
* @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);
}
/**
* 根据步进单位获取起始日期时间和结束日期时间的时间区间集合
*
* @param start 起始日期时间
* @param end 结束日期时间
* @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));
}
/**
* 创建日期范围生成器
* 根据步进单位和步进获取起始日期时间和结束日期时间的时间区间集合
*
* @param start 起始日期时间
* @param end 结束日期时间
@@ -2104,6 +2183,22 @@ public class DateUtil extends CalendarUtil {
}
}
/**
* 检查两个时间段是否有时间重叠<br>
* 重叠指两个时间段是否有交集
*
* @param realStartTime 第一个时间段的开始时间
* @param realEndTime 第一个时间段的结束时间
* @param startTime 第二个时间段的开始时间
* @param endTime 第二个时间段的结束时间
* @return true 表示时间有重合
* @since 5.7.22
*/
public static boolean isOverlap(Date realStartTime, Date realEndTime,
Date startTime, Date endTime) {
return startTime.after(realEndTime) || endTime.before(realStartTime);
}
// ------------------------------------------------------------------------ Private method start
/**

View File

@@ -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;
@@ -181,6 +182,8 @@ public class LocalDateTimeUtil {
if (temporalAccessor instanceof LocalDate) {
return ((LocalDate) temporalAccessor).atStartOfDay();
} else if(temporalAccessor instanceof Instant){
return LocalDateTime.ofInstant((Instant) temporalAccessor, ZoneId.systemDefault());
}
return LocalDateTime.of(
@@ -208,6 +211,8 @@ public class LocalDateTimeUtil {
if (temporalAccessor instanceof LocalDateTime) {
return ((LocalDateTime) temporalAccessor).toLocalDate();
} else if(temporalAccessor instanceof Instant){
return of(temporalAccessor).toLocalDate();
}
return LocalDate.of(
@@ -562,4 +567,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());
}
}

View File

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

View File

@@ -26,11 +26,12 @@ import java.time.temporal.UnsupportedTemporalTypeException;
public class TemporalAccessorUtil extends TemporalUtil{
/**
* 安全获取时间的某个属性,属性不存在返回0
* 安全获取时间的某个属性,属性不存在返回最小值一般为0<br>
* 注意请谨慎使用此方法,某些{@link TemporalAccessor#isSupported(TemporalField)}为{@code false}的方法返回最小值
*
* @param temporalAccessor 需要获取的时间对象
* @param field 需要获取的属性
* @return 时间的值,如果无法获取则默认为 0
* @return 时间的值,如果无法获取则获取最小值,一般为0
*/
public static int get(TemporalAccessor temporalAccessor, TemporalField field) {
if (temporalAccessor.isSupported(field)) {

View File

@@ -75,7 +75,7 @@ public class GanZhi {
public static String getGanzhiOfDay(int year, int month, int day) {
// 与1970-01-01相差天数不包括当天
final long days = LocalDate.of(year, month, day).toEpochDay() - 1;
//1899-12-21是农历1899年腊月甲子日 40相差1900-01-31有40
return cyclicalm((int) (days - LunarInfo.BASE_DAY + 40));
//1899-12-21是农历1899年腊月甲子日 41相差1900-01-31有41
return cyclicalm((int) (days - LunarInfo.BASE_DAY + 41));
}
}

View File

@@ -1,5 +1,7 @@
package cn.hutool.core.date.chinese;
import java.time.LocalDate;
/**
* 阴历(农历)信息
*
@@ -13,9 +15,9 @@ public class LunarInfo {
*/
public static final int BASE_YEAR = 1900;
/**
* 1900-01-31
* 1900-01-31,农历正月初一
*/
public static final long BASE_DAY = -25538;
public static final long BASE_DAY = LocalDate.of(BASE_YEAR, 1, 31).toEpochDay();
/**
* 此表来自https://github.com/jjonline/calendar.js/blob/master/calendar.js

View File

@@ -51,10 +51,10 @@ 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());
return uncheck(expression, RuntimeException::new);
}
/**
@@ -63,10 +63,10 @@ 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());
return uncheck(expression, RuntimeException::new);
}
/**
@@ -76,10 +76,10 @@ 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());
return uncheck(expression, RuntimeException::new);
}
@@ -89,10 +89,10 @@ 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());
return uncheck(expression, RuntimeException::new);
}
/**
@@ -100,10 +100,10 @@ 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());
return uncheck(expression, RuntimeException::new);
}
/**
@@ -112,110 +112,106 @@ 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());
return uncheck(expression, RuntimeException::new);
}
/**
* 接收一个可以转化成 cn.hutool.core.lang.func.Func的Lambda表达式和一个RuntimeException当执行表达式抛出任何异常的时候都会转化成运行时异常
* 接收一个可以转化成 cn.hutool.core.lang.func.Func的Lambda表达式和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常
* 如此一来代码中就不用显示的try-catch转化成运行时异常
*
* @param expression Lambda表达式
* @param rte 期望抛出的运行时异常
* @param <P> 运行时传入的参数类型
* @param <R> 最终返回的数据类型
* @return cn.hutool.core.lang.func.Func
* @param expression Lambda表达式
* @param rteSupplier 转化运行时异常的表达式
* @param <P> 运行时传入的参数类型
* @param <R> 最终返回的数据类型
* @return {@link FuncRt}
*/
public static <P, R> FuncRt<P, R> uncheck(Func<P, R> expression, RuntimeException rte) {
public static <P, R> FuncRt<P, R> uncheck(Func<P, R> expression, Supplier1<RuntimeException, Exception> rteSupplier) {
Objects.requireNonNull(expression, "expression can not be null");
return t -> {
try {
return expression.call(t);
} catch (Exception e) {
if (rte == null) {
if (rteSupplier == null) {
throw new RuntimeException(e);
} else {
rte.initCause(e);
throw rte;
throw rteSupplier.get(e);
}
}
};
}
/**
* 接收一个可以转化成 cn.hutool.core.lang.func.Func0的Lambda表达式和一个RuntimeException当执行表达式抛出任何异常的时候都会转化成运行时异常
* 接收一个可以转化成 cn.hutool.core.lang.func.Func0的Lambda表达式和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常
* 如此一来代码中就不用显示的try-catch转化成运行时异常
*
* @param expression Lambda表达式
* @param rte 期望抛出的运行时异常
* @param <R> 最终返回的数据类型
* @return cn.hutool.core.lang.func.Func0
* @param expression Lambda表达式
* @param rteSupplier 转化运行时异常的表达式
* @param <R> 最终返回的数据类型
* @return {@link Func0Rt}
*/
public static <R> Func0Rt<R> uncheck(Func0<R> expression, RuntimeException rte) {
public static <R> Func0Rt<R> uncheck(Func0<R> expression, Supplier1<RuntimeException, Exception> rteSupplier) {
Objects.requireNonNull(expression, "expression can not be null");
return () -> {
try {
return expression.call();
} catch (Exception e) {
if (rte == null) {
if (rteSupplier == null) {
throw new RuntimeException(e);
} else {
rte.initCause(e);
throw rte;
throw rteSupplier.get(e);
}
}
};
}
/**
* 接收一个可以转化成 cn.hutool.core.lang.func.Func1的Lambda表达式和一个RuntimeException当执行表达式抛出任何异常的时候都会转化成运行时异常
* 接收一个可以转化成 cn.hutool.core.lang.func.Func1的Lambda表达式和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常
* 如此一来代码中就不用显示的try-catch转化成运行时异常
*
* @param expression Lambda表达式
* @param rte 期望抛出的运行时异常
* @param <P> 运行时传入的参数类型
* @param <R> 最终返回的数据类型
* @return cn.hutool.core.lang.func.Func1
* @param expression Lambda表达式
* @param rteSupplier 转化运行时异常的表达式
* @param <P> 运行时传入的参数类型
* @param <R> 最终返回的数据类型
* @return {@link Func1Rt}
*/
public static <P, R> Func1Rt<P, R> uncheck(Func1<P, R> expression, RuntimeException rte) {
public static <P, R> Func1Rt<P, R> uncheck(Func1<P, R> expression, Supplier1<RuntimeException, Exception> rteSupplier) {
Objects.requireNonNull(expression, "expression can not be null");
return t -> {
try {
return expression.call(t);
} catch (Exception e) {
if (rte == null) {
if (rteSupplier == null) {
throw new RuntimeException(e);
} else {
rte.initCause(e);
throw rte;
throw rteSupplier.get(e);
}
}
};
}
/**
* 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc的Lambda表达式和一个RuntimeException当执行表达式抛出任何异常的时候都会转化成运行时异常
* 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc的Lambda表达式和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常
* 如此一来代码中就不用显示的try-catch转化成运行时异常
*
* @param expression Lambda表达式
* @param rte 期望抛出的运行时异常
* @param <P> 运行时传入的参数类型
* @return cn.hutool.core.lang.func.VoidFunc
* @param expression Lambda表达式
* @param rteSupplier 转化运行时异常的表达式
* @param <P> 运行时传入的参数类型
* @return {@link VoidFuncRt}
*/
public static <P> VoidFuncRt<P> uncheck(VoidFunc<P> expression, RuntimeException rte) {
public static <P> VoidFuncRt<P> uncheck(VoidFunc<P> expression, Supplier1<RuntimeException, Exception> rteSupplier) {
Objects.requireNonNull(expression, "expression can not be null");
return t -> {
try {
expression.call(t);
} catch (Exception e) {
if (rte == null) {
if (rteSupplier == null) {
throw new RuntimeException(e);
} else {
rte.initCause(e);
throw rte;
throw rteSupplier.get(e);
}
}
};
@@ -228,7 +224,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");
@@ -246,27 +242,48 @@ public class CheckedUtil {
};
}
/**
* 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc0的Lambda表达式和一个可以把Exception转化成RuntimeExceptionde的表达式当执行表达式抛出任何异常的时候都会转化成运行时异常
* 如此一来代码中就不用显示的try-catch转化成运行时异常
*
* @param expression Lambda表达式
* @param rteSupplier 转化运行时异常的表达式
* @return {@link VoidFunc0Rt}
*/
public static VoidFunc0Rt uncheck(VoidFunc0 expression, Supplier1<RuntimeException, Exception> rteSupplier) {
Objects.requireNonNull(expression, "expression can not be null");
return () -> {
try {
expression.call();
} catch (Exception e) {
if (rteSupplier == null) {
throw new RuntimeException(e);
} else {
throw rteSupplier.get(e);
}
}
};
}
/**
* 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc1的Lambda表达式和一个RuntimeException当执行表达式抛出任何异常的时候都会转化成运行时异常
* 如此一来代码中就不用显示的try-catch转化成运行时异常
*
* @param expression Lambda表达式
* @param rte 期望抛出的运行时异常
* @param <P> 运行时传入的参数类型
* @return cn.hutool.core.lang.func.VoidFunc1
* @param expression Lambda表达式
* @param rteSupplier 转化运行时异常的表达式
* @param <P> 运行时传入的参数类型
* @return {@link VoidFunc1Rt}
*/
public static <P> VoidFunc1Rt<P> uncheck(VoidFunc1<P> expression, RuntimeException rte) {
public static <P> VoidFunc1Rt<P> uncheck(VoidFunc1<P> expression, Supplier1<RuntimeException, Exception> rteSupplier) {
Objects.requireNonNull(expression, "expression can not be null");
return t -> {
try {
expression.call(t);
} catch (Exception e) {
if (rte == null) {
if (rteSupplier == null) {
throw new RuntimeException(e);
} else {
rte.initCause(e);
throw rte;
throw rteSupplier.get(e);
}
}
};
@@ -274,27 +291,33 @@ public class CheckedUtil {
public interface FuncRt<P, R> extends Func<P, R> {
@SuppressWarnings("unchecked")
@Override
R call(P... parameters) throws RuntimeException;
}
public interface Func0Rt<R> extends Func0<R> {
@Override
R call() throws RuntimeException;
}
public interface Func1Rt<P, R> extends Func1<P, R> {
@Override
R call(P parameter) throws RuntimeException;
}
public interface VoidFuncRt<P> extends VoidFunc<P> {
@SuppressWarnings("unchecked")
@Override
void call(P... parameters) throws RuntimeException;
}
public interface VoidFunc0Rt extends VoidFunc0 {
@Override
void call() throws RuntimeException;
}
public interface VoidFunc1Rt<P> extends VoidFunc1<P> {
@Override
void call(P parameter) throws RuntimeException;
}

View File

@@ -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;
@@ -32,6 +33,7 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.LineNumberReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
@@ -40,7 +42,11 @@ import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.Charset;
import java.nio.file.*;
import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
@@ -526,24 +532,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 {
@@ -551,6 +572,31 @@ public class FileUtil extends PathUtil {
}
}
/**
* 计算文件的总行数<br>
* 读取文件采用系统默认编码,一般乱码不会造成行数错误。
*
* @param file 文件
* @return 该文件总行数
* @since 5.7.22
*/
public static int getTotalLines(File file) {
if (false == isFile(file)) {
throw new IORuntimeException("Input must be a File");
}
try (final LineNumberReader lineNumberReader = new LineNumberReader(new java.io.FileReader(file))) {
// 设置起始为1
lineNumberReader.setLineNumber(1);
// 跳过文件中内容
//noinspection ResultOfMethodCallIgnored
lineNumberReader.skip(Long.MAX_VALUE);
// 获取当前行号
return lineNumberReader.getLineNumber();
} catch (IOException e) {
throw new IORuntimeException(e);
}
}
/**
* 给定文件或目录的最后修改时间是否晚于给定时间
*
@@ -811,7 +857,7 @@ public class FileUtil extends PathUtil {
/**
* 创建文件夹,会递归自动创建其不存在的父文件夹,如果存在直接返回此文件夹<br>
* 此方法不对File对象类型做判断如果File不存在无法判断其类型
* 此方法不对File对象类型做判断如果File不存在无法判断其类型<br>
*
* @param dir 目录
* @return 创建的目录
@@ -821,12 +867,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
@@ -839,6 +921,54 @@ public class FileUtil extends PathUtil {
return createTempFile("hutool", null, dir, true);
}
/**
* 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].tmp。
* 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。
* 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}
* 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。
* 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。
* @return 临时文件
* @throws IORuntimeException IO异常
* @since 5.7.22
*/
public static File createTempFile() throws IORuntimeException {
return createTempFile("hutool", null, null, true);
}
/**
* 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].suffix。
* 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。
* 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}
* 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。
* 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。
* @param suffix 后缀如果null则使用默认.tmp
* @param isReCreat 是否重新创建文件(删掉原来的,创建新的)
* @return 临时文件
* @throws IORuntimeException IO异常
* @since 5.7.22
*/
public static File createTempFile(String suffix, boolean isReCreat) throws IORuntimeException {
return createTempFile("hutool", suffix, null, isReCreat);
}
/**
* 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].suffix。
* 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。
* 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}
* 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。
* 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。
*
* @param prefix 前缀至少3个字符
* @param suffix 后缀如果null则使用默认.tmp
* @param isReCreat 是否重新创建文件(删掉原来的,创建新的)
* @return 临时文件
* @throws IORuntimeException IO异常
* @since 5.7.22
*/
public static File createTempFile(String prefix, String suffix, boolean isReCreat) throws IORuntimeException {
return createTempFile(prefix, suffix, null, isReCreat);
}
/**
* 创建临时文件<br>
* 创建后的文件名为 prefix[Randon].tmp

View File

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

View File

@@ -17,7 +17,7 @@ public class CRC16 implements Checksum, Serializable {
private final CRC16Checksum crc16;
public CRC16(){
public CRC16() {
this(new CRC16IBM());
}
@@ -26,10 +26,31 @@ public class CRC16 implements Checksum, Serializable {
*
* @param crc16Checksum {@link CRC16Checksum} 实现
*/
public CRC16(CRC16Checksum crc16Checksum){
public CRC16(CRC16Checksum crc16Checksum) {
this.crc16 = crc16Checksum;
}
/**
* 获取16进制的CRC16值
*
* @return 16进制的CRC16值
* @since 5.7.22
*/
public String getHexValue() {
return this.crc16.getHexValue();
}
/**
* 获取16进制的CRC16值
*
* @param isPadding 不足4位时是否填充0以满足位数
* @return 16进制的CRC16值4位
* @since 5.7.22
*/
public String getHexValue(boolean isPadding) {
return crc16.getHexValue(isPadding);
}
@Override
public long getValue() {
return crc16.getValue();

View File

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

View File

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

View File

@@ -53,6 +53,17 @@ public interface Resource {
*/
InputStream getStream();
/**
* 检查资源是否变更<br>
* 一般用于文件类资源,检查文件是否被修改过。
*
* @return 是否变更
* @since 5.7.21
*/
default boolean isModified(){
return false;
}
/**
* 将资源内容写出到流,不关闭输出流,但是关闭资源流
*

View File

@@ -1,9 +1,10 @@
package cn.hutool.core.io.resource;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.EnumerationIter;
import cn.hutool.core.collection.IterUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.lang.Filter;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ClassLoaderUtil;
import cn.hutool.core.util.StrUtil;
@@ -21,7 +22,6 @@ import java.util.List;
* Resource资源工具类
*
* @author Looly
*
*/
public class ResourceUtil {
@@ -40,7 +40,7 @@ public class ResourceUtil {
* 读取Classpath下的资源为字符串
*
* @param resource 可以是绝对路径也可以是相对路径相对ClassPath
* @param charset 编码
* @param charset 编码
* @return 资源内容
* @since 3.1.1
*/
@@ -102,7 +102,7 @@ public class ResourceUtil {
* 从ClassPath资源中获取{@link BufferedReader}
*
* @param resource ClassPath资源
* @param charset 编码
* @param charset 编码
* @return {@link InputStream}
* @since 3.1.2
*/
@@ -139,13 +139,24 @@ public class ResourceUtil {
* @return 资源列表
*/
public static List<URL> getResources(String resource) {
final Enumeration<URL> resources;
try {
resources = ClassLoaderUtil.getClassLoader().getResources(resource);
} catch (IOException e) {
throw new IORuntimeException(e);
}
return CollUtil.newArrayList(resources);
return getResources(resource, null);
}
/**
* 获取指定路径下的资源列表<br>
* 路径格式必须为目录格式,用/分隔,例如:
*
* <pre>
* config/a
* spring/xml
* </pre>
*
* @param resource 资源路径
* @param filter 过滤器,用于过滤不需要的资源,{@code null}表示不过滤,保留所有元素
* @return 资源列表
*/
public static List<URL> getResources(String resource, Filter<URL> filter) {
return IterUtil.filterToList(getResourceIter(resource), filter);
}
/**
@@ -174,7 +185,7 @@ public class ResourceUtil {
/**
* 获得资源相对路径对应的URL
*
* @param resource 资源相对路径,{@code null}和""都表示classpath根路径
* @param resource 资源相对路径,{@code null}和""都表示classpath根路径
* @param baseClass 基准Class获得的相对路径相对于此Class所在路径如果为{@code null}则相对ClassPath
* @return {@link URL}
*/
@@ -192,8 +203,8 @@ public class ResourceUtil {
* @since 3.2.1
*/
public static Resource getResourceObj(String path) {
if(StrUtil.isNotBlank(path)) {
if(path.startsWith(URLUtil.FILE_URL_PREFIX) || FileUtil.isAbsolutePath(path)) {
if (StrUtil.isNotBlank(path)) {
if (path.startsWith(URLUtil.FILE_URL_PREFIX) || FileUtil.isAbsolutePath(path)) {
return new FileResource(path);
}
}

View File

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

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

View File

@@ -0,0 +1,23 @@
package cn.hutool.core.lang.func;
/**
* 3参数Consumer
*
* @param <P1> 参数一类型
* @param <P2> 参数二类型
* @param <P3> 参数三类型
* @author TomXin
* @since 5.7.22
*/
@FunctionalInterface
public interface Consumer3<P1, P2, P3> {
/**
* 接收参数方法
*
* @param p1 参数一
* @param p2 参数二
* @param p3 参数三
*/
void accept(P1 p1, P2 p2, P3 p3);
}

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

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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

@@ -1,6 +1,8 @@
package cn.hutool.core.map;
import java.io.Serializable;
import cn.hutool.core.builder.Builder;
import java.util.Map;
import java.util.function.Supplier;
@@ -11,7 +13,7 @@ import java.util.function.Supplier;
* @param <V> Value类型
* @since 3.1.1
*/
public class MapBuilder<K, V> implements Serializable {
public class MapBuilder<K, V> implements Builder<Map<K,V>> {
private static final long serialVersionUID = 1L;
private final Map<K, V> map;
@@ -133,6 +135,7 @@ public class MapBuilder<K, V> implements Serializable {
* @return 创建后的map
* @since 3.3.0
*/
@Override
public Map<K, V> build() {
return map();
}

View File

@@ -15,7 +15,7 @@ import java.security.cert.X509Certificate;
public class DefaultTrustManager extends X509ExtendedTrustManager {
/**
* 默认的全局单例默认信任管理器,默认信任所有客户端和服务端证书
* 默认的全局单例默认信任管理器,默认信任所有客户端和服务端证书
* @since 5.7.8
*/
public static DefaultTrustManager INSTANCE = new DefaultTrustManager();

View File

@@ -1,5 +1,6 @@
package cn.hutool.core.net;
import cn.hutool.core.builder.Builder;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
@@ -20,13 +21,14 @@ import java.security.SecureRandom;
* <li>{@link TrustManager},默认{@link DefaultTrustManager},即信任全部</li>
* <li>{@link SecureRandom}</li>
* </ul>
*
* <p>
* 构建后可获得{@link SSLContext},通过调用{@link SSLContext#getSocketFactory()}获取{@link javax.net.ssl.SSLSocketFactory}
*
* @author Looly
* @since 5.5.2
*/
public class SSLContextBuilder implements SSLProtocols {
public class SSLContextBuilder implements SSLProtocols, Builder<SSLContext> {
private static final long serialVersionUID = 1L;
private String protocol = TLS;
private KeyManager[] keyManagers;
@@ -99,10 +101,21 @@ public class SSLContextBuilder implements SSLProtocols {
* 构建{@link SSLContext}
*
* @return {@link SSLContext}
* @throws NoSuchAlgorithmException 无此算法
* @throws KeyManagementException Key管理异常
*/
public SSLContext build() throws NoSuchAlgorithmException, KeyManagementException {
@Override
public SSLContext build() {
return buildQuietly();
}
/**
* 构建{@link SSLContext}需要处理异常
*
* @return {@link SSLContext}
* @throws NoSuchAlgorithmException 无此算法异常
* @throws KeyManagementException 密钥管理异常
* @since 5.7.22
*/
public SSLContext buildChecked() throws NoSuchAlgorithmException, KeyManagementException {
SSLContext sslContext = SSLContext.getInstance(protocol);
sslContext.init(this.keyManagers, this.trustManagers, this.secureRandom);
return sslContext;
@@ -116,7 +129,7 @@ public class SSLContextBuilder implements SSLProtocols {
*/
public SSLContext buildQuietly() throws IORuntimeException {
try {
return build();
return buildChecked();
} catch (GeneralSecurityException e) {
throw new IORuntimeException(e);
}

View File

@@ -23,7 +23,7 @@ public class SSLUtil {
* @since 5.7.8
*/
public static SSLContext createSSLContext(String protocol) throws IORuntimeException{
return SSLContextBuilder.create().setProtocol(protocol).buildQuietly();
return SSLContextBuilder.create().setProtocol(protocol).build();
}
/**
@@ -55,6 +55,6 @@ public class SSLUtil {
return SSLContextBuilder.create()
.setProtocol(protocol)
.setKeyManagers(keyManagers)
.setTrustManagers(trustManagers).buildQuietly();
.setTrustManagers(trustManagers).build();
}
}

View File

@@ -1,12 +1,12 @@
package cn.hutool.core.net.url;
import cn.hutool.core.builder.Builder;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.net.RFC3986;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
@@ -26,7 +26,7 @@ import java.nio.charset.Charset;
* @see <a href="https://en.wikipedia.org/wiki/Uniform_Resource_Identifier">Uniform Resource Identifier</a>
* @since 5.3.1
*/
public final class UrlBuilder implements Serializable {
public final class UrlBuilder implements Builder<String> {
private static final long serialVersionUID = 1L;
private static final String DEFAULT_SCHEME = "http";
@@ -468,6 +468,7 @@ public final class UrlBuilder implements Serializable {
*
* @return URL字符串
*/
@Override
public String build() {
return toURL().toString();
}

View File

@@ -279,6 +279,12 @@ public class UrlQuery {
return sb.toString();
}
/**
* 生成查询字符串类似于aaa=111&amp;bbb=222<br>
* 此方法不对任何特殊字符编码,仅用于输出显示
*
* @return 查询字符串
*/
@Override
public String toString() {
return build(null);

View File

@@ -8,12 +8,14 @@ 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.stream.Collector;
import java.util.stream.Collectors;
/**
* 可变的汇聚操作{@link Collector} 相关工具封装
@@ -140,6 +142,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

View File

@@ -0,0 +1,942 @@
package cn.hutool.core.text;
import cn.hutool.core.util.StrUtil;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Ant风格的路径匹配器。<br>
* 来自Spring-core和Ant
*
* <p>匹配URL的规则如下<br>
* <ul>
* <li>{@code ?} 匹配单个字符</li>
* <li>{@code *} 匹配0个或多个字符</li>
* <li>{@code **} 0个或多个路径中的<em>目录节点</em></li>
* <li>{@code {hutool:[a-z]+}} 匹配以"hutool"命名的正则 {@code [a-z]+}</li>
* </ul>
*
* <p>例子: </p>
* <ul>
* <li>{@code com/t?st.jsp} &mdash; 匹配 {@code com/test.jsp} 或 {@code com/tast.jsp} 或 {@code com/txst.jsp}</li>
* <li>{@code com/*.jsp} &mdash; 匹配{@code com}目录下全部 {@code .jsp}文件</li>
* <li>{@code com/&#42;&#42;/test.jsp} &mdash; 匹配{@code com}目录下全部 {@code test.jsp}文件</li>
* <li>{@code cn/hutool/&#42;&#42;/*.jsp} &mdash; 匹配{@code cn/hutool}路径下全部{@code .jsp} 文件</li>
* <li>{@code org/&#42;&#42;/servlet/bla.jsp} &mdash; 匹配{@code cn/hutool/servlet/bla.jsp} 或{@code cn/hutool/testing/servlet/bla.jsp} 或 {@code org/servlet/bla.jsp}</li>
* <li>{@code com/{filename:\\w+}.jsp} 匹配 {@code com/test.jsp} 并将 {@code test} 关联到 {@code filename} 变量</li>
* </ul>
*
* <p><strong>注意:</strong> 表达式和路径必须都为绝对路径或都为相对路径。
*
* @author Alef Arendsen, Juergen Hoeller, Rob Harrop, Arjen Poutsma, Rossen Stoyanchev, Sam Brannen, Vladislav Kisel
* @since 5.7.22
*/
public class AntPathMatcher {
/**
* Default path separator: "/".
*/
public static final String DEFAULT_PATH_SEPARATOR = StrUtil.SLASH;
private static final int CACHE_TURNOFF_THRESHOLD = 65536;
private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{[^/]+?}");
private static final char[] WILDCARD_CHARS = {'*', '?', '{'};
private String pathSeparator;
private PathSeparatorPatternCache pathSeparatorPatternCache;
private boolean caseSensitive = true;
private boolean trimTokens = false;
private volatile Boolean cachePatterns;
private final Map<String, String[]> tokenizedPatternCache = new ConcurrentHashMap<>(256);
private final Map<String, AntPathStringMatcher> stringMatcherCache = new ConcurrentHashMap<>(256);
/**
* 使用 {@link #DEFAULT_PATH_SEPARATOR} 作为分隔符构造
*/
public AntPathMatcher() {
this(DEFAULT_PATH_SEPARATOR);
}
/**
* 使用自定义的分隔符构造
*
* @param pathSeparator the path separator to use, must not be {@code null}.
* @since 4.1
*/
public AntPathMatcher(String pathSeparator) {
if (null == pathSeparator) {
pathSeparator = DEFAULT_PATH_SEPARATOR;
}
setPathSeparator(pathSeparator);
}
/**
* 设置路径分隔符
*
* @param pathSeparator 分隔符,{@code null}表示使用默认分隔符{@link #DEFAULT_PATH_SEPARATOR}
* @return this
*/
public AntPathMatcher setPathSeparator(String pathSeparator) {
if (null == pathSeparator) {
pathSeparator = DEFAULT_PATH_SEPARATOR;
}
this.pathSeparator = pathSeparator;
this.pathSeparatorPatternCache = new PathSeparatorPatternCache(this.pathSeparator);
return this;
}
/**
* 设置是否大小写敏感,默认为{@code true}
*
* @param caseSensitive 是否大小写敏感
* @return this
*/
public AntPathMatcher setCaseSensitive(boolean caseSensitive) {
this.caseSensitive = caseSensitive;
return this;
}
/**
* 设置是否去除路径节点两边的空白符,默认为{@code false}
*
* @param trimTokens 是否去除路径节点两边的空白符
* @return this
*/
public AntPathMatcher setTrimTokens(boolean trimTokens) {
this.trimTokens = trimTokens;
return this;
}
/**
* Specify whether to cache parsed pattern metadata for patterns passed
* into this matcher's {@link #match} method. A value of {@code true}
* activates an unlimited pattern cache; a value of {@code false} turns
* the pattern cache off completely.
* <p>Default is for the cache to be on, but with the variant to automatically
* turn it off when encountering too many patterns to cache at runtime
* (the threshold is 65536), assuming that arbitrary permutations of patterns
* are coming in, with little chance for encountering a recurring pattern.
*
* @param cachePatterns 是否缓存表达式
* @see #getStringMatcher(String)
*/
public AntPathMatcher setCachePatterns(boolean cachePatterns) {
this.cachePatterns = cachePatterns;
return this;
}
/**
* 判断给定路径是否是表达式
*
* @param path 路径
* @return 是否为表达式
*/
public boolean isPattern(String path) {
if (path == null) {
return false;
}
boolean uriVar = false;
final int length = path.length();
char c;
for (int i = 0; i < length; i++) {
c = path.charAt(i);
// 含有通配符
if (c == '*' || c == '?') {
return true;
}
if (c == CharPool.DELIM_START) {
uriVar = true;
continue;
}
if (c == CharPool.DELIM_END && uriVar) {
return true;
}
}
return false;
}
/**
* 给定路径是否匹配表达式
*
* @param pattern 表达式
* @param path 路径
* @return 是否匹配
*/
public boolean match(String pattern, String path) {
return doMatch(pattern, path, true, null);
}
/**
* 前置部分匹配
*
* @param pattern 表达式
* @param path 路径
* @return 是否匹配
*/
public boolean matchStart(String pattern, String path) {
return doMatch(pattern, path, false, null);
}
/**
* 执行匹配,判断给定的{@code path}是否匹配{@code pattern}
*
* @param pattern 表达式
* @param path 路径
* @param fullMatch 是否全匹配。{@code true} 表示全路径匹配,{@code false}表示只匹配开始
* @param uriTemplateVariables 变量映射
* @return {@code true} 表示提供的 {@code path} 匹配, {@code false} 表示不匹配
*/
protected boolean doMatch(String pattern, String path, boolean fullMatch, Map<String, String> uriTemplateVariables) {
if (path == null || path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) {
return false;
}
final String[] pattDirs = tokenizePattern(pattern);
if (fullMatch && this.caseSensitive && false == isPotentialMatch(path, pattDirs)) {
return false;
}
final String[] pathDirs = tokenizePath(path);
int pattIdxStart = 0;
int pattIdxEnd = pattDirs.length - 1;
int pathIdxStart = 0;
int pathIdxEnd = pathDirs.length - 1;
// Match all elements up to the first **
while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
String pattDir = pattDirs[pattIdxStart];
if ("**".equals(pattDir)) {
break;
}
if (notMatchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables)) {
return false;
}
pattIdxStart++;
pathIdxStart++;
}
if (pathIdxStart > pathIdxEnd) {
// Path is exhausted, only match if rest of pattern is * or **'s
if (pattIdxStart > pattIdxEnd) {
return (pattern.endsWith(this.pathSeparator) == path.endsWith(this.pathSeparator));
}
if (false == fullMatch) {
return true;
}
if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) {
return true;
}
for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
if (false == pattDirs[i].equals("**")) {
return false;
}
}
return true;
} else if (pattIdxStart > pattIdxEnd) {
// String not exhausted, but pattern is. Failure.
return false;
} else if (false == fullMatch && "**".equals(pattDirs[pattIdxStart])) {
// Path start definitely matches due to "**" part in pattern.
return true;
}
// up to last '**'
while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
String pattDir = pattDirs[pattIdxEnd];
if (pattDir.equals("**")) {
break;
}
if (notMatchStrings(pattDir, pathDirs[pathIdxEnd], uriTemplateVariables)) {
return false;
}
pattIdxEnd--;
pathIdxEnd--;
}
if (pathIdxStart > pathIdxEnd) {
// String is exhausted
for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
if (false == pattDirs[i].equals("**")) {
return false;
}
}
return true;
}
while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) {
int patIdxTmp = -1;
for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) {
if (pattDirs[i].equals("**")) {
patIdxTmp = i;
break;
}
}
if (patIdxTmp == pattIdxStart + 1) {
// '**/**' situation, so skip one
pattIdxStart++;
continue;
}
// Find the pattern between padIdxStart & padIdxTmp in str between
// strIdxStart & strIdxEnd
int patLength = (patIdxTmp - pattIdxStart - 1);
int strLength = (pathIdxEnd - pathIdxStart + 1);
int foundIdx = -1;
strLoop:
for (int i = 0; i <= strLength - patLength; i++) {
for (int j = 0; j < patLength; j++) {
String subPat = pattDirs[pattIdxStart + j + 1];
String subStr = pathDirs[pathIdxStart + i + j];
if (notMatchStrings(subPat, subStr, uriTemplateVariables)) {
continue strLoop;
}
}
foundIdx = pathIdxStart + i;
break;
}
if (foundIdx == -1) {
return false;
}
pattIdxStart = patIdxTmp;
pathIdxStart = foundIdx + patLength;
}
for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
if (false == pattDirs[i].equals("**")) {
return false;
}
}
return true;
}
private boolean isPotentialMatch(String path, String[] pattDirs) {
if (!this.trimTokens) {
int pos = 0;
for (String pattDir : pattDirs) {
int skipped = skipSeparator(path, pos, this.pathSeparator);
pos += skipped;
skipped = skipSegment(path, pos, pattDir);
if (skipped < pattDir.length()) {
return (skipped > 0 || (pattDir.length() > 0 && isWildcardChar(pattDir.charAt(0))));
}
pos += skipped;
}
}
return true;
}
private int skipSegment(String path, int pos, String prefix) {
int skipped = 0;
for (int i = 0; i < prefix.length(); i++) {
char c = prefix.charAt(i);
if (isWildcardChar(c)) {
return skipped;
}
int currPos = pos + skipped;
if (currPos >= path.length()) {
return 0;
}
if (c == path.charAt(currPos)) {
skipped++;
}
}
return skipped;
}
private int skipSeparator(String path, int pos, String separator) {
int skipped = 0;
while (path.startsWith(separator, pos + skipped)) {
skipped += separator.length();
}
return skipped;
}
private boolean isWildcardChar(char c) {
for (char candidate : WILDCARD_CHARS) {
if (c == candidate) {
return true;
}
}
return false;
}
/**
* Tokenize the given path pattern into parts, based on this matcher's settings.
* <p>Performs caching based on {@link #setCachePatterns}, delegating to
* {@link #tokenizePath(String)} for the actual tokenization algorithm.
*
* @param pattern the pattern to tokenize
* @return the tokenized pattern parts
*/
protected String[] tokenizePattern(String pattern) {
String[] tokenized = null;
Boolean cachePatterns = this.cachePatterns;
if (cachePatterns == null || cachePatterns) {
tokenized = this.tokenizedPatternCache.get(pattern);
}
if (tokenized == null) {
tokenized = tokenizePath(pattern);
if (cachePatterns == null && this.tokenizedPatternCache.size() >= CACHE_TURNOFF_THRESHOLD) {
// Try to adapt to the runtime situation that we're encountering:
// There are obviously too many different patterns coming in here...
// So let's turn off the cache since the patterns are unlikely to be reoccurring.
deactivatePatternCache();
return tokenized;
}
if (cachePatterns == null || cachePatterns) {
this.tokenizedPatternCache.put(pattern, tokenized);
}
}
return tokenized;
}
private void deactivatePatternCache() {
this.cachePatterns = false;
this.tokenizedPatternCache.clear();
this.stringMatcherCache.clear();
}
/**
* Tokenize the given path into parts, based on this matcher's settings.
*
* @param path the path to tokenize
* @return the tokenized path parts
*/
protected String[] tokenizePath(String path) {
return StrSplitter.splitToArray(path, this.pathSeparator, 0, this.trimTokens, true);
}
/**
* Test whether or not a string matches against a pattern.
*
* @param pattern the pattern to match against (never {@code null})
* @param str the String which must be matched against the pattern (never {@code null})
* @return {@code true} if the string matches against the pattern, or {@code false} otherwise
*/
private boolean notMatchStrings(String pattern, String str, Map<String, String> uriTemplateVariables) {
return false == getStringMatcher(pattern).matchStrings(str, uriTemplateVariables);
}
/**
* Build or retrieve an {@link AntPathStringMatcher} for the given pattern.
* <p>The default implementation checks this AntPathMatcher's internal cache
* (see {@link #setCachePatterns}), creating a new AntPathStringMatcher instance
* if no cached copy is found.
* <p>When encountering too many patterns to cache at runtime (the threshold is 65536),
* it turns the default cache off, assuming that arbitrary permutations of patterns
* are coming in, with little chance for encountering a recurring pattern.
* <p>This method may be overridden to implement a custom cache strategy.
*
* @param pattern the pattern to match against (never {@code null})
* @return a corresponding AntPathStringMatcher (never {@code null})
* @see #setCachePatterns
*/
protected AntPathStringMatcher getStringMatcher(String pattern) {
AntPathStringMatcher matcher = null;
Boolean cachePatterns = this.cachePatterns;
if (cachePatterns == null || cachePatterns) {
matcher = this.stringMatcherCache.get(pattern);
}
if (matcher == null) {
matcher = new AntPathStringMatcher(pattern, this.caseSensitive);
if (cachePatterns == null && this.stringMatcherCache.size() >= CACHE_TURNOFF_THRESHOLD) {
// Try to adapt to the runtime situation that we're encountering:
// There are obviously too many different patterns coming in here...
// So let's turn off the cache since the patterns are unlikely to be reoccurring.
deactivatePatternCache();
return matcher;
}
if (cachePatterns == null || cachePatterns) {
this.stringMatcherCache.put(pattern, matcher);
}
}
return matcher;
}
/**
* Given a pattern and a full path, determine the pattern-mapped part. <p>For example: <ul>
* <li>'{@code /docs/cvs/commit.html}' and '{@code /docs/cvs/commit.html} &rarr; ''</li>
* <li>'{@code /docs/*}' and '{@code /docs/cvs/commit} &rarr; '{@code cvs/commit}'</li>
* <li>'{@code /docs/cvs/*.html}' and '{@code /docs/cvs/commit.html} &rarr; '{@code commit.html}'</li>
* <li>'{@code /docs/**}' and '{@code /docs/cvs/commit} &rarr; '{@code cvs/commit}'</li>
* <li>'{@code /docs/**\/*.html}' and '{@code /docs/cvs/commit.html} &rarr; '{@code cvs/commit.html}'</li>
* <li>'{@code /*.html}' and '{@code /docs/cvs/commit.html} &rarr; '{@code docs/cvs/commit.html}'</li>
* <li>'{@code *.html}' and '{@code /docs/cvs/commit.html} &rarr; '{@code /docs/cvs/commit.html}'</li>
* <li>'{@code *}' and '{@code /docs/cvs/commit.html} &rarr; '{@code /docs/cvs/commit.html}'</li> </ul>
* <p>Assumes that {@link #match} returns {@code true} for '{@code pattern}' and '{@code path}', but
* does <strong>not</strong> enforce this.
*
* @param pattern 表达式
* @param path 路径
* @return 表达式匹配到的部分
*/
public String extractPathWithinPattern(String pattern, String path) {
String[] patternParts = tokenizePath(pattern);
String[] pathParts = tokenizePath(path);
StringBuilder builder = new StringBuilder();
boolean pathStarted = false;
for (int segment = 0; segment < patternParts.length; segment++) {
String patternPart = patternParts[segment];
if (patternPart.indexOf('*') > -1 || patternPart.indexOf('?') > -1) {
for (; segment < pathParts.length; segment++) {
if (pathStarted || (segment == 0 && !pattern.startsWith(this.pathSeparator))) {
builder.append(this.pathSeparator);
}
builder.append(pathParts[segment]);
pathStarted = true;
}
}
}
return builder.toString();
}
public Map<String, String> extractUriTemplateVariables(String pattern, String path) {
Map<String, String> variables = new LinkedHashMap<>();
boolean result = doMatch(pattern, path, true, variables);
if (!result) {
throw new IllegalStateException("Pattern \"" + pattern + "\" is not a match for \"" + path + "\"");
}
return variables;
}
/**
* Combine two patterns into a new pattern.
* <p>This implementation simply concatenates the two patterns, unless
* the first pattern contains a file extension match (e.g., {@code *.html}).
* In that case, the second pattern will be merged into the first. Otherwise,
* an {@code IllegalArgumentException} will be thrown.
* <p>Examples</p>
* <table border="1" summary="">
* <tr><th>Pattern 1</th><th>Pattern 2</th><th>Result</th></tr>
* <tr><td>{@code null}</td><td>{@code null}</td><td>&nbsp;</td></tr>
* <tr><td>/hotels</td><td>{@code null}</td><td>/hotels</td></tr>
* <tr><td>{@code null}</td><td>/hotels</td><td>/hotels</td></tr>
* <tr><td>/hotels</td><td>/bookings</td><td>/hotels/bookings</td></tr>
* <tr><td>/hotels</td><td>bookings</td><td>/hotels/bookings</td></tr>
* <tr><td>/hotels/*</td><td>/bookings</td><td>/hotels/bookings</td></tr>
* <tr><td>/hotels/&#42;&#42;</td><td>/bookings</td><td>/hotels/&#42;&#42;/bookings</td></tr>
* <tr><td>/hotels</td><td>{hotel}</td><td>/hotels/{hotel}</td></tr>
* <tr><td>/hotels/*</td><td>{hotel}</td><td>/hotels/{hotel}</td></tr>
* <tr><td>/hotels/&#42;&#42;</td><td>{hotel}</td><td>/hotels/&#42;&#42;/{hotel}</td></tr>
* <tr><td>/*.html</td><td>/hotels.html</td><td>/hotels.html</td></tr>
* <tr><td>/*.html</td><td>/hotels</td><td>/hotels.html</td></tr>
* <tr><td>/*.html</td><td>/*.txt</td><td>{@code IllegalArgumentException}</td></tr>
* </table>
*
* @param pattern1 the first pattern
* @param pattern2 the second pattern
* @return the combination of the two patterns
* @throws IllegalArgumentException if the two patterns cannot be combined
*/
public String combine(String pattern1, String pattern2) {
if (StrUtil.isEmpty(pattern1) && StrUtil.isEmpty(pattern2)) {
return StrUtil.EMPTY;
}
if (StrUtil.isEmpty(pattern1)) {
return pattern2;
}
if (StrUtil.isEmpty(pattern2)) {
return pattern1;
}
boolean pattern1ContainsUriVar = (pattern1.indexOf('{') != -1);
if (!pattern1.equals(pattern2) && !pattern1ContainsUriVar && match(pattern1, pattern2)) {
// /* + /hotel -> /hotel ; "/*.*" + "/*.html" -> /*.html
// However /user + /user -> /usr/user ; /{foo} + /bar -> /{foo}/bar
return pattern2;
}
// /hotels/* + /booking -> /hotels/booking
// /hotels/* + booking -> /hotels/booking
if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnWildCard())) {
return concat(pattern1.substring(0, pattern1.length() - 2), pattern2);
}
// /hotels/** + /booking -> /hotels/**/booking
// /hotels/** + booking -> /hotels/**/booking
if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnDoubleWildCard())) {
return concat(pattern1, pattern2);
}
int starDotPos1 = pattern1.indexOf("*.");
if (pattern1ContainsUriVar || starDotPos1 == -1 || this.pathSeparator.equals(".")) {
// simply concatenate the two patterns
return concat(pattern1, pattern2);
}
String ext1 = pattern1.substring(starDotPos1 + 1);
int dotPos2 = pattern2.indexOf('.');
String file2 = (dotPos2 == -1 ? pattern2 : pattern2.substring(0, dotPos2));
String ext2 = (dotPos2 == -1 ? "" : pattern2.substring(dotPos2));
boolean ext1All = (ext1.equals(".*") || ext1.isEmpty());
boolean ext2All = (ext2.equals(".*") || ext2.isEmpty());
if (!ext1All && !ext2All) {
throw new IllegalArgumentException("Cannot combine patterns: " + pattern1 + " vs " + pattern2);
}
String ext = (ext1All ? ext2 : ext1);
return file2 + ext;
}
private String concat(String path1, String path2) {
boolean path1EndsWithSeparator = path1.endsWith(this.pathSeparator);
boolean path2StartsWithSeparator = path2.startsWith(this.pathSeparator);
if (path1EndsWithSeparator && path2StartsWithSeparator) {
return path1 + path2.substring(1);
} else if (path1EndsWithSeparator || path2StartsWithSeparator) {
return path1 + path2;
} else {
return path1 + this.pathSeparator + path2;
}
}
/**
* Given a full path, returns a {@link Comparator} suitable for sorting patterns in order of
* explicitness.
* <p>This {@code Comparator} will {@linkplain List#sort(Comparator) sort}
* a list so that more specific patterns (without URI templates or wild cards) come before
* generic patterns. So given a list with the following patterns, the returned comparator
* will sort this list so that the order will be as indicated.
* <ol>
* <li>{@code /hotels/new}</li>
* <li>{@code /hotels/{hotel}}</li>
* <li>{@code /hotels/*}</li>
* </ol>
* <p>The full path given as parameter is used to test for exact matches. So when the given path
* is {@code /hotels/2}, the pattern {@code /hotels/2} will be sorted before {@code /hotels/1}.
*
* @param path the full path to use for comparison
* @return a comparator capable of sorting patterns in order of explicitness
*/
public Comparator<String> getPatternComparator(String path) {
return new AntPatternComparator(path);
}
/**
* Tests whether or not a string matches against a pattern via a {@link Pattern}.
* <p>The pattern may contain special characters: '*' means zero or more characters; '?' means one and
* only one character; '{' and '}' indicate a URI template pattern. For example <tt>/users/{user}</tt>.
*/
protected static class AntPathStringMatcher {
private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?}|[^/{}]|\\\\[{}])+?)}");
private static final String DEFAULT_VARIABLE_PATTERN = "((?s).*)";
private final String rawPattern;
private final boolean caseSensitive;
private final boolean exactMatch;
private final Pattern pattern;
private final List<String> variableNames = new ArrayList<>();
public AntPathStringMatcher(String pattern, boolean caseSensitive) {
this.rawPattern = pattern;
this.caseSensitive = caseSensitive;
StringBuilder patternBuilder = new StringBuilder();
Matcher matcher = GLOB_PATTERN.matcher(pattern);
int end = 0;
while (matcher.find()) {
patternBuilder.append(quote(pattern, end, matcher.start()));
String match = matcher.group();
if ("?".equals(match)) {
patternBuilder.append('.');
} else if ("*".equals(match)) {
patternBuilder.append(".*");
} else if (match.startsWith("{") && match.endsWith("}")) {
int colonIdx = match.indexOf(':');
if (colonIdx == -1) {
patternBuilder.append(DEFAULT_VARIABLE_PATTERN);
this.variableNames.add(matcher.group(1));
} else {
String variablePattern = match.substring(colonIdx + 1, match.length() - 1);
patternBuilder.append('(');
patternBuilder.append(variablePattern);
patternBuilder.append(')');
String variableName = match.substring(1, colonIdx);
this.variableNames.add(variableName);
}
}
end = matcher.end();
}
// No glob pattern was found, this is an exact String match
if (end == 0) {
this.exactMatch = true;
this.pattern = null;
} else {
this.exactMatch = false;
patternBuilder.append(quote(pattern, end, pattern.length()));
this.pattern = (this.caseSensitive ? Pattern.compile(patternBuilder.toString()) :
Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE));
}
}
private String quote(String s, int start, int end) {
if (start == end) {
return "";
}
return Pattern.quote(s.substring(start, end));
}
/**
* Main entry point.
*
* @return {@code true} if the string matches against the pattern, or {@code false} otherwise.
*/
public boolean matchStrings(String str, Map<String, String> uriTemplateVariables) {
if (this.exactMatch) {
return this.caseSensitive ? this.rawPattern.equals(str) : this.rawPattern.equalsIgnoreCase(str);
} else if (this.pattern != null) {
Matcher matcher = this.pattern.matcher(str);
if (matcher.matches()) {
if (uriTemplateVariables != null) {
if (this.variableNames.size() != matcher.groupCount()) {
throw new IllegalArgumentException("The number of capturing groups in the pattern segment " +
this.pattern + " does not match the number of URI template variables it defines, " +
"which can occur if capturing groups are used in a URI template regex. " +
"Use non-capturing groups instead.");
}
for (int i = 1; i <= matcher.groupCount(); i++) {
String name = this.variableNames.get(i - 1);
if (name.startsWith("*")) {
throw new IllegalArgumentException("Capturing patterns (" + name + ") are not " +
"supported by the AntPathMatcher. Use the PathPatternParser instead.");
}
String value = matcher.group(i);
uriTemplateVariables.put(name, value);
}
}
return true;
}
}
return false;
}
}
/**
* The default {@link Comparator} implementation returned by
* {@link #getPatternComparator(String)}.
* <p>In order, the most "generic" pattern is determined by the following:
* <ul>
* <li>if it's null or a capture all pattern (i.e. it is equal to "/**")</li>
* <li>if the other pattern is an actual match</li>
* <li>if it's a catch-all pattern (i.e. it ends with "**"</li>
* <li>if it's got more "*" than the other pattern</li>
* <li>if it's got more "{foo}" than the other pattern</li>
* <li>if it's shorter than the other pattern</li>
* </ul>
*/
protected static class AntPatternComparator implements Comparator<String> {
private final String path;
public AntPatternComparator(String path) {
this.path = path;
}
/**
* Compare two patterns to determine which should match first, i.e. which
* is the most specific regarding the current path.
*
* @param pattern1 表达式1
* @param pattern2 表达式2
* @return a negative integer, zero, or a positive integer as pattern1 is
* more specific, equally specific, or less specific than pattern2.
*/
@Override
public int compare(String pattern1, String pattern2) {
PatternInfo info1 = new PatternInfo(pattern1);
PatternInfo info2 = new PatternInfo(pattern2);
if (info1.isLeastSpecific() && info2.isLeastSpecific()) {
return 0;
} else if (info1.isLeastSpecific()) {
return 1;
} else if (info2.isLeastSpecific()) {
return -1;
}
boolean pattern1EqualsPath = pattern1.equals(this.path);
boolean pattern2EqualsPath = pattern2.equals(this.path);
if (pattern1EqualsPath && pattern2EqualsPath) {
return 0;
} else if (pattern1EqualsPath) {
return -1;
} else if (pattern2EqualsPath) {
return 1;
}
if (info1.isPrefixPattern() && info2.isPrefixPattern()) {
return info2.getLength() - info1.getLength();
} else if (info1.isPrefixPattern() && info2.getDoubleWildcards() == 0) {
return 1;
} else if (info2.isPrefixPattern() && info1.getDoubleWildcards() == 0) {
return -1;
}
if (info1.getTotalCount() != info2.getTotalCount()) {
return info1.getTotalCount() - info2.getTotalCount();
}
if (info1.getLength() != info2.getLength()) {
return info2.getLength() - info1.getLength();
}
if (info1.getSingleWildcards() < info2.getSingleWildcards()) {
return -1;
} else if (info2.getSingleWildcards() < info1.getSingleWildcards()) {
return 1;
}
if (info1.getUriVars() < info2.getUriVars()) {
return -1;
} else if (info2.getUriVars() < info1.getUriVars()) {
return 1;
}
return 0;
}
/**
* Value class that holds information about the pattern, e.g. number of
* occurrences of "*", "**", and "{" pattern elements.
*/
private static class PatternInfo {
private final String pattern;
private int uriVars;
private int singleWildcards;
private int doubleWildcards;
private boolean catchAllPattern;
private boolean prefixPattern;
private Integer length;
public PatternInfo(String pattern) {
this.pattern = pattern;
if (this.pattern != null) {
initCounters();
this.catchAllPattern = this.pattern.equals("/**");
this.prefixPattern = !this.catchAllPattern && this.pattern.endsWith("/**");
}
if (this.uriVars == 0) {
this.length = (this.pattern != null ? this.pattern.length() : 0);
}
}
protected void initCounters() {
int pos = 0;
if (this.pattern != null) {
while (pos < this.pattern.length()) {
if (this.pattern.charAt(pos) == '{') {
this.uriVars++;
pos++;
} else if (this.pattern.charAt(pos) == '*') {
if (pos + 1 < this.pattern.length() && this.pattern.charAt(pos + 1) == '*') {
this.doubleWildcards++;
pos += 2;
} else if (pos > 0 && !this.pattern.substring(pos - 1).equals(".*")) {
this.singleWildcards++;
pos++;
} else {
pos++;
}
} else {
pos++;
}
}
}
}
public int getUriVars() {
return this.uriVars;
}
public int getSingleWildcards() {
return this.singleWildcards;
}
public int getDoubleWildcards() {
return this.doubleWildcards;
}
public boolean isLeastSpecific() {
return (this.pattern == null || this.catchAllPattern);
}
public boolean isPrefixPattern() {
return this.prefixPattern;
}
public int getTotalCount() {
return this.uriVars + this.singleWildcards + (2 * this.doubleWildcards);
}
/**
* Returns the length of the given pattern, where template variables are considered to be 1 long.
*
* @return 长度
*/
public int getLength() {
if (this.length == null) {
this.length = (this.pattern != null ?
VARIABLE_PATTERN.matcher(this.pattern).replaceAll("#").length() : 0);
}
return this.length;
}
}
}
/**
* A simple cache for patterns that depend on the configured path separator.
*/
private static class PathSeparatorPatternCache {
private final String endsOnWildCard;
private final String endsOnDoubleWildCard;
public PathSeparatorPatternCache(String pathSeparator) {
this.endsOnWildCard = pathSeparator + "*";
this.endsOnDoubleWildCard = pathSeparator + "**";
}
public String getEndsOnWildCard() {
return this.endsOnWildCard;
}
public String getEndsOnDoubleWildCard() {
return this.endsOnDoubleWildCard;
}
}
}

View File

@@ -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}提取出匹配到的内容的不同部分,然后经过重新处理、组装变成新的内容放回原位。

View File

@@ -12,7 +12,14 @@ import java.util.Iterator;
import java.util.function.Function;
/**
* 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串
* 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串<br>
* 相较于{@link java.util.StringJoiner}提供更加灵活的配置,包括:
* <ul>
* <li>支持任意Appendable接口实现</li>
* <li>支持每个元素单独wrap</li>
* <li>支持自定义null的处理逻辑</li>
* <li>支持自定义默认结果</li>
* </ul>
*
* @author looly
* @since 5.7.2
@@ -282,6 +289,11 @@ public class StrJoiner implements Appendable, Serializable {
@Override
public StrJoiner append(CharSequence csq) {
return append(csq, 0, StrUtil.length(csq));
}
@Override
public StrJoiner append(CharSequence csq, int startInclude, int endExclude) {
if (null == csq) {
switch (this.nullMode) {
case IGNORE:
@@ -291,6 +303,8 @@ public class StrJoiner implements Appendable, Serializable {
break;
case NULL_STRING:
csq = StrUtil.NULL;
endExclude = StrUtil.NULL.length();
break;
}
}
try {
@@ -298,7 +312,7 @@ public class StrJoiner implements Appendable, Serializable {
if (wrapElement && StrUtil.isNotEmpty(this.prefix)) {
appendable.append(prefix);
}
appendable.append(csq);
appendable.append(csq, startInclude, endExclude);
if (wrapElement && StrUtil.isNotEmpty(this.suffix)) {
appendable.append(suffix);
}
@@ -308,29 +322,55 @@ public class StrJoiner implements Appendable, Serializable {
return this;
}
@Override
public StrJoiner append(CharSequence csq, int startInclude, int endExclude) {
return append(StrUtil.sub(csq, startInclude, endExclude));
}
@Override
public StrJoiner append(char c) {
return append(String.valueOf(c));
}
/**
* 合并一个StrJoiner 到当前的StrJoiner<br>
* 合并规则为,在尾部直接追加,当存在{@link #prefix}时,如果{@link #wrapElement}为{@code false},则去除之。
*
* @param strJoiner 其他的StrJoiner
* @return this
* @since 5.7.22
*/
public StrJoiner merge(StrJoiner strJoiner){
if(null != strJoiner && null != strJoiner.appendable){
final String otherStr = strJoiner.toString();
if(strJoiner.wrapElement){
this.append(otherStr);
}else{
this.append(otherStr, this.prefix.length(), otherStr.length());
}
}
return this;
}
/**
* 长度<br>
* 长度计算方式为prefix + suffix + content<br>
* 此方法结果与toString().length()一致。
*
* @return 长度,如果结果为{@code null},返回-1
* @since 5.7.22
*/
public int length() {
return (this.appendable != null ? this.appendable.toString().length() + suffix.length() :
null == this.emptyResult ? -1 : emptyResult.length());
}
@Override
public String toString() {
if (null == this.appendable) {
return emptyResult;
}
String result = this.appendable.toString();
if (false == wrapElement && StrUtil.isNotEmpty(this.suffix)) {
try {
this.appendable.append(this.suffix);
} catch (IOException e) {
throw new IORuntimeException(e);
}
result += this.suffix;
}
return this.appendable.toString();
return result;
}
/**

View File

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

View File

@@ -1,5 +1,6 @@
/**
* 提供CSV文件读写的封装入口为CsvUtil
* 提供CSV文件读写的封装入口为CsvUtil<br>
* 规范见https://datatracker.ietf.org/doc/html/rfc4180
*
* @author looly
*

View File

@@ -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,30 @@ 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 start 开始位置(包含)
* @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 +261,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);
}
}

View File

@@ -22,12 +22,12 @@ public class CoordinateUtil {
/**
* 坐标转换参数:(火星坐标系与百度坐标系转换的中间量)
*/
public static final double X_PI = 3.14159265358979324 * 3000.0 / 180.0;
public static final double X_PI = 3.1415926535897932384626433832795 * 3000.0 / 180.0;
/**
* 坐标转换参数:π
*/
public static final double PI = 3.1415926535897932384626D;
public static final double PI = 3.1415926535897932384626433832795D;
/**
* 地球半径Krasovsky 1940
@@ -280,7 +280,7 @@ public class CoordinateUtil {
*/
public Coordinate offset(Coordinate offset){
this.lng += offset.lng;
this.lat += offset.lng;
this.lat += offset.lat;
return this;
}

View File

@@ -7,7 +7,8 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
/**
* 统一社会信用代码工具类
* 统一社会信用代码GB32100-2015工具类<br>
* 标准见https://www.cods.org.cn/c/2020-10-29/12575.html
*
* <pre>
* 第一部分登记管理部门代码1位 (数字或大写英文字母)
@@ -24,7 +25,13 @@ public class CreditCodeUtil {
public static final Pattern CREDIT_CODE_PATTERN = PatternPool.CREDIT_CODE;
/**
* 加权因子
*/
private static final int[] WEIGHT = {1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28};
/**
* 代码字符集
*/
private static final char[] BASE_CODE_ARRAY = "0123456789ABCDEFGHJKLMNPQRTUWXY".toCharArray();
private static final Map<Character, Integer> CODE_INDEX_MAP;
@@ -110,10 +117,10 @@ public class CreditCodeUtil {
}
/**
* 获取校验
* 获取校验位的值
*
* @param creditCode 统一社会信息代码
* @return 获取验位的值
* @return 获取验位的值-1表示获取错误
*/
private static int getParityBit(CharSequence creditCode) {
int sum = 0;

View File

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

View File

@@ -105,7 +105,7 @@ public class ReUtil {
}
/**
* 获得匹配的字符串,获得正则中分组0的内容
* 获得匹配的字符串获得正则中分组0的内容
*
* @param pattern 编译后的正则模式
* @param content 被匹配的内容

View File

@@ -34,7 +34,7 @@ public class RuntimeUtil {
}
/**
* 执行系统命令,使用系统默认编码
* 执行系统命令,使用传入的 {@link Charset charset} 编码
*
* @param charset 编码
* @param cmds 命令列表,每个元素代表一条命令
@@ -58,7 +58,7 @@ public class RuntimeUtil {
}
/**
* 执行系统命令,使用系统默认编码
* 执行系统命令,使用传入的 {@link Charset charset} 编码
*
* @param charset 编码
* @param cmds 命令列表,每个元素代表一条命令
@@ -131,7 +131,7 @@ public class RuntimeUtil {
}
/**
* 获取命令执行结果,使用系统默认编码,获取后销毁进程
* 获取命令执行结果,使用传入的 {@link Charset charset} 编码,获取后销毁进程
*
* @param process {@link Process} 进程
* @param charset 编码
@@ -150,7 +150,7 @@ public class RuntimeUtil {
}
/**
* 获取命令执行结果,使用系统默认编码,获取后销毁进程
* 获取命令执行结果,使用系统默认编码,获取后销毁进程
*
* @param process {@link Process} 进程
* @return 命令执行结果列表
@@ -180,7 +180,7 @@ public class RuntimeUtil {
}
/**
* 获取命令执行异常结果,使用系统默认编码,获取后销毁进程
* 获取命令执行异常结果,使用系统默认编码,获取后销毁进程
*
* @param process {@link Process} 进程
* @return 命令执行结果列表

View File

@@ -108,7 +108,7 @@ public class SystemPropsUtil {
* @param defaultValue 默认值
* @return 值
*/
public static long getInt(String key, int defaultValue) {
public static int getInt(String key, int defaultValue) {
return Convert.toInt(get(key), defaultValue);
}

View File

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

View File

@@ -1,5 +1,6 @@
package cn.hutool.core.util;
import cn.hutool.core.collection.EnumerationIter;
import cn.hutool.core.compress.Deflate;
import cn.hutool.core.compress.Gzip;
import cn.hutool.core.compress.ZipCopyVisitor;
@@ -29,7 +30,6 @@ import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import java.util.zip.ZipEntry;
@@ -953,7 +953,8 @@ public class ZipUtil {
}
/**
* 获取Zip文件中指定目录下的所有文件只显示文件不显示目录
* 获取Zip文件中指定目录下的所有文件只显示文件不显示目录<br>
* 此方法并不会关闭{@link ZipFile}。
*
* @param zipFile Zip文件
* @param dir 目录前缀(目录前缀不包含开头的/
@@ -968,7 +969,7 @@ public class ZipUtil {
final List<String> fileNames = new ArrayList<>();
String name;
for (ZipEntry entry : Collections.list(zipFile.entries())) {
for (ZipEntry entry : new EnumerationIter<>(zipFile.entries())) {
name = entry.getName();
if (StrUtil.isEmpty(dir) || name.startsWith(dir)) {
final String nameSuffix = StrUtil.removePrefix(name, dir);

View File

@@ -0,0 +1,111 @@
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;
import java.util.HashMap;
import java.util.Map;
/**
* {@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());
Assert.assertEquals("TomXin:\"Hello Partner!\"", box1.getTitleAlias());
}
@Test
public void buildMapTest(){
//Map创建
HashMap<String, String> colorMap = GenericBuilder
.of(HashMap<String,String>::new)
.with(Map::put, "red", "#FF0000")
.with(Map::put, "yellow", "#FFFF00")
.with(Map::put, "blue", "#0000FF")
.build();
Assert.assertEquals("#FF0000", colorMap.get("red"));
Assert.assertEquals("#FFFF00", colorMap.get("yellow"));
Assert.assertEquals("#0000FF", colorMap.get("blue"));
}
@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 + "\"";
}
}
}
}

View File

@@ -0,0 +1,40 @@
package cn.hutool.core.codec;
import org.junit.Assert;
import org.junit.Test;
import java.nio.charset.StandardCharsets;
public class Base58Test {
@Test
public void encodeCheckedTest() {
String a = "hello world";
String encode = Base58.encodeChecked(0, a.getBytes());
Assert.assertEquals(1 + "3vQB7B6MrGQZaxCuFg4oh", encode);
// 无版本位
encode = Base58.encodeChecked(null, a.getBytes());
Assert.assertEquals("3vQB7B6MrGQZaxCuFg4oh", encode);
}
@Test
public void encodeTest() {
String a = "hello world";
String encode = Base58.encode(a.getBytes(StandardCharsets.UTF_8));
Assert.assertEquals("StV1DL6CwTryKyV", encode);
}
@Test
public void decodeCheckedTest() {
String a = "3vQB7B6MrGQZaxCuFg4oh";
byte[] decode = Base58.decodeChecked(1 + a);
Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode);
decode = Base58.decodeChecked(a);
Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode);
}
@Test
public void testDecode() {
String a = "StV1DL6CwTryKyV";
byte[] decode = Base58.decode(a);
Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode);
}
}

View File

@@ -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;
}
@@ -224,5 +227,9 @@ public class ListUtilTest {
ListUtil.swapElement(list, map2, map3);
Map<String, String> map = list.get(2);
Assert.assertEquals("李四", map.get("2"));
ListUtil.swapElement(list, map2, map1);
map = list.get(0);
Assert.assertEquals("李四", map.get("2"));
}
}

View File

@@ -65,6 +65,14 @@ public class ConvertTest {
Assert.assertEquals("a", result);
}
@Test
public void toStrTest4() {
// 被当作八进制
@SuppressWarnings("OctalInteger")
final String result = Convert.toStr(001200);
Assert.assertEquals("640", result);
}
@Test
public void toIntTest() {
String a = " 34232";

View File

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

View File

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

View File

@@ -4,6 +4,8 @@ import cn.hutool.core.util.StrUtil;
import org.junit.Assert;
import org.junit.Test;
import java.util.Date;
public class ChineseDateTest {
@Test
@@ -105,4 +107,36 @@ 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);
}
@Test
public void day19700101Test(){
// https://gitee.com/dromara/hutool/issues/I4UTPK
Date date = DateUtil.parse("1970-01-01");
ChineseDate chineseDate = new ChineseDate(date);
Assert.assertEquals("己酉鸡年 冬月廿四", chineseDate.toString());
date = DateUtil.parse("1970-01-02");
chineseDate = new ChineseDate(date);
Assert.assertEquals("己酉鸡年 冬月廿五", chineseDate.toString());
date = DateUtil.parse("1970-01-03");
chineseDate = new ChineseDate(date);
Assert.assertEquals("己酉鸡年 冬月廿六", chineseDate.toString());
}
@Test
public void day19000101Test(){
// 1900-01-31之前不支持
Date date = DateUtil.parse("1900-01-31");
ChineseDate chineseDate = new ChineseDate(date);
Assert.assertEquals("庚子鼠年 正月初一", chineseDate.toString());
}
}

View File

@@ -694,6 +694,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 +994,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 +1008,43 @@ 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);
}
@Test
public void parseTimeTest(){
final DateTime dateTime = DateUtil.parse("12:23:34");
Console.log(dateTime);
}
@Test
public void isOverlapTest() {
DateTime oneStartTime = DateUtil.parse("2022-01-01 10:10:10");
DateTime oneEndTime = DateUtil.parse("2022-01-01 11:10:10");
DateTime oneStartTime2 = DateUtil.parse("2022-01-01 11:20:10");
DateTime oneEndTime2 = DateUtil.parse("2022-01-01 11:30:10");
DateTime oneStartTime3 = DateUtil.parse("2022-01-01 11:40:10");
DateTime oneEndTime3 = DateUtil.parse("2022-01-01 11:50:10");
//真实请假数据
DateTime realStartTime = DateUtil.parse("2022-01-01 11:49:10");
DateTime realEndTime = DateUtil.parse("2022-01-01 12:00:10");
Assert.assertTrue(DateUtil.isOverlap(oneStartTime, oneEndTime, realStartTime, realEndTime));
Assert.assertTrue(DateUtil.isOverlap(oneStartTime2, oneEndTime2, realStartTime, realEndTime));
Assert.assertFalse(DateUtil.isOverlap(oneStartTime3, oneEndTime3, realStartTime, realEndTime));
}
}

View File

@@ -1,13 +1,16 @@
package cn.hutool.core.date;
import cn.hutool.core.lang.Console;
import org.junit.Assert;
import org.junit.Test;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAccessor;
public class LocalDateTimeUtilTest {
@@ -197,4 +200,31 @@ 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);
}
@Test
public void ofTest2(){
final Instant instant = DateUtil.parse("2022-02-22").toInstant();
final LocalDateTime of = LocalDateTimeUtil.of((TemporalAccessor) instant);
Console.log(of);
}
}

View File

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

View File

@@ -21,11 +21,8 @@ public class CheckedUtilTest {
@Test
public void sleepTest() {
VoidFunc0 func = () -> Thread.sleep(1000L);
func.callWithRuntimeException();
}
@@ -39,10 +36,8 @@ public class CheckedUtilTest {
} catch (Exception re) {
Assert.assertTrue(re instanceof RuntimeException);
}
}
@SuppressWarnings("ConstantConditions")
@Test
public void functionTest() {
Func1<String, String> afunc = (funcParam) -> {

View File

@@ -452,4 +452,26 @@ public class FileUtilTest {
List<String> list = ListUtil.toList("a", "b", "c");
FileUtil.appendLines(list, FileUtil.file("d:/test/appendLines.txt"), CharsetUtil.CHARSET_UTF_8);
}
@Test
@Ignore
public void createTempFileTest(){
File nullDirTempFile = FileUtil.createTempFile();
Assert.assertTrue(nullDirTempFile.exists());
File suffixDirTempFile = FileUtil.createTempFile(".xlsx",true);
Assert.assertEquals("xlsx", FileUtil.getSuffix(suffixDirTempFile));
File prefixDirTempFile = FileUtil.createTempFile("prefix",".xlsx",true);
Assert.assertTrue(FileUtil.getPrefix(prefixDirTempFile).startsWith("prefix"));
}
@Test
@Ignore
public void getTotalLinesTest() {
// 千万行秒级内返回
final int totalLines = FileUtil.getTotalLines(FileUtil.file(""));
Assert.assertEquals(10000000, totalLines);
}
}

View File

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

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

View File

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

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

Some files were not shown because too many files have changed in this diff Show More