Merge branch 'v5-dev' of https://github.com/totalo/hutool into v5-dev

This commit is contained in:
totalo
2020-09-25 15:32:39 +08:00
154 changed files with 3750 additions and 949 deletions

View File

@@ -3,6 +3,64 @@
-------------------------------------------------------------------------------------------------------------
# 5.4.4 (2020-09-23)
### 新特性
* 【core 】 ServiceLoaderUtil改为使用contextClassLoaderpr#183@Gitee
* 【core 】 NetUtil增加getLocalHostNamepr#1103@Github
* 【extra 】 FTP增加stat方法issue#I1W346@Gitee
* 【core 】 Convert.toNumber支持类似12.2F这种形式字符串转换issue#I1VYLJ@Gitee
* 【core 】 使用静态变量替换999等issue#I1W8IB@Gitee
* 【core 】 URLUtil自动trimissue#I1W803@Gitee
* 【crypto 】 RC4增加ecryptpr#1108@Github
* 【core 】 CharUtil and StrUtil增加@pr#1106@Github
* 【extra 】 优化EMOJ查询逻辑pr#1112@Github
* 【extra 】 优化CollUtil交并集结果集合设置初始化大小避免扩容成本pr#1110@Github
* 【core 】 优化PageUtil彩虹算法issue#1110@Github
* 【core 】 IoUtil增加readUtf8方法
* 【core 】 优化全局邮箱账户初始化逻辑pr#1114@Github
### Bug修复
* 【crypto 】 修复SM2验签后无法解密问题issue#I1W0VP@Gitee
* 【core 】 修复新建默认TreeSet没有默认比较器导致的问题issue#1101@Github
* 【core 】 修复Linux下使用Windows路径分隔符导致的解压错误issue#I1MW0E@Gitee
* 【core 】 修复Word07Writer写出map问题issue#I1W49R@Gitee
-------------------------------------------------------------------------------------------------------------
# 5.4.3 (2020-09-16)
### 新特性
* 【core 】 使用静态的of方法来new对象pr#177@Gitee
* 【setting】 Setting增加store无参方法issue#1072@Github
* 【setting】 StatementUtil增加null缓存pr#1076@Github
* 【core 】 扩充Console功能支持可变参数issue#1077@Github
* 【crypto 】 增加ECKeyUtilissue#I1UOF5@Gitee
* 【core 】 增加TransXXXissue#I1TU1Y@Gitee
* 【core 】 增加Generator
* 【db 】 Column增加是否主键、保留位数等字段
* 【cache 】 Cache接口增加get重载issue#1080@Github
* 【core 】 增加Interner和InternUtilissue#I1TU1Y@Gitee
* 【core 】 增加Calculatorissue#1090@Github
* 【core 】 IdcardUtil增加getIdcardInfo方法issue#1092@Github
* 【core 】 改进ObjectUtil.equal支持BigDecimal判断
* 【core 】 ArrayConverter增加可选是否忽略错误issue#I1VNYQ@Gitee
* 【db 】 增加ConditionBuilder
* 【setting】 Setting和Props增加create方法
* 【log 】 增加TinyLog2支持issue#1094@Github
### Bug修复
* 【core 】 修复Dict.of错误issue#I1UUO5@Gitee
* 【core 】 修复UrlBuilder地址参数问题issue#I1UWCA@Gitee
* 【core 】 修复StrUtil.toSymbolCase转换问题issue#1075@Github
* 【log 】 修复打印null对象显示{msg}异常问题issue#1084@Github
* 【extra 】 修复ServletUtil.getReader中未关闭的问题
* 【extra 】 修复QrCodeUtil在新版本zxing报错问题issue#1088@Github
* 【core 】 修复LocalDateTimeUtil.parse无法解析yyyyMMddHHmmssSSS的bugissue#1082@Github
* 【core 】 修复VersionComparator.equals递归调用问题issue#1093@Github
-------------------------------------------------------------------------------------------------------------
# 5.4.2 (2020-09-09)
### 新特性

View File

@@ -43,8 +43,7 @@
<br/>
<p align="center">
<a href="https://qm.qq.com/cgi-bin/qm/qr?k=oolXnM7i9pHbiyUOfCdQWSA3RuIXqUsk&jump_from=webapi"><img src="https://img.shields.io/badge/QQ%E7%BE%A4%E2%91%A3-718802356-orange"/></a>
<a href="https://qm.qq.com/cgi-bin/qm/qr?k=0wwldaU0E8r-ZzHl_wma33W7420zwXYi&jump_from=webapi"><img src="https://img.shields.io/badge/QQ%E7%BE%A4%E2%91%A4-956375658-orange"/></a>
<a href="https://qm.qq.com/cgi-bin/qm/qr?k=0wwldaU0E8r-ZzHl_wma33W7420zwXYi&jump_from=webapi"><img src="https://img.shields.io/badge/QQ%20Group-956375658-orange"/></a>
</p>
-------------------------------------------------------------------------------
@@ -121,19 +120,19 @@ Each module can be introduced individually, or all modules can be introduced by
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.4.2</version>
<version>5.4.4</version>
</dependency>
```
### Gradle
```
compile 'cn.hutool:hutool-all:5.4.2'
compile 'cn.hutool:hutool-all:5.4.4'
```
## Download
- [Maven1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.4.2/)
- [Maven2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.4.2/)
- [Maven1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.4.4/)
- [Maven2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.4.4/)
> 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

@@ -43,7 +43,6 @@
<br/>
<p align="center">
<a href="https://qm.qq.com/cgi-bin/qm/qr?k=oolXnM7i9pHbiyUOfCdQWSA3RuIXqUsk&jump_from=webapi"><img src="https://img.shields.io/badge/QQ%E7%BE%A4%E2%91%A3-718802356-orange"/></a>
<a href="https://qm.qq.com/cgi-bin/qm/qr?k=0wwldaU0E8r-ZzHl_wma33W7420zwXYi&jump_from=webapi"><img src="https://img.shields.io/badge/QQ%E7%BE%A4%E2%91%A4-956375658-orange"/></a>
</p>
@@ -120,21 +119,21 @@ Hutool的存在就是为了减少代码搜索成本避免网络上参差不
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.4.2</version>
<version>5.4.4</version>
</dependency>
```
### Gradle
```
compile 'cn.hutool:hutool-all:5.4.2'
compile 'cn.hutool:hutool-all:5.4.4'
```
### 非Maven项目
点击以下任一链接,下载`hutool-all-X.X.X.jar`即可:
- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.4.2/)
- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.4.2/)
- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.4.4/)
- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.4.4/)
> 注意
> Hutool 5.x支持JDK8+对Android平台没有测试不能保证所有工具类或工具方法可用。

View File

@@ -1 +1 @@
5.4.2
5.4.4

View File

@@ -1 +1 @@
var version = '5.4.2'
var version = '5.4.4'

View File

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

View File

@@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.4.2</version>
<version>5.4.4-SNAPSHOT</version>
</parent>
<artifactId>hutool-aop</artifactId>
@@ -19,7 +19,7 @@
<properties>
<!-- versions -->
<cglib.version>3.3.0</cglib.version>
<spring.version>5.2.7.RELEASE</spring.version>
<spring.version>5.2.9.RELEASE</spring.version>
</properties>
<dependencies>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.4.2</version>
<version>5.4.4-SNAPSHOT</version>
</parent>
<artifactId>hutool-bloomFilter</artifactId>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.4.2</version>
<version>5.4.4-SNAPSHOT</version>
</parent>
<artifactId>hutool-bom</artifactId>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.4.2</version>
<version>5.4.4-SNAPSHOT</version>
</parent>
<artifactId>hutool-cache</artifactId>

View File

@@ -1,39 +1,38 @@
package cn.hutool.cache;
import java.io.Serializable;
import java.util.Iterator;
import cn.hutool.cache.impl.CacheObj;
import cn.hutool.core.lang.func.Func0;
import java.io.Serializable;
import java.util.Iterator;
/**
* 缓存接口
*
* @author Looly,jodd
*
* @param <K> 键类型
* @param <V> 值类型
* @author Looly, jodd
*/
public interface Cache<K, V> extends Iterable<V>, Serializable {
/**
* 返回缓存容量,<code>0</code>表示无大小限制
*
*
* @return 返回缓存容量,<code>0</code>表示无大小限制
*/
int capacity();
/**
* 缓存失效时长, <code>0</code> 表示没有设置,单位毫秒
*
*
* @return 缓存失效时长, <code>0</code> 表示没有设置,单位毫秒
*/
long timeout();
/**
* 将对象加入到缓存,使用默认失效时长
*
* @param key 键
*
* @param key
* @param object 缓存的对象
* @see Cache#put(Object, Object, long)
*/
@@ -42,9 +41,9 @@ public interface Cache<K, V> extends Iterable<V>, Serializable {
/**
* 将对象加入到缓存,使用指定失效时长<br>
* 如果缓存空间满了,{@link #prune()} 将被调用以获得空间来存放新对象
*
* @param key 键
* @param object 缓存的对象
*
* @param key
* @param object 缓存的对象
* @param timeout 失效时长,单位毫秒
* @see Cache#put(Object, Object, long)
*/
@@ -56,28 +55,50 @@ public interface Cache<K, V> extends Iterable<V>, Serializable {
* 调用此方法时,会检查上次调用时间,如果与当前时间差值大于超时时间返回<code>null</code>,否则返回值。
* <p>
* 每次调用此方法会刷新最后访问时间,也就是说会重新计算超时时间。
*
*
* @param key 键
* @return 键对应的对象
* @see #get(Object, boolean)
*/
V get(K key);
default V get(K key) {
return get(key, true);
}
/**
* 从缓存中获得对象当对象不在缓存中或已经过期返回Func0回调产生的对象
*
* @param key 键
* <p>
* 调用此方法时,会检查上次调用时间,如果与当前时间差值大于超时时间返回<code>null</code>,否则返回值。
* <p>
* 每次调用此方法会刷新最后访问时间,也就是说会重新计算超时时间。
*
* @param key 键
* @param supplier 如果不存在回调方法,用于生产值对象
* @return 值对象
*/
V get(K key, Func0<V> supplier);
default V get(K key, Func0<V> supplier) {
return get(key, true, supplier);
}
/**
* 从缓存中获得对象当对象不在缓存中或已经过期返回Func0回调产生的对象
* <p>
* 调用此方法时,会检查上次调用时间,如果与当前时间差值大于超时时间返回<code>null</code>,否则返回值。
* <p>
* 每次调用此方法会刷新最后访问时间,也就是说会重新计算超时时间。
*
* @param key 键
* @param isUpdateLastAccess 是否更新最后访问时间,即重新计算超时时间。
* @param supplier 如果不存在回调方法,用于生产值对象
* @return 值对象
*/
V get(K key, boolean isUpdateLastAccess, Func0<V> supplier);
/**
* 从缓存中获得对象,当对象不在缓存中或已经过期返回<code>null</code>
* <p>
* 调用此方法时,会检查上次调用时间,如果与当前时间差值大于超时时间返回<code>null</code>,否则返回值。
*
* @param key 键
*
* @param key
* @param isUpdateLastAccess 是否更新最后访问时间,即重新计算超时时间。
* @return 键对应的对象
*/
@@ -85,7 +106,7 @@ public interface Cache<K, V> extends Iterable<V>, Serializable {
/**
* 返回包含键和值得迭代器
*
*
* @return 缓存对象迭代器
* @since 4.0.10
*/
@@ -93,21 +114,21 @@ public interface Cache<K, V> extends Iterable<V>, Serializable {
/**
* 从缓存中清理过期对象,清理策略取决于具体实现
*
*
* @return 清理的缓存对象个数
*/
int prune();
/**
* 缓存是否已满,仅用于有空间限制的缓存对象
*
*
* @return 缓存是否已满,仅用于有空间限制的缓存对象
*/
boolean isFull();
/**
* 从缓存中移除对象
*
*
* @param key 键
*/
void remove(K key);
@@ -119,21 +140,21 @@ public interface Cache<K, V> extends Iterable<V>, Serializable {
/**
* 缓存的对象数量
*
*
* @return 缓存的对象数量
*/
int size();
/**
* 缓存是否为空
*
*
* @return 缓存是否为空
*/
boolean isEmpty();
/**
* 是否包含key
*
*
* @param key KEY
* @return 是否包含key
*/

View File

@@ -125,13 +125,8 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
}
@Override
public V get(K key) {
return get(key, true);
}
@Override
public V get(K key, Func0<V> supplier) {
V v = get(key);
public V get(K key, boolean isUpdateLastAccess, Func0<V> supplier) {
V v = get(key, isUpdateLastAccess);
if (null == v && null != supplier) {
final long stamp = lock.writeLock();
try {

View File

@@ -52,6 +52,11 @@ public class NoCache<K, V> implements Cache<K, V> {
@Override
public V get(K key, Func0<V> supplier) {
return get(key, true, supplier);
}
@Override
public V get(K key, boolean isUpdateLastAccess, Func0<V> supplier) {
try {
return (null == supplier) ? null : supplier.call();
} catch (Exception e) {

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.4.2</version>
<version>5.4.4-SNAPSHOT</version>
</parent>
<artifactId>hutool-captcha</artifactId>

View File

@@ -1,5 +1,6 @@
package cn.hutool.captcha.generator;
import cn.hutool.core.math.Calculator;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
@@ -59,20 +60,8 @@ public class MathGenerator implements CodeGenerator {
return false;
}
final int a = Integer.parseInt(StrUtil.sub(code, 0, this.numberLength).trim());
final char operator = code.charAt(this.numberLength);
final int b = Integer.parseInt(StrUtil.sub(code, this.numberLength + 1, this.numberLength + 1 + this.numberLength).trim());
switch (operator) {
case '+':
return (a + b) == result;
case '-':
return (a - b) == result;
case '*':
return (a * b) == result;
default:
return false;
}
final int calculateResult = (int) Calculator.conversion(code);
return result == calculateResult;
}
/**

View File

@@ -0,0 +1,14 @@
package cn.hutool.captcha;
import cn.hutool.captcha.generator.MathGenerator;
import org.junit.Test;
public class GeneratorTest {
@Test
public void mathGeneratorTest(){
final MathGenerator mathGenerator = new MathGenerator();
for (int i = 0; i < 1000; i++) {
mathGenerator.verify(mathGenerator.generate(), "0");
}
}
}

View File

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

View File

@@ -109,6 +109,7 @@ public class BeanCopier<T> implements Copier<T>, Serializable {
}
}
}
return this.dest;
}

View File

@@ -1,6 +1,7 @@
package cn.hutool.core.collection;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.comparator.CompareUtil;
import cn.hutool.core.comparator.PinyinComparator;
import cn.hutool.core.comparator.PropertyComparator;
import cn.hutool.core.convert.Convert;
@@ -103,22 +104,22 @@ public class CollUtil {
* @return 并集的集合,返回 {@link ArrayList}
*/
public static <T> Collection<T> union(Collection<T> coll1, Collection<T> coll2) {
final ArrayList<T> list = new ArrayList<>();
if (isEmpty(coll1)) {
list.addAll(coll2);
return new ArrayList<>(coll2);
} else if (isEmpty(coll2)) {
list.addAll(coll1);
} else {
final Map<T, Integer> map1 = countMap(coll1);
final Map<T, Integer> map2 = countMap(coll2);
final Set<T> elts = newHashSet(coll2);
elts.addAll(coll1);
int m;
for (T t : elts) {
m = Math.max(Convert.toInt(map1.get(t), 0), Convert.toInt(map2.get(t), 0));
for (int i = 0; i < m; i++) {
list.add(t);
}
return new ArrayList<>(coll1);
}
final ArrayList<T> list = new ArrayList<>(Math.max(coll1.size(), coll2.size()));
final Map<T, Integer> map1 = countMap(coll1);
final Map<T, Integer> map2 = countMap(coll2);
final Set<T> elts = newHashSet(coll2);
elts.addAll(coll1);
int m;
for (T t : elts) {
m = Math.max(Convert.toInt(map1.get(t), 0), Convert.toInt(map2.get(t), 0));
for (int i = 0; i < m; i++) {
list.add(t);
}
}
return list;
@@ -225,8 +226,8 @@ public class CollUtil {
* @return 交集的集合,返回 {@link ArrayList}
*/
public static <T> Collection<T> intersection(Collection<T> coll1, Collection<T> coll2) {
final ArrayList<T> list = new ArrayList<>();
if (isNotEmpty(coll1) && isNotEmpty(coll2)) {
final ArrayList<T> list = new ArrayList<>(Math.min(coll1.size(), coll2.size()));
final Map<T, Integer> map1 = countMap(coll1);
final Map<T, Integer> map2 = countMap(coll2);
final Set<T> elts = newHashSet(coll2);
@@ -237,8 +238,10 @@ public class CollUtil {
list.add(t);
}
}
return list;
}
return list;
return new ArrayList<>();
}
/**
@@ -414,9 +417,9 @@ public class CollUtil {
/**
* 自定义函数判断集合是否包含某类值
*
* @param collection 集合
* @param collection 集合
* @param containFunc 自定义判断函数
* @param <T> 值类型
* @param <T> 值类型
* @return 是否包含自定义规则的值
*/
public static <T> boolean contains(Collection<T> collection, Predicate<? super T> containFunc) {
@@ -992,8 +995,13 @@ public class CollUtil {
} else if (collectionType.isAssignableFrom(LinkedHashSet.class)) {
list = new LinkedHashSet<>();
} else if (collectionType.isAssignableFrom(TreeSet.class)) {
//noinspection SortedCollectionWithNonComparableKeys
list = new TreeSet<>();
list = new TreeSet<>((o1, o2) -> {
// 优先按照对象本身比较如果没有实现比较接口默认按照toString内容比较
if (o1 instanceof Comparable) {
return ((Comparable<T>) o1).compareTo(o2);
}
return CompareUtil.compare(o1.toString(), o2.toString());
});
} else if (collectionType.isAssignableFrom(EnumSet.class)) {
list = (Collection<T>) EnumSet.noneOf((Class<Enum>) ClassUtil.getTypeArgument(collectionType));
}
@@ -2837,6 +2845,20 @@ public class CollUtil {
}
}
/**
* 使用给定的转换函数,转换源集合为新类型的集合
*
* @param <F> 源元素类型
* @param <T> 目标元素类型
* @param collection 集合
* @param function 转换函数
* @return 新类型的集合
* @since 5.4.3
*/
public static <F, T> Collection<T> trans(Collection<F> collection, Function<? super F, ? extends T> function) {
return new TransCollection<>(collection, function);
}
// ---------------------------------------------------------------------------------------------- Interface start
/**

View File

@@ -807,4 +807,18 @@ public class IterUtil {
public static <T> Iterator<T> empty() {
return Collections.emptyIterator();
}
/**
* 按照给定函数,转换{@link Iterator}为另一种类型的{@link Iterator}
*
* @param <F> 源元素类型
* @param <T> 目标元素类型
* @param iterator 源{@link Iterator}
* @param function 转换函数
* @return 转换后的{@link Iterator}
* @since 5.4.3
*/
public static <F, T> Iterator<T> trans(Iterator<F> iterator, Function<? super F, ? extends T> function) {
return new TransIter<>(iterator, function);
}
}

View File

@@ -149,6 +149,23 @@ public class ListUtil {
return (LinkedList<T>) list(true, values);
}
/**
* 数组转为一个不可变List<br>
* 类似于Java9中的List.of
*
* @param ts 对象
* @param <T> 对象类型
* @return 不可修改List
* @since 5.4.3
*/
@SafeVarargs
public static <T> List<T> of(T... ts) {
if (ArrayUtil.isEmpty(ts)) {
return Collections.emptyList();
}
return Collections.unmodifiableList(toList(ts));
}
/**
* 新建一个CopyOnWriteArrayList
*
@@ -236,7 +253,7 @@ public class ListUtil {
}
}
if((pageNo * pageSize) > resultSize){
if ((pageNo * pageSize) > resultSize) {
// 越界直接返回空
return new ArrayList<>(0);
}
@@ -461,6 +478,9 @@ public class ListUtil {
* @since 5.2.6
*/
public static <T> List<T> unmodifiable(List<T> list) {
if(null == list){
return null;
}
return Collections.unmodifiableList(list);
}

View File

@@ -0,0 +1,26 @@
package cn.hutool.core.collection;
import java.util.Spliterator;
import java.util.function.Function;
/**
* {@link Spliterator}相关工具类
*
* @author looly
* @since 5.4.3
*/
public class SpliteratorUtil {
/**
* 使用给定的转换函数,转换源{@link Spliterator}为新类型的{@link Spliterator}
*
* @param <F> 源元素类型
* @param <T> 目标元素类型
* @param fromSpliterator 源{@link Spliterator}
* @param function 转换函数
* @return 新类型的{@link Spliterator}
*/
public static <F, T> Spliterator<T> trans(Spliterator<F> fromSpliterator, Function<? super F, ? extends T> function) {
return new TransSpliterator<>(fromSpliterator, function);
}
}

View File

@@ -0,0 +1,73 @@
package cn.hutool.core.collection;
import cn.hutool.core.lang.Assert;
import java.util.AbstractCollection;
import java.util.Collection;
import java.util.Iterator;
import java.util.Spliterator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
/**
* 使用给定的转换函数,转换源集合为新类型的集合
*
* @param <F> 源元素类型
* @param <T> 目标元素类型
* @author looly
* @since 5.4.3
*/
public class TransCollection<F, T> extends AbstractCollection<T> {
private final Collection<F> fromCollection;
private final Function<? super F, ? extends T> function;
/**
* 构造
*
* @param fromCollection 源集合
* @param function 转换函数
*/
public TransCollection(Collection<F> fromCollection, Function<? super F, ? extends T> function) {
this.fromCollection = Assert.notNull(fromCollection);
this.function = Assert.notNull(function);
}
@Override
public Iterator<T> iterator() {
return IterUtil.trans(fromCollection.iterator(), function);
}
@Override
public void clear() {
fromCollection.clear();
}
@Override
public boolean isEmpty() {
return fromCollection.isEmpty();
}
@Override
public void forEach(Consumer<? super T> action) {
Assert.notNull(action);
fromCollection.forEach((f) -> action.accept(function.apply(f)));
}
@Override
public boolean removeIf(Predicate<? super T> filter) {
Assert.notNull(filter);
return fromCollection.removeIf(element -> filter.test(function.apply(element)));
}
@Override
public Spliterator<T> spliterator() {
return SpliteratorUtil.trans(fromCollection.spliterator(), function);
}
@Override
public int size() {
return fromCollection.size();
}
}

View File

@@ -0,0 +1,46 @@
package cn.hutool.core.collection;
import cn.hutool.core.lang.Assert;
import java.util.Iterator;
import java.util.function.Function;
/**
* 使用给定的转换函数,转换源{@link Iterator}为新类型的{@link Iterator}
*
* @param <F> 源元素类型
* @param <T> 目标元素类型
* @author looly
* @since 5.4.3
*/
public class TransIter<F, T> implements Iterator<T> {
private final Iterator<? extends F> backingIterator;
private final Function<? super F, ? extends T> func;
/**
* 构造
*
* @param backingIterator 源{@link Iterator}
* @param func 转换函数
*/
public TransIter(Iterator<? extends F> backingIterator, Function<? super F, ? extends T> func) {
this.backingIterator = Assert.notNull(backingIterator);
this.func = Assert.notNull(func);
}
@Override
public final boolean hasNext() {
return backingIterator.hasNext();
}
@Override
public final T next() {
return func.apply(backingIterator.next());
}
@Override
public final void remove() {
backingIterator.remove();
}
}

View File

@@ -0,0 +1,51 @@
package cn.hutool.core.collection;
import java.util.Spliterator;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* 使用给定的转换函数,转换源{@link Spliterator}为新类型的{@link Spliterator}
*
* @param <F> 源元素类型
* @param <T> 目标元素类型
* @author looly
* @since 5.4.3
*/
public class TransSpliterator<F, T> implements Spliterator<T> {
private final Spliterator<F> fromSpliterator;
private final Function<? super F, ? extends T> function;
public TransSpliterator(Spliterator<F> fromSpliterator, Function<? super F, ? extends T> function) {
this.fromSpliterator = fromSpliterator;
this.function = function;
}
@Override
public boolean tryAdvance(Consumer<? super T> action) {
return fromSpliterator.tryAdvance(
fromElement -> action.accept(function.apply(fromElement)));
}
@Override
public void forEachRemaining(Consumer<? super T> action) {
fromSpliterator.forEachRemaining(fromElement -> action.accept(function.apply(fromElement)));
}
@Override
public Spliterator<T> trySplit() {
Spliterator<F> fromSplit = fromSpliterator.trySplit();
return (fromSplit != null) ? new TransSpliterator<>(fromSplit, function) : null;
}
@Override
public long estimateSize() {
return fromSpliterator.estimateSize();
}
@Override
public int characteristics() {
return fromSpliterator.characteristics()
& ~(Spliterator.DISTINCT | Spliterator.NONNULL | Spliterator.SORTED);
}
}

View File

@@ -4,6 +4,7 @@ import cn.hutool.core.lang.Chain;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Comparator;
import java.util.Iterator;
@@ -14,20 +15,92 @@ import java.util.Objects;
* 比较器链。此链包装了多个比较器,最终比较结果按照比较器顺序综合多个比较器结果。<br>
* 按照比较器链的顺序分别比较,如果比较出相等则转向下一个比较器,否则直接返回<br>
* 此类copy from Apache-commons-collections
*
*
* @author looly
* @since 3.0.7
*/
public class ComparatorChain<E> implements Chain<Comparator<E>, ComparatorChain<E>>, Comparator<E>, Serializable {
private static final long serialVersionUID = -2426725788913962429L;
/** 比较器链. */
/**
* 比较器链.
*/
private final List<Comparator<E>> chain;
/** 对应比较器位置是否反序. */
/**
* 对应比较器位置是否反序.
*/
private final BitSet orderingBits;
/** 比较器是否被锁定。锁定的比较器链不能再添加新的比较器。比较器会在开始比较时开始加锁。 */
/**
* 比较器是否被锁定。锁定的比较器链不能再添加新的比较器。比较器会在开始比较时开始加锁。
*/
private boolean lock = false;
//------------------------------------------------------------------------------------- Static method start
/**
* 构建 {@link ComparatorChain}
*
* @param <E> 被比较对象类型
* @param comparator 比较器
* @return {@link ComparatorChain}
* @since 5.4.3
*/
public static <E> ComparatorChain<E> of(Comparator<E> comparator) {
return of(comparator, false);
}
/**
* 构建 {@link ComparatorChain}
*
* @param <E> 被比较对象类型
* @param comparator 比较器
* @param reverse 是否反向
* @return {@link ComparatorChain}
* @since 5.4.3
*/
public static <E> ComparatorChain<E> of(Comparator<E> comparator, boolean reverse) {
return new ComparatorChain<>(comparator, reverse);
}
/**
* 构建 {@link ComparatorChain}
*
* @param <E> 被比较对象类型
* @param comparators 比较器数组
* @return {@link ComparatorChain}
* @since 5.4.3
*/
@SafeVarargs
public static <E> ComparatorChain<E> of(Comparator<E>... comparators) {
return of(Arrays.asList(comparators));
}
/**
* 构建 {@link ComparatorChain}
*
* @param <E> 被比较对象类型
* @param comparators 比较器列表
* @return {@link ComparatorChain}
* @since 5.4.3
*/
public static <E> ComparatorChain<E> of(List<Comparator<E>> comparators) {
return new ComparatorChain<>(comparators);
}
/**
* 构建 {@link ComparatorChain}
*
* @param <E> 被比较对象类型
* @param comparators 比较器列表
* @param bits {@link Comparator} 列表对应的排序boolean值true表示正序false反序
* @return {@link ComparatorChain}
* @since 5.4.3
*/
public static <E> ComparatorChain<E> of(List<Comparator<E>> comparators, BitSet bits) {
return new ComparatorChain<>(comparators, bits);
}
//------------------------------------------------------------------------------------- Static method start
/**
* 构造空的比较器链必须至少有一个比较器否则会在compare时抛出{@link UnsupportedOperationException}
*/
@@ -36,7 +109,7 @@ public class ComparatorChain<E> implements Chain<Comparator<E>, ComparatorChain<
}
/**
*构造,初始化单一比较器。比较器为正序
* 构造,初始化单一比较器。比较器为正序
*
* @param comparator 在比较器链中的第一个比较器
*/
@@ -48,7 +121,7 @@ public class ComparatorChain<E> implements Chain<Comparator<E>, ComparatorChain<
* 构造,初始化单一比较器。自定义正序还是反序
*
* @param comparator 在比较器链中的第一个比较器
* @param reverse 是否反序true表示反序false正序
* @param reverse 是否反序true表示反序false正序
*/
public ComparatorChain(final Comparator<E> comparator, final boolean reverse) {
chain = new ArrayList<>(1);
@@ -61,9 +134,9 @@ public class ComparatorChain<E> implements Chain<Comparator<E>, ComparatorChain<
/**
* 构造,使用已有的比较器列表
*
*
* @param list 比较器列表
* @see #ComparatorChain(List,BitSet)
* @see #ComparatorChain(List, BitSet)
*/
public ComparatorChain(final List<Comparator<E>> list) {
this(list, new BitSet(list.size()));
@@ -81,7 +154,6 @@ public class ComparatorChain<E> implements Chain<Comparator<E>, ComparatorChain<
orderingBits = bits;
}
// -----------------------------------------------------------------------
/**
* 在链的尾部添加比较器,使用正向排序
*
@@ -96,7 +168,7 @@ public class ComparatorChain<E> implements Chain<Comparator<E>, ComparatorChain<
* 在链的尾部添加比较器,使用给定排序方式
*
* @param comparator {@link Comparator} 比较器
* @param reverse 是否反序true表示正序false反序
* @param reverse 是否反序true表示正序false反序
* @return this
*/
public ComparatorChain<E> addComparator(final Comparator<E> comparator, final boolean reverse) {
@@ -112,10 +184,10 @@ public class ComparatorChain<E> implements Chain<Comparator<E>, ComparatorChain<
/**
* 替换指定位置的比较器,保持原排序方式
*
* @param index 位置
* @param index 位置
* @param comparator {@link Comparator}
* @return this
* @exception IndexOutOfBoundsException if index &lt; 0 or index &gt;= size()
* @throws IndexOutOfBoundsException if index &lt; 0 or index &gt;= size()
*/
public ComparatorChain<E> setComparator(final int index, final Comparator<E> comparator) throws IndexOutOfBoundsException {
return setComparator(index, comparator, false);
@@ -124,9 +196,9 @@ public class ComparatorChain<E> implements Chain<Comparator<E>, ComparatorChain<
/**
* 替换指定位置的比较器,替换指定排序方式
*
* @param index 位置
* @param index 位置
* @param comparator {@link Comparator}
* @param reverse 是否反序true表示正序false反序
* @param reverse 是否反序true表示正序false反序
* @return this
*/
public ComparatorChain<E> setComparator(final int index, final Comparator<E> comparator, final boolean reverse) {
@@ -176,12 +248,13 @@ public class ComparatorChain<E> implements Chain<Comparator<E>, ComparatorChain<
/**
* 是否已经被锁定。当开始比较时调用compare方法此值为true
*
* @return true = ComparatorChain cannot be modified; false = ComparatorChain can still be modified.
*/
public boolean isLocked() {
return lock;
}
@Override
public Iterator<Comparator<E>> iterator() {
return this.chain.iterator();
@@ -191,7 +264,7 @@ public class ComparatorChain<E> implements Chain<Comparator<E>, ComparatorChain<
public ComparatorChain<E> addChain(Comparator<E> element) {
return this.addComparator(element);
}
/**
* 执行比较<br>
* 按照比较器链的顺序分别比较,如果比较出相等则转向下一个比较器,否则直接返回
@@ -207,7 +280,7 @@ public class ComparatorChain<E> implements Chain<Comparator<E>, ComparatorChain<
checkChainIntegrity();
lock = true;
}
final Iterator<Comparator<E>> comparators = chain.iterator();
Comparator<? super E> comparator;
int retval;
@@ -257,6 +330,7 @@ public class ComparatorChain<E> implements Chain<Comparator<E>, ComparatorChain<
}
//------------------------------------------------------------------------------------------------------------------------------- Private method start
/**
* 被锁定时抛出异常
*
@@ -270,7 +344,7 @@ public class ComparatorChain<E> implements Chain<Comparator<E>, ComparatorChain<
/**
* 检查比较器链是否为空,为空抛出异常
*
*
* @throws UnsupportedOperationException 为空抛出此异常
*/
private void checkChainIntegrity() {

View File

@@ -1,6 +1,8 @@
package cn.hutool.core.comparator;
import java.util.Comparator;
import java.util.Objects;
import java.util.function.Function;
/**
* 比较工具类
@@ -108,4 +110,34 @@ public class CompareUtil {
return result;
}
/**
* 中文比较器
*
* @param keyExtractor 从对象中提取中文(参与比较的内容)
* @param <T> 对象类型
* @return 中文比较器
* @since 5.4.3
*/
public static <T> Comparator<T> comparingPinyin(Function<T, String> keyExtractor) {
return comparingPinyin(keyExtractor, false);
}
/**
* 中文比较器
*
* @param keyExtractor 从对象中提取中文(参与比较的内容)
* @param reverse 是否反序
* @param <T> 对象类型
* @return 中文比较器
* @since 5.4.3
*/
public static <T> Comparator<T> comparingPinyin(Function<T, String> keyExtractor, boolean reverse) {
Objects.requireNonNull(keyExtractor);
PinyinComparator pinyinComparator = new PinyinComparator();
if (reverse) {
return (o1, o2) -> pinyinComparator.compare(keyExtractor.apply(o2), keyExtractor.apply(o1));
}
return (o1, o2) -> pinyinComparator.compare(keyExtractor.apply(o1), keyExtractor.apply(o2));
}
}

View File

@@ -1,13 +1,13 @@
package cn.hutool.core.comparator;
import java.io.Serializable;
import java.util.Comparator;
import java.util.List;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import java.io.Serializable;
import java.util.Comparator;
import java.util.List;
/**
* 版本比较器<br>
* 比较两个版本的大小<br>
@@ -85,19 +85,4 @@ public class VersionComparator implements Comparator<String>, Serializable {
// 如果已经分出大小,则直接返回,如果未分出大小,则再比较位数,有子版本的为大;
return (diff != 0) ? diff : v1s.size() - v2s.size();
}
@Override
public boolean equals(final Object object) {
if (this == object) {
return true;
}
if (null == object) {
return false;
}
if (object.getClass().equals(this.getClass())) {
final VersionComparator other = (VersionComparator) object;
return this.equals(other);
}
return false;
}
}

View File

@@ -106,9 +106,9 @@ public class ConverterRegistry implements Serializable {
}
/**
* 获得单例的 {@link ConverterRegistry}
* 获得单例的 ConverterRegistry
*
* @return {@link ConverterRegistry}
* @return ConverterRegistry
*/
public static ConverterRegistry getInstance() {
return SingletonHolder.INSTANCE;
@@ -140,7 +140,7 @@ public class ConverterRegistry implements Serializable {
*
* @param type 转换的目标类型
* @param converterClass 转换器类,必须有默认构造方法
* @return {@link ConverterRegistry}
* @return ConverterRegistry
*/
public ConverterRegistry putCustom(Type type, Class<? extends Converter<?>> converterClass) {
return putCustom(type, ReflectUtil.newInstance(converterClass));
@@ -151,7 +151,7 @@ public class ConverterRegistry implements Serializable {
*
* @param type 转换的目标类型
* @param converter 转换器
* @return {@link ConverterRegistry}
* @return ConverterRegistry
*/
public ConverterRegistry putCustom(Type type, Converter<?> converter) {
if (null == customConverterMap) {
@@ -257,6 +257,7 @@ public class ConverterRegistry implements Serializable {
}
}
// 特殊类型转换包括Collection、Map、强转、Array等
final T result = convertSpecial(type, rowType, value, defaultValue);
if (null != result) {
@@ -269,7 +270,7 @@ public class ConverterRegistry implements Serializable {
}
// 无法转换
throw new ConvertException("No Converter for type [{}]", rowType.getName());
throw new ConvertException("Can not Converter from [{}] to [{}]", value.getClass().getName(), type.getTypeName());
}
/**
@@ -350,11 +351,7 @@ public class ConverterRegistry implements Serializable {
// 数组转换
if (rowType.isArray()) {
final ArrayConverter arrayConverter = new ArrayConverter(rowType);
try {
return (T) arrayConverter.convert(value, defaultValue);
} catch (Exception e) {
// 数组转换失败进行下一步
}
return (T) arrayConverter.convert(value, defaultValue);
}
// 表示非需要特殊转换的对象

View File

@@ -2,7 +2,7 @@ package cn.hutool.core.convert.impl;
import cn.hutool.core.collection.IterUtil;
import cn.hutool.core.convert.AbstractConverter;
import cn.hutool.core.convert.ConverterRegistry;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
@@ -15,35 +15,54 @@ import java.util.List;
/**
* 数组转换器,包括原始类型数组
*
*
* @author Looly
*/
public class ArrayConverter extends AbstractConverter<Object> {
private static final long serialVersionUID = 1L;
private final Class<?> targetType;
/** 目标元素类型 */
/**
* 目标元素类型
*/
private final Class<?> targetComponentType;
/**
* 是否忽略元素转换错误
*/
private boolean ignoreElementError;
/**
* 构造
*
*
* @param targetType 目标数组类型
*/
public ArrayConverter(Class<?> targetType) {
this(targetType, false);
}
/**
* 构造
*
* @param targetType 目标数组类型
* @param ignoreElementError 是否忽略元素转换错误
*/
public ArrayConverter(Class<?> targetType, boolean ignoreElementError) {
if (null == targetType) {
// 默认Object数组
targetType = Object[].class;
}
if(targetType.isArray()) {
if (targetType.isArray()) {
this.targetType = targetType;
this.targetComponentType = targetType.getComponentType();
}else {
} else {
//用户传入类为非数组时,按照数组元素类型对待
this.targetComponentType = targetType;
this.targetType = ArrayUtil.getArrayType(targetType);
}
this.ignoreElementError = ignoreElementError;
}
@Override
@@ -51,16 +70,27 @@ public class ArrayConverter extends AbstractConverter<Object> {
return value.getClass().isArray() ? convertArrayToArray(value) : convertObjectToArray(value);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@SuppressWarnings({"unchecked", "rawtypes"})
@Override
public Class getTargetType() {
return this.targetType;
}
/**
* 设置是否忽略元素转换错误
*
* @param ignoreElementError 是否忽略元素转换错误
* @since 5.4.3
*/
public void setIgnoreElementError(boolean ignoreElementError) {
this.ignoreElementError = ignoreElementError;
}
// -------------------------------------------------------------------------------------- Private method start
/**
* 数组对数组转换
*
*
* @param array 被转换的数组值
* @return 转换后的数组
*/
@@ -74,16 +104,15 @@ public class ArrayConverter extends AbstractConverter<Object> {
final int len = ArrayUtil.length(array);
final Object result = Array.newInstance(targetComponentType, len);
final ConverterRegistry converter = ConverterRegistry.getInstance();
for (int i = 0; i < len; i++) {
Array.set(result, i, converter.convert(targetComponentType, Array.get(array, i)));
Array.set(result, i, convertComponentType(Array.get(array, i)));
}
return result;
}
/**
* 非数组对数组转换
*
*
* @param value 被转换值
* @return 转换后的数组
*/
@@ -98,14 +127,13 @@ public class ArrayConverter extends AbstractConverter<Object> {
return convertArrayToArray(strings);
}
final ConverterRegistry converter = ConverterRegistry.getInstance();
Object result;
if (value instanceof List) {
// List转数组
final List<?> list = (List<?>) value;
result = Array.newInstance(targetComponentType, list.size());
for (int i = 0; i < list.size(); i++) {
Array.set(result, i, converter.convert(targetComponentType, list.get(i)));
Array.set(result, i, convertComponentType(list.get(i)));
}
} else if (value instanceof Collection) {
// 集合转数组
@@ -114,7 +142,7 @@ public class ArrayConverter extends AbstractConverter<Object> {
int i = 0;
for (Object element : collection) {
Array.set(result, i, converter.convert(targetComponentType, element));
Array.set(result, i, convertComponentType(element));
i++;
}
} else if (value instanceof Iterable) {
@@ -122,16 +150,16 @@ public class ArrayConverter extends AbstractConverter<Object> {
final List<?> list = IterUtil.toList((Iterable<?>) value);
result = Array.newInstance(targetComponentType, list.size());
for (int i = 0; i < list.size(); i++) {
Array.set(result, i, converter.convert(targetComponentType, list.get(i)));
Array.set(result, i, convertComponentType(list.get(i)));
}
} else if (value instanceof Iterator) {
// 可循环对象转数组可循环对象无法获取长度因此先转为List后转为数组
final List<?> list = IterUtil.toList((Iterator<?>) value);
result = Array.newInstance(targetComponentType, list.size());
for (int i = 0; i < list.size(); i++) {
Array.set(result, i, converter.convert(targetComponentType, list.get(i)));
Array.set(result, i, convertComponentType(list.get(i)));
}
}else if (value instanceof Serializable && byte.class == targetComponentType) {
} else if (value instanceof Serializable && byte.class == targetComponentType) {
// 用户可能想序列化指定对象
result = ObjectUtil.serialize(value);
} else {
@@ -144,14 +172,25 @@ public class ArrayConverter extends AbstractConverter<Object> {
/**
* 单元素数组
*
*
* @param value 被转换的值
* @return 数组,只包含一个元素
*/
private Object[] convertToSingleElementArray(Object value) {
final Object[] singleElementArray = ArrayUtil.newArray(targetComponentType, 1);
singleElementArray[0] = ConverterRegistry.getInstance().convert(targetComponentType, value);
singleElementArray[0] = convertComponentType(value);
return singleElementArray;
}
/**
* 转换元素类型
*
* @param value 值
* @return 转换后的值,转换失败若{@link #ignoreElementError}为true返回null否则抛出异常
* @since 5.4.3
*/
private Object convertComponentType(Object value) {
return Convert.convertWithCheck(this.targetComponentType, value, null, this.ignoreElementError);
}
// -------------------------------------------------------------------------------------- Private method end
}

View File

@@ -2,6 +2,7 @@ package cn.hutool.core.convert.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Converter;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.TypeUtil;
import java.lang.reflect.Type;
@@ -60,13 +61,8 @@ public class CollectionConverter implements Converter<Collection<?>> {
@Override
public Collection<?> convert(Object value, Collection<?> defaultValue) throws IllegalArgumentException {
Collection<?> result;
try {
result = convertInternal(value);
} catch (RuntimeException e) {
return defaultValue;
}
return ((null == result) ? defaultValue : result);
final Collection<?> result = convertInternal(value);
return ObjectUtil.defaultIfNull(result, defaultValue);
}
/**

View File

@@ -207,7 +207,16 @@ public class NumberConverter extends AbstractConverter<Number> {
@Override
protected String convertToStr(Object value) {
return StrUtil.trim(super.convertToStr(value));
String result = StrUtil.trim(super.convertToStr(value));
if(StrUtil.isNotEmpty(result)){
final char c = Character.toUpperCase(result.charAt(result.length() - 1));
if(c == 'D' || c == 'L' || c == 'F'){
// 类型标识形式例如123.6D
return StrUtil.subPre(result, -1);
}
}
return result;
}
@Override

View File

@@ -214,9 +214,14 @@ public class DateUtil extends CalendarUtil {
/**
* 获得指定日期是所在年份的第几周<br>
* 此方法返回值与一周的第一天有关,比如:<br>
* 2016年1月3日为周日如果一周的第一天为周日那这天是第二周返回2<br>
* 如果一周的第一天为周一那这天是第一周返回1<br>
* 跨年的那个星期得到的结果总是1
*
* @param date 日期
* @return 周
* @see DateTime#setFirstDayOfWeek(Week)
*/
public static int weekOfYear(Date date) {
return DateTime.of(date).weekOfYear();
@@ -1383,30 +1388,6 @@ public class DateUtil extends CalendarUtil {
return new DateBetween(beginDate, endDate).betweenMonth(isReset);
}
/**
* 获取两个日期之间所有的月份
* @param start 开始时间
* @param end 结束时间
* @return List<String> 格式为yyyMM格式的月份列表 包含收尾</>
* @since 5.4.4
*/
public static List<String> getBetweenMonths(Date start, Date end) {
List<String> result = new ArrayList<>();
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM");
Calendar tempStart = Calendar.getInstance();
tempStart.setTime(start);
// 加一个月,保证开始和结束同步时返回当月
tempStart.add(Calendar.MONTH, 1);
Calendar tempEnd = Calendar.getInstance();
tempEnd.setTime(end);
result.add(sdf.format(start));
while (tempStart.before(tempEnd) || tempStart.equals(tempEnd)) {
result.add(sdf.format(tempStart.getTime()));
tempStart.add(Calendar.MONTH, 1);
}
return result;
}
/**
* 计算两个日期相差年数<br>
* 在非重置情况下如果起始日期的月小于结束日期的月年数要少算1不足1年

View File

@@ -1,6 +1,8 @@
package cn.hutool.core.date;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import java.time.Duration;
import java.time.Instant;
@@ -10,6 +12,7 @@ import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalUnit;
@@ -245,7 +248,29 @@ public class LocalDateTimeUtil {
if (null == text) {
return null;
}
return parse(text, DateTimeFormatter.ofPattern(format));
DateTimeFormatter formatter = null;
if(StrUtil.isNotBlank(format)){
// 修复yyyyMMddHHmmssSSS格式不能解析的问题
// fix issue#1082
//see https://stackoverflow.com/questions/22588051/is-java-time-failing-to-parse-fraction-of-second
// jdk8 bug at: https://bugs.openjdk.java.net/browse/JDK-8031085
if(StrUtil.startWithIgnoreEquals(format, DatePattern.PURE_DATETIME_PATTERN)){
final String fraction = StrUtil.removePrefix(format, DatePattern.PURE_DATETIME_PATTERN);
if(ReUtil.isMatch("[S]{1,2}", fraction)){
//将yyyyMMddHHmmssS、yyyyMMddHHmmssSS的日期统一替换为yyyyMMddHHmmssSSS格式用0补
text += StrUtil.repeat('0', 3-fraction.length());
}
formatter = new DateTimeFormatterBuilder()
.appendPattern(DatePattern.PURE_DATETIME_PATTERN)
.appendValue(ChronoField.MILLI_OF_SECOND, 3)
.toFormatter();
} else{
formatter = DateTimeFormatter.ofPattern(format);
}
}
return parse(text, formatter);
}
/**
@@ -403,7 +428,7 @@ public class LocalDateTimeUtil {
* @return 一天的开始时间
*/
public static LocalDateTime beginOfDay(LocalDateTime time) {
return time.with(LocalTime.of(0, 0, 0, 0));
return time.with(LocalTime.MIN);
}
/**
@@ -413,7 +438,7 @@ public class LocalDateTimeUtil {
* @return 一天的结束时间
*/
public static LocalDateTime endOfDay(LocalDateTime time) {
return time.with(LocalTime.of(23, 59, 59, 999_999_999));
return time.with(LocalTime.MAX);
}
/**

View File

@@ -14,10 +14,12 @@ public class ChineseMonth {
/**
* 当前农历月份是否为闰月
*
* @param year 农历年
* @param month 农历月
* @return 是否为闰月
* @since 5.4.2
*/
public static boolean isLeapMonth(int year, int month){
public static boolean isLeapMonth(int year, int month) {
return month == LunarInfo.leapMonth(year);
}
@@ -26,7 +28,7 @@ public class ChineseMonth {
* 当为传统表示时,表示为二月,腊月,或者润正月等
* 当为非传统表示时,二月,十二月,或者润一月等
*
* @param isLeapMonth 是否闰月
* @param isLeapMonth 是否闰月
* @param month 月份从1开始
* @param isTraditional 是否传统表示,例如一月传统表示为正月
* @return 返回农历月份称呼

View File

@@ -22,7 +22,7 @@ public class LunarFestival {
lFtv.put(new Pair<>(1, 3), "猪日");
lFtv.put(new Pair<>(1, 4), "羊日");
lFtv.put(new Pair<>(1, 5), "牛日 破五日");
lFtv.put(new Pair<>(1, 6), "马日送穷日");
lFtv.put(new Pair<>(1, 6), "马日 送穷日");
lFtv.put(new Pair<>(1, 7), "人日 人胜节");
lFtv.put(new Pair<>(1, 8), "谷日 八仙日");
lFtv.put(new Pair<>(1, 9), "天日 九皇会");

View File

@@ -392,35 +392,36 @@ public class ImgUtil {
int srcWidth = srcImage.getWidth(null); // 源图宽度
int srcHeight = srcImage.getHeight(null); // 源图高度
try {
if (srcWidth > destWidth && srcHeight > destHeight) {
int cols; // 切片横向数量
int rows; // 切片纵向数量
// 计算切片的横向和纵向数量
if (srcWidth % destWidth == 0) {
cols = srcWidth / destWidth;
} else {
cols = (int) Math.floor((double) srcWidth / destWidth) + 1;
}
if (srcHeight % destHeight == 0) {
rows = srcHeight / destHeight;
} else {
rows = (int) Math.floor((double) srcHeight / destHeight) + 1;
}
// 循环建立切片
Image tag;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
// 四个参数分别为图像起点坐标和宽高
// 即: CropImageFilter(int x,int y,int width,int height)
tag = cut(srcImage, new Rectangle(j * destWidth, i * destHeight, destWidth, destHeight));
// 输出为文件
ImageIO.write(toRenderedImage(tag), IMAGE_TYPE_JPEG, new File(descDir, "_r" + i + "_c" + j + ".jpg"));
}
}
if(srcWidth < destWidth){
destWidth = srcWidth;
}
if(srcHeight < destHeight){
destHeight = srcHeight;
}
int cols; // 切片横向数量
int rows; // 切片纵向数量
// 计算切片的横向和纵向数量
if (srcWidth % destWidth == 0) {
cols = srcWidth / destWidth;
} else {
cols = (int) Math.floor((double) srcWidth / destWidth) + 1;
}
if (srcHeight % destHeight == 0) {
rows = srcHeight / destHeight;
} else {
rows = (int) Math.floor((double) srcHeight / destHeight) + 1;
}
// 循环建立切片
Image tag;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
// 四个参数分别为图像起点坐标和宽高
// 即: CropImageFilter(int x,int y,int width,int height)
tag = cut(srcImage, new Rectangle(j * destWidth, i * destHeight, destWidth, destHeight));
// 输出为文件
write(tag, FileUtil.file(descDir, "_r" + i + "_c" + j + ".jpg"));
}
} catch (IOException e) {
throw new IORuntimeException(e);
}
}
@@ -463,9 +464,9 @@ public class ImgUtil {
cols = 2; // 切片列数
}
// 读取源图像
final Image bi = toBufferedImage(srcImage);
int srcWidth = bi.getWidth(null); // 源图宽度
int srcHeight = bi.getHeight(null); // 源图高度
final BufferedImage bi = toBufferedImage(srcImage);
int srcWidth = bi.getWidth(); // 源图宽度
int srcHeight = bi.getHeight(); // 源图高度
int destWidth = NumberUtil.partValue(srcWidth, cols); // 每张切片的宽度
int destHeight = NumberUtil.partValue(srcHeight, rows); // 每张切片的高度

View File

@@ -1005,20 +1005,14 @@ public class FileUtil extends PathUtil {
}
/**
* 修改文件或目录的文件名,不变更路径,只是简单修改文件名<br>
* 重命名有两种模式:<br>
* 1、isRetainExt为true时保留原扩展名
* 修改文件或目录的文件名,不变更路径,只是简单修改文件名,不保留扩展名。<br>
*
* <pre>
* FileUtil.rename(file, "aaa", true) xx/xx.png =》xx/aaa.png
* </pre>
*
* <pre>
* FileUtil.rename(file, "aaa.jpg", false) xx/xx.png =》xx/aaa.jpg
* FileUtil.rename(file, "aaa.png", true) xx/xx.png =》xx/aaa.png
* </pre>
*
* @param file 被修改的文件
* @param newName 新的文件名,包括扩展名
* @param newName 新的文件名,如需扩展名,需自行在此参数加上,原文件名的扩展名不会被保留
* @param isOverride 是否覆盖目标文件
* @return 目标文件
* @since 5.3.6
@@ -1035,6 +1029,7 @@ public class FileUtil extends PathUtil {
* <pre>
* FileUtil.rename(file, "aaa", true) xx/xx.png =》xx/aaa.png
* </pre>
*
* <p>
* 2、isRetainExt为false时不保留原扩展名需要在newName中
*
@@ -2441,6 +2436,20 @@ public class FileUtil extends PathUtil {
return new PrintWriter(getWriter(file, charset, isAppend));
}
/**
* 获得一个打印写入对象可以有print
*
* @param file 文件
* @param charset 字符集
* @param isAppend 是否追加
* @return 打印对象
* @throws IORuntimeException IO异常
* @since 5.4.3
*/
public static PrintWriter getPrintWriter(File file, Charset charset, boolean isAppend) throws IORuntimeException {
return new PrintWriter(getWriter(file, charset, isAppend));
}
/**
* 获取当前系统的换行分隔符
*

View File

@@ -421,6 +421,18 @@ public class IoUtil {
// -------------------------------------------------------------------------------------- read start
/**
* 从流中读取UTF8编码的内容
*
* @param in 输入流
* @return 内容
* @throws IORuntimeException IO异常
* @since 5.4.4
*/
public static String readUtf8(InputStream in) throws IORuntimeException {
return read(in, CharsetUtil.CHARSET_UTF_8);
}
/**
* 从流中读取内容
*
@@ -1251,7 +1263,7 @@ public class IoUtil {
* @throws IORuntimeException IO异常
* @since 5.4.0
*/
public static long checksumValue(InputStream in, Checksum checksum){
public static long checksumValue(InputStream in, Checksum checksum) {
return checksum(in, checksum).getValue();
}
}

View File

@@ -1,5 +1,6 @@
package cn.hutool.core.lang;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.StrUtil;
@@ -11,14 +12,16 @@ import static java.lang.System.out;
/**
* 命令行(控制台)工具方法类<br>
* 此类主要针对{@link System#out} 和 {@link System#err} 做封装。
*
* @author Looly
*
* @author Looly
*/
public class Console {
private static final String TEMPLATE_VAR = "{}";
// --------------------------------------------------------------------------------- Log
/**
* 同 System.out.println()方法,打印控制台日志
*/
@@ -29,79 +32,55 @@ public class Console {
/**
* 同 System.out.println()方法,打印控制台日志<br>
* 如果传入打印对象为{@link Throwable}对象,那么同时打印堆栈
*
*
* @param obj 要打印的对象
*/
public static void log(Object obj) {
if (obj instanceof Throwable) {
Throwable e = (Throwable) obj;
final Throwable e = (Throwable) obj;
log(e, e.getMessage());
} else {
log("{}", obj);
log(TEMPLATE_VAR, obj);
}
}
/**
* 同 System.out.print()方法,打印控制台日志
*
* @param obj 要打印的对象
* @since 3.3.1
* 同 System.out.println()方法,打印控制台日志<br>
* 如果传入打印对象为{@link Throwable}对象,那么同时打印堆栈
*
* @param obj1 第一个要打印的对象
* @param otherObjs 其它要打印的对象
* @since 5.4.3
*/
public static void print(Object obj) {
print("{}", obj);
public static void log(Object obj1, Object... otherObjs) {
if(ArrayUtil.isEmpty(otherObjs)){
log(obj1);
} else{
log(buildTemplateSplitBySpace(otherObjs.length + 1), ArrayUtil.insert(otherObjs, 0, obj1));
}
}
/**
* 打印进度条
*
* @param showChar 进度条提示字符,例如“#”
* @param len 打印长度
* @since 4.5.6
*/
public static void printProgress(char showChar, int len) {
print("{}{}", CharUtil.CR, StrUtil.repeat(showChar, len));
}
/**
* 打印进度条
*
* @param showChar 进度条提示字符,例如“#”
* @param totalLen 总长度
* @param rate 总长度所占比取值0~1
* @since 4.5.6
*/
public static void printProgress(char showChar, int totalLen, double rate) {
Assert.isTrue(rate >= 0 && rate <= 1, "Rate must between 0 and 1 (both include)");
printProgress(showChar, (int) (totalLen * rate));
}
/**
* 同 System.out.println()方法,打印控制台日志
*
* 同 System.out.println()方法,打印控制台日志<br>
* 当传入template无"{}"时,被认为非模板,直接打印多个参数以空格分隔
*
* @param template 文本模板,被替换的部分用 {} 表示
* @param values 值
* @param values
*/
public static void log(String template, Object... values) {
log(null, template, values);
}
/**
* 同 System.out.print()方法,打印控制台日志
*
* @param template 文本模板,被替换的部分用 {} 表示
* @param values 值
* @since 3.3.1
*/
public static void print(String template, Object... values) {
out.print(StrUtil.format(template, values));
if (ArrayUtil.isEmpty(values) || StrUtil.contains(template, TEMPLATE_VAR)) {
logInternal(template, values);
} else {
logInternal(buildTemplateSplitBySpace(values.length + 1), ArrayUtil.insert(values, 0, template));
}
}
/**
* 同 System.out.println()方法,打印控制台日志
*
* @param t 异常对象
*
* @param t 异常对象
* @param template 文本模板,被替换的部分用 {} 表示
* @param values 值
* @param values
*/
public static void log(Throwable t, String template, Object... values) {
out.println(StrUtil.format(template, values));
@@ -111,7 +90,96 @@ public class Console {
}
}
/**
* 同 System.out.println()方法,打印控制台日志
*
* @param template 文本模板,被替换的部分用 {} 表示
* @param values 值
* @since 5.4.3
*/
private static void logInternal(String template, Object... values){
log(null, template, values);
}
// --------------------------------------------------------------------------------- print
/**
* 同 System.out.print()方法,打印控制台日志
*
* @param obj 要打印的对象
* @since 3.3.1
*/
public static void print(Object obj) {
print(TEMPLATE_VAR, obj);
}
/**
* 同 System.out.println()方法,打印控制台日志<br>
* 如果传入打印对象为{@link Throwable}对象,那么同时打印堆栈
*
* @param obj1 第一个要打印的对象
* @param otherObjs 其它要打印的对象
* @since 5.4.3
*/
public static void print(Object obj1, Object... otherObjs) {
if(ArrayUtil.isEmpty(otherObjs)){
print(obj1);
} else{
print(buildTemplateSplitBySpace(otherObjs.length + 1), ArrayUtil.insert(otherObjs, 0, obj1));
}
}
/**
* 同 System.out.print()方法,打印控制台日志
*
* @param template 文本模板,被替换的部分用 {} 表示
* @param values 值
* @since 3.3.1
*/
public static void print(String template, Object... values) {
if (ArrayUtil.isEmpty(values) || StrUtil.contains(template, TEMPLATE_VAR)) {
printInternal(template, values);
} else {
printInternal(buildTemplateSplitBySpace(values.length + 1), ArrayUtil.insert(values, 0, template));
}
}
/**
* 打印进度条
*
* @param showChar 进度条提示字符,例如“#”
* @param len 打印长度
* @since 4.5.6
*/
public static void printProgress(char showChar, int len) {
print("{}{}", CharUtil.CR, StrUtil.repeat(showChar, len));
}
/**
* 打印进度条
*
* @param showChar 进度条提示字符,例如“#”
* @param totalLen 总长度
* @param rate 总长度所占比取值0~1
* @since 4.5.6
*/
public static void printProgress(char showChar, int totalLen, double rate) {
Assert.isTrue(rate >= 0 && rate <= 1, "Rate must between 0 and 1 (both include)");
printProgress(showChar, (int) (totalLen * rate));
}
/**
* 同 System.out.println()方法,打印控制台日志
*
* @param template 文本模板,被替换的部分用 {} 表示
* @param values 值
* @since 5.4.3
*/
private static void printInternal(String template, Object... values){
out.print(StrUtil.format(template, values));
}
// --------------------------------------------------------------------------------- Error
/**
* 同 System.err.println()方法,打印控制台日志
*/
@@ -121,7 +189,7 @@ public class Console {
/**
* 同 System.err.println()方法,打印控制台日志
*
*
* @param obj 要打印的对象
*/
public static void error(Object obj) {
@@ -129,26 +197,46 @@ public class Console {
Throwable e = (Throwable) obj;
error(e, e.getMessage());
} else {
error("{}", obj);
error(TEMPLATE_VAR, obj);
}
}
/**
* 同 System.out.println()方法,打印控制台日志<br>
* 如果传入打印对象为{@link Throwable}对象,那么同时打印堆栈
*
* @param obj1 第一个要打印的对象
* @param otherObjs 其它要打印的对象
* @since 5.4.3
*/
public static void error(Object obj1, Object... otherObjs) {
if(ArrayUtil.isEmpty(otherObjs)){
error(obj1);
} else{
error(buildTemplateSplitBySpace(otherObjs.length + 1), ArrayUtil.insert(otherObjs, 0, obj1));
}
}
/**
* 同 System.err.println()方法,打印控制台日志
*
*
* @param template 文本模板,被替换的部分用 {} 表示
* @param values 值
* @param values
*/
public static void error(String template, Object... values) {
error(null, template, values);
if (ArrayUtil.isEmpty(values) || StrUtil.contains(template, TEMPLATE_VAR)) {
errorInternal(template, values);
} else {
errorInternal(buildTemplateSplitBySpace(values.length + 1), ArrayUtil.insert(values, 0, template));
}
}
/**
* 同 System.err.println()方法,打印控制台日志
*
* @param t 异常对象
*
* @param t 异常对象
* @param template 文本模板,被替换的部分用 {} 表示
* @param values 值
* @param values
*/
public static void error(Throwable t, String template, Object... values) {
err.println(StrUtil.format(template, values));
@@ -158,10 +246,21 @@ public class Console {
}
}
/**
* 同 System.err.println()方法,打印控制台日志
*
* @param template 文本模板,被替换的部分用 {} 表示
* @param values 值
*/
private static void errorInternal(String template, Object... values) {
error(null, template, values);
}
// --------------------------------------------------------------------------------- in
/**
* 创建从控制台读取内容的{@link Scanner}
*
*
* @return {@link Scanner}
* @since 3.3.1
*/
@@ -171,7 +270,7 @@ public class Console {
/**
* 读取用户输入的内容(在控制台敲回车前的内容)
*
*
* @return 用户输入的内容
* @since 3.3.1
*/
@@ -180,6 +279,7 @@ public class Console {
}
// --------------------------------------------------------------------------------- console lineNumber
/**
* 返回当前位置+行号 (不支持Lambda、内部类、递归内使用)
*
@@ -193,7 +293,7 @@ public class Console {
final String methodName = stackTraceElement.getMethodName();
final String fileName = stackTraceElement.getFileName();
final Integer lineNumber = stackTraceElement.getLineNumber();
return String.format("%s.%s(%s:%s)", className,methodName,fileName,lineNumber);
return String.format("%s.%s(%s:%s)", className, methodName, fileName, lineNumber);
}
/**
@@ -206,4 +306,14 @@ public class Console {
return new Throwable().getStackTrace()[1].getLineNumber();
}
/**
* 构建空格分隔的模板,类似于"{} {} {} {}"
*
* @param count 变量数量
* @return 模板
*/
private static String buildTemplateSplitBySpace(int count){
return StrUtil.repeatAndJoin(TEMPLATE_VAR, count, StrUtil.SPACE);
}
}

View File

@@ -93,9 +93,9 @@ public class Dict extends LinkedHashMap<String, Object> implements BasicTypeGett
String key = null;
for(int i = 0; i < keysAndValues.length; i++){
if(i % 2 == 0){
dict.put(key, keysAndValues[i]);
} else{
key = Convert.toStr(keysAndValues[i]);
} else{
dict.put(key, keysAndValues[i]);
}
}

View File

@@ -7,23 +7,36 @@ import java.util.Objects;
/**
* 键值对对象,只能在构造时传入键值
*
* @author looly
*
* @param <K> 键类型
* @param <V> 值类型
* @author looly
* @since 4.1.5
*/
public class Pair<K, V> extends CloneSupport<Pair<K, V>> implements Serializable{
public class Pair<K, V> extends CloneSupport<Pair<K, V>> implements Serializable {
private static final long serialVersionUID = 1L;
private final K key;
private final V value;
/**
* 构建{@link Pair}对象
*
* @param <K> 键类型
* @param <V> 值类型
* @param key 键
* @param value 值
* @return {@link Pair}
* @since 5.4.3
*/
public static <K, V> Pair<K, V> of(K key, V value) {
return new Pair<>(key, value);
}
/**
* 构造
*
* @param key 键
*
* @param key
* @param value 值
*/
public Pair(K key, V value) {
@@ -33,6 +46,7 @@ public class Pair<K, V> extends CloneSupport<Pair<K, V>> implements Serializable
/**
* 获取键
*
* @return 键
*/
public K getKey() {
@@ -41,6 +55,7 @@ public class Pair<K, V> extends CloneSupport<Pair<K, V>> implements Serializable
/**
* 获取值
*
* @return 值
*/
public V getValue() {

View File

@@ -0,0 +1,18 @@
package cn.hutool.core.lang.generator;
/**
* 生成器泛型接口<br>
* 通过实现此接口可以自定义生成对象的策略
*
* @param <T> 生成对象类型
* @since 5.4.3
*/
public interface Generator<T> {
/**
* 生成新的对象
*
* @return 新的对象
*/
T next();
}

View File

@@ -0,0 +1,28 @@
package cn.hutool.core.lang.generator;
import cn.hutool.core.util.ReflectUtil;
/**
* 对象生成器通过指定对象的Class类型调用next方法时生成新的对象。
*
* @param <T> 对象类型
* @author looly
* @since 5.4.3
*/
public class ObjectGenerator<T> implements Generator<T> {
private final Class<T> clazz;
/**
* 构造
* @param clazz 对象类型
*/
public ObjectGenerator(Class<T> clazz) {
this.clazz = clazz;
}
@Override
public T next() {
return ReflectUtil.newInstanceIfPossible(this.clazz);
}
}

View File

@@ -0,0 +1,16 @@
package cn.hutool.core.lang.generator;
import cn.hutool.core.lang.ObjectId;
/**
* ObjectId生成器
*
* @author looly
* @since 5.4.3
*/
public class ObjectIdGenerator implements Generator<String> {
@Override
public String next() {
return ObjectId.next();
}
}

View File

@@ -0,0 +1,38 @@
package cn.hutool.core.lang.generator;
import cn.hutool.core.lang.Snowflake;
/**
* Snowflake生成器<br>
* 注意,默认此生成器必须单例使用,否则会有重复<br>
* 默认构造的终端ID和数据中心ID都为0不适用于分布式环境。
*
* @author looly
* @since 5.4.3
*/
public class SnowflakeGenerator implements Generator<Long> {
private final Snowflake snowflake;
/**
* 构造
*/
public SnowflakeGenerator() {
this(0, 0);
}
/**
* 构造
*
* @param workerId 终端ID
* @param dataCenterId 数据中心ID
*/
public SnowflakeGenerator(long workerId, long dataCenterId) {
snowflake = new Snowflake(workerId, dataCenterId);
}
@Override
public Long next() {
return this.snowflake.nextId();
}
}

View File

@@ -0,0 +1,16 @@
package cn.hutool.core.lang.generator;
import cn.hutool.core.util.IdUtil;
/**
* UUID生成器
*
* @author looly
* @since 5.4.3
*/
public class UUIDGenerator implements Generator<String> {
@Override
public String next() {
return IdUtil.fastUUID();
}
}

View File

@@ -0,0 +1,7 @@
/**
* 提供生成器接口及相关封装
*
* @author looly
*
*/
package cn.hutool.core.lang.generator;

View File

@@ -0,0 +1,40 @@
package cn.hutool.core.lang.intern;
/**
* 规范化对象生成工具
*
* @author looly
* @since 5.4.3
*/
public class InternUtil {
/**
* 创建WeakHshMap实现的字符串规范化器
*
* @param <T> 规范对象的类型
* @return {@link Interner}
*/
public static <T> Interner<T> createWeakInterner(){
return new WeakInterner<>();
}
/**
* 创建JDK默认实现的字符串规范化器
*
* @return {@link Interner}
* @see String#intern()
*/
public static Interner<String> createJdkInterner(){
return new JdkStringInterner();
}
/**
* 创建字符串规范化器
*
* @param isWeak 是否创建使用WeakHashMap实现的Interner
* @return {@link Interner}
*/
public static Interner<String> createStringInterner(boolean isWeak){
return isWeak ? createWeakInterner() : createJdkInterner();
}
}

View File

@@ -0,0 +1,21 @@
package cn.hutool.core.lang.intern;
/**
* 规范化表示形式封装<br>
* 所谓规范化即当两个对象equals时规范化的对象则可以实现==<br>
* 此包中的相关封装类似于 {@link String#intern()}
*
* @param <T> 规范化的对象类型
* @author looly
* @since 5.4.3
*/
public interface Interner<T> {
/**
* 返回指定对象对应的规范化对象sample对象可能有多个但是这些对象如果都equals则返回的是同一个对象
*
* @param sample 对象
* @return 样例对象
*/
T intern(T sample);
}

View File

@@ -0,0 +1,17 @@
package cn.hutool.core.lang.intern;
/**
* JDK中默认的字符串规范化实现
*
* @author looly
* @since 5.4.3
*/
public class JdkStringInterner implements Interner<String>{
@Override
public String intern(String sample) {
if(null == sample){
return null;
}
return sample.intern();
}
}

View File

@@ -0,0 +1,22 @@
package cn.hutool.core.lang.intern;
import cn.hutool.core.lang.SimpleCache;
/**
* 使用WeakHashMap(线程安全)存储对象的规范化对象,注意此对象需单例使用!<br>
*
* @author looly
* @since 5.4.3
*/
public class WeakInterner<T> implements Interner<T>{
private final SimpleCache<T, T> cache = new SimpleCache<>();
@Override
public T intern(T sample) {
if(null == sample){
return null;
}
return cache.get(sample, ()->sample);
}
}

View File

@@ -0,0 +1,8 @@
/**
* 规范化表示形式封装<br>
* 所谓规范化即当两个对象equals时规范化的对象则可以实现==<br>
* 此包中的相关封装类似于 String#intern()
*
* @author looly
*/
package cn.hutool.core.lang.intern;

View File

@@ -0,0 +1,193 @@
package cn.hutool.core.math;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.Stack;
/**
* 数学表达式计算工具类<br>
* 见https://github.com/looly/hutool/issues/1090#issuecomment-693750140
*
* @author trainliang, looly
* @since 5.4.3
*/
public class Calculator {
private final Stack<String> postfixStack = new Stack<>();// 后缀式栈
private final Stack<Character> opStack = new Stack<>();// 运算符栈
private final int[] operatPriority = new int[]{0, 3, 2, 1, -1, 1, 0, 2};// 运用运算符ASCII码-40做索引的运算符优先级
/**
* 计算表达式的值
*
* @param expression 表达式
* @return 计算结果
*/
public static double conversion(String expression) {
final Calculator cal = new Calculator();
expression = transform(expression);
return cal.calculate(expression);
}
/**
* 将表达式中负数的符号更改
*
* @param expression 例如-2+-1*(-3E-2)-(-1) 被转为 ~2+~1*(~3E~2)-(~1)
* @return 转换后的字符串
*/
private static String transform(String expression) {
expression = StrUtil.cleanBlank(expression);
expression = StrUtil.removeSuffix(expression, "=");
final char[] arr = expression.toCharArray();
for (int i = 0; i < arr.length; i++) {
if (arr[i] == '-') {
if (i == 0) {
arr[i] = '~';
} else {
char c = arr[i - 1];
if (c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == 'E' || c == 'e') {
arr[i] = '~';
}
}
}
}
if (arr[0] == '~' || arr[1] == '(') {
arr[0] = '-';
return "0" + new String(arr);
} else {
return new String(arr);
}
}
/**
* 按照给定的表达式计算
*
* @param expression 要计算的表达式例如:5+12*(3+5)/7
* @return 计算结果
*/
public double calculate(String expression) {
Stack<String> resultStack = new Stack<>();
prepare(expression);
Collections.reverse(postfixStack);// 将后缀式栈反转
String firstValue, secondValue, currentValue;// 参与计算的第一个值,第二个值和算术运算符
while (false == postfixStack.isEmpty()) {
currentValue = postfixStack.pop();
if (false == isOperator(currentValue.charAt(0))) {// 如果不是运算符则存入操作数栈中
currentValue = currentValue.replace("~", "-");
resultStack.push(currentValue);
} else {// 如果是运算符则从操作数栈中取两个值和该数值一起参与运算
secondValue = resultStack.pop();
firstValue = resultStack.pop();
// 将负数标记符改为负号
firstValue = firstValue.replace("~", "-");
secondValue = secondValue.replace("~", "-");
BigDecimal tempResult = calculate(firstValue, secondValue, currentValue.charAt(0));
resultStack.push(tempResult.toString());
}
}
return Double.parseDouble(resultStack.pop());
}
/**
* 数据准备阶段将表达式转换成为后缀式栈
*
* @param expression 表达式
*/
private void prepare(String expression) {
opStack.push(',');// 运算符放入栈底元素逗号,此符号优先级最低
char[] arr = expression.toCharArray();
int currentIndex = 0;// 当前字符的位置
int count = 0;// 上次算术运算符到本次算术运算符的字符的长度便于或者之间的数值
char currentOp, peekOp;// 当前操作符和栈顶操作符
for (int i = 0; i < arr.length; i++) {
currentOp = arr[i];
if (isOperator(currentOp)) {// 如果当前字符是运算符
if (count > 0) {
postfixStack.push(new String(arr, currentIndex, count));// 取两个运算符之间的数字
}
peekOp = opStack.peek();
if (currentOp == ')') {// 遇到反括号则将运算符栈中的元素移除到后缀式栈中直到遇到左括号
while (opStack.peek() != '(') {
postfixStack.push(String.valueOf(opStack.pop()));
}
opStack.pop();
} else {
while (currentOp != '(' && peekOp != ',' && compare(currentOp, peekOp)) {
postfixStack.push(String.valueOf(opStack.pop()));
peekOp = opStack.peek();
}
opStack.push(currentOp);
}
count = 0;
currentIndex = i + 1;
} else {
count++;
}
}
if (count > 1 || (count == 1 && !isOperator(arr[currentIndex]))) {// 最后一个字符不是括号或者其他运算符的则加入后缀式栈中
postfixStack.push(new String(arr, currentIndex, count));
}
while (opStack.peek() != ',') {
postfixStack.push(String.valueOf(opStack.pop()));// 将操作符栈中的剩余的元素添加到后缀式栈中
}
}
/**
* 判断是否为算术符号
*
* @param c 字符
* @return 是否为算术符号
*/
private boolean isOperator(char c) {
return c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == ')';
}
/**
* 利用ASCII码-40做下标去算术符号优先级
*
* @param cur 下标
* @param peek peek
* @return 优先级
*/
public boolean compare(char cur, char peek) {// 如果是peek优先级高于cur返回true默认都是peek优先级要低
boolean result = false;
if (operatPriority[(peek) - 40] >= operatPriority[(cur) - 40]) {
result = true;
}
return result;
}
/**
* 按照给定的算术运算符做计算
*
* @param firstValue 第一个值
* @param secondValue 第二个值
* @param currentOp 算数符,只支持'+'、'-'、'*'、'/'
* @return 结果
*/
private BigDecimal calculate(String firstValue, String secondValue, char currentOp) {
BigDecimal result;
switch (currentOp) {
case '+':
result = NumberUtil.add(firstValue, secondValue);
break;
case '-':
result = NumberUtil.sub(firstValue, secondValue);
break;
case '*':
result = NumberUtil.mul(firstValue, secondValue);
break;
case '/':
result = NumberUtil.div(firstValue, secondValue);
break;
default:
throw new IllegalStateException("Unexpected value: " + currentOp);
}
return result;
}
}

View File

@@ -42,7 +42,8 @@ import java.util.TreeSet;
public class NetUtil {
public final static String LOCAL_IP = "127.0.0.1";
public static String LOCAL_HOSTNAME = "";
public static String localhostName;
/**
* 默认最小端口1024
@@ -535,20 +536,26 @@ public class NetUtil {
}
/**
* 获取主机名称
* 获取主机名称,一次获取会缓存名称
*
* @return 主机名称
* @since 5.4.4
*/
public static String getLocalHostName() {
try {
if (StrUtil.isNotBlank(LOCAL_HOSTNAME)) {
return LOCAL_HOSTNAME;
}
LOCAL_HOSTNAME = InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
LOCAL_HOSTNAME = getLocalhostStr();
if (StrUtil.isNotBlank(localhostName)) {
return localhostName;
}
return LOCAL_HOSTNAME;
final InetAddress localhost = getLocalhost();
if(null != localhost){
String name = localhost.getHostName();
if(StrUtil.isEmpty(name)){
name = localhost.getHostAddress();
}
localhostName = name;
}
return localhostName;
}
/**

View File

@@ -70,6 +70,18 @@ public final class UrlBuilder implements Serializable {
return of(uri.getScheme(), uri.getHost(), uri.getPort(), uri.getPath(), uri.getRawQuery(), uri.getFragment(), charset);
}
/**
* 使用URL字符串构建UrlBuilder当传入的URL没有协议时按照http协议对待<br>
* 此方法不对URL编码
*
* @param httpUrl URL字符串
* @return UrlBuilder
* @since 5.4.3
*/
public static UrlBuilder ofHttpWithoutEncode(String httpUrl) {
return ofHttp(httpUrl, null);
}
/**
* 使用URL字符串构建UrlBuilder当传入的URL没有协议时按照http协议对待。
*

View File

@@ -195,10 +195,10 @@ public class UrlQuery {
}
key = entry.getKey();
if (StrUtil.isNotEmpty(key)) {
sb.append(URLUtil.encodeAll(StrUtil.str(key), charset)).append("=");
sb.append(URLUtil.encodeAll(StrUtil.str(key), charset));
value = entry.getValue();
if (StrUtil.isNotEmpty(value)) {
sb.append(URLUtil.encodeAll(StrUtil.str(value), charset));
if (null != value) {
sb.append("=").append(URLUtil.encodeAll(StrUtil.str(value), charset));
}
}
}
@@ -246,8 +246,8 @@ public class UrlQuery {
final String actualKey = URLUtil.decode(key, charset);
this.query.put(actualKey, StrUtil.nullToEmpty(URLUtil.decode(value, charset)));
} else if (null != value) {
// name为空value作为namevalue赋值""
this.query.put(URLUtil.decode(value, charset), StrUtil.EMPTY);
// name为空value作为namevalue赋值null
this.query.put(URLUtil.decode(value, charset), null);
}
}
}

View File

@@ -462,6 +462,9 @@ public class StrBuilder implements CharSequence, Appendable, Serializable {
@Override
public char charAt(int index) {
if(index < 0){
index = this.position + index;
}
if ((index < 0) || (index > this.position)) {
throw new StringIndexOutOfBoundsException(index);
}

View File

@@ -5,6 +5,8 @@ import cn.hutool.core.text.replacer.ReplacerChain;
/**
* HTML4的ESCAPE
* 参考Commons Lang3
*
* @author looly
*
*/

View File

@@ -29,6 +29,7 @@ public class CharUtil {
public static final char DOUBLE_QUOTES = '"';
public static final char SINGLE_QUOTE = '\'';
public static final char AMP = '&';
public static final char AT = '@';
/**
* 是否为ASCII字符ASCII字符位于0~127之间

View File

@@ -7,6 +7,7 @@ import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.PatternPool;
import cn.hutool.core.lang.Validator;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@@ -194,29 +195,29 @@ public class IdcardUtil {
* <li>通过上面得知如果余数是2就会在身份证的第18位数字上出现罗马数字的。如果余数是10身份证的最后一位号码就是2</li>
* </ol>
*
* @param idCard 待验证的身份证
* @param idcard 待验证的身份证
* @return 是否有效的18位身份证
*/
public static boolean isValidCard18(String idCard) {
if (CHINA_ID_MAX_LENGTH != idCard.length()) {
public static boolean isValidCard18(String idcard) {
if (CHINA_ID_MAX_LENGTH != idcard.length()) {
return false;
}
// 省份
final String proCode = idCard.substring(0, 2);
final String proCode = idcard.substring(0, 2);
if (null == CITY_CODES.get(proCode)) {
return false;
}
//校验生日
if (false == Validator.isBirthday(idCard.substring(6, 14))) {
if (false == Validator.isBirthday(idcard.substring(6, 14))) {
return false;
}
// 前17位
String code17 = idCard.substring(0, 17);
String code17 = idcard.substring(0, 17);
// 第18位
char code18 = Character.toLowerCase(idCard.charAt(17));
char code18 = Character.toLowerCase(idcard.charAt(17));
if (ReUtil.isMatch(PatternPool.NUMBERS, code17)) {
// 获取校验位
char val = getCheckCode18(code17);
@@ -228,22 +229,22 @@ public class IdcardUtil {
/**
* 验证15位身份编码是否合法
*
* @param idCard 身份编码
* @param idcard 身份编码
* @return 是否合法
*/
public static boolean isValidCard15(String idCard) {
if (CHINA_ID_MIN_LENGTH != idCard.length()) {
public static boolean isValidCard15(String idcard) {
if (CHINA_ID_MIN_LENGTH != idcard.length()) {
return false;
}
if (ReUtil.isMatch(PatternPool.NUMBERS, idCard)) {
if (ReUtil.isMatch(PatternPool.NUMBERS, idcard)) {
// 省份
String proCode = idCard.substring(0, 2);
String proCode = idcard.substring(0, 2);
if (null == CITY_CODES.get(proCode)) {
return false;
}
//校验生日两位年份补充为19XX
return false != Validator.isBirthday("19" + idCard.substring(6, 12));
return false != Validator.isBirthday("19" + idcard.substring(6, 12));
} else {
return false;
}
@@ -252,24 +253,24 @@ public class IdcardUtil {
/**
* 验证10位身份编码是否合法
*
* @param idCard 身份编码
* @param idcard 身份编码
* @return 身份证信息数组
* <p>
* [0] - 台湾、澳门、香港 [1] - 性别(男M,女F,未知N) [2] - 是否合法(合法true,不合法false) 若不是身份证件号码则返回null
* </p>
*/
public static String[] isValidCard10(String idCard) {
if (StrUtil.isBlank(idCard)) {
public static String[] isValidCard10(String idcard) {
if (StrUtil.isBlank(idcard)) {
return null;
}
String[] info = new String[3];
String card = idCard.replaceAll("[()]", "");
if (card.length() != 8 && card.length() != 9 && idCard.length() != 10) {
String card = idcard.replaceAll("[()]", "");
if (card.length() != 8 && card.length() != 9 && idcard.length() != 10) {
return null;
}
if (idCard.matches("^[a-zA-Z][0-9]{9}$")) { // 台湾
if (idcard.matches("^[a-zA-Z][0-9]{9}$")) { // 台湾
info[0] = "台湾";
char char2 = idCard.charAt(1);
char char2 = idcard.charAt(1);
if ('1' == char2) {
info[1] = "M";
} else if ('2' == char2) {
@@ -279,14 +280,14 @@ public class IdcardUtil {
info[2] = "false";
return info;
}
info[2] = isValidTWCard(idCard) ? "true" : "false";
} else if (idCard.matches("^[157][0-9]{6}\\(?[0-9A-Z]\\)?$")) { // 澳门
info[2] = isValidTWCard(idcard) ? "true" : "false";
} else if (idcard.matches("^[157][0-9]{6}\\(?[0-9A-Z]\\)?$")) { // 澳门
info[0] = "澳门";
info[1] = "N";
} else if (idCard.matches("^[A-Z]{1,2}[0-9]{6}\\(?[0-9A]\\)?$")) { // 香港
} else if (idcard.matches("^[A-Z]{1,2}[0-9]{6}\\(?[0-9A]\\)?$")) { // 香港
info[0] = "香港";
info[1] = "N";
info[2] = isValidHKCard(idCard) ? "true" : "false";
info[2] = isValidHKCard(idcard) ? "true" : "false";
} else {
return null;
}
@@ -296,20 +297,20 @@ public class IdcardUtil {
/**
* 验证台湾身份证号码
*
* @param idCard 身份证号码
* @param idcard 身份证号码
* @return 验证码是否符合
*/
public static boolean isValidTWCard(String idCard) {
if (StrUtil.isEmpty(idCard)) {
public static boolean isValidTWCard(String idcard) {
if (StrUtil.isEmpty(idcard)) {
return false;
}
String start = idCard.substring(0, 1);
String start = idcard.substring(0, 1);
Integer iStart = TW_FIRST_CODE.get(start);
if (null == iStart) {
return false;
}
String mid = idCard.substring(1, 9);
String end = idCard.substring(9, 10);
String mid = idcard.substring(1, 9);
String end = idcard.substring(9, 10);
int sum = iStart / 10 + (iStart % 10) * 9;
final char[] chars = mid.toCharArray();
int iflag = 8;
@@ -329,11 +330,11 @@ public class IdcardUtil {
* 将身份证号码全部转换为数字分别对应乘9-1相加的总和整除11则证件号码有效
* </p>
*
* @param idCard 身份证号码
* @param idcard 身份证号码
* @return 验证码是否符合
*/
public static boolean isValidHKCard(String idCard) {
String card = idCard.replaceAll("[()]", "");
public static boolean isValidHKCard(String idcard) {
String card = idcard.replaceAll("[()]", "");
int sum;
if (card.length() == 9) {
sum = (Character.toUpperCase(card.charAt(0)) - 55) * 9 + (Character.toUpperCase(card.charAt(1)) - 55) * 8;
@@ -343,7 +344,7 @@ public class IdcardUtil {
}
// 首字母A-ZA表示1以此类推
char start = idCard.charAt(0);
char start = idcard.charAt(0);
int iStart = start - 'A' + 1;
String mid = card.substring(1, 7);
String end = card.substring(7, 8);
@@ -364,12 +365,12 @@ public class IdcardUtil {
/**
* 根据身份编号获取生日只支持15或18位身份证号码
*
* @param idCard 身份编号
* @param idcard 身份编号
* @return 生日(yyyyMMdd)
* @see #getBirth(String)
*/
public static String getBirthByIdCard(String idCard) {
return getBirth(idCard);
public static String getBirthByIdCard(String idcard) {
return getBirth(idcard);
}
/**
@@ -404,120 +405,145 @@ public class IdcardUtil {
/**
* 根据身份编号获取年龄只支持15或18位身份证号码
*
* @param idCard 身份编号
* @param idcard 身份编号
* @return 年龄
*/
public static int getAgeByIdCard(String idCard) {
return getAgeByIdCard(idCard, DateUtil.date());
public static int getAgeByIdCard(String idcard) {
return getAgeByIdCard(idcard, DateUtil.date());
}
/**
* 根据身份编号获取指定日期当时的年龄年龄只支持15或18位身份证号码
*
* @param idCard 身份编号
* @param idcard 身份编号
* @param dateToCompare 以此日期为界,计算年龄。
* @return 年龄
*/
public static int getAgeByIdCard(String idCard, Date dateToCompare) {
String birth = getBirthByIdCard(idCard);
public static int getAgeByIdCard(String idcard, Date dateToCompare) {
String birth = getBirthByIdCard(idcard);
return DateUtil.age(DateUtil.parse(birth, "yyyyMMdd"), dateToCompare);
}
/**
* 根据身份编号获取生日年只支持15或18位身份证号码
*
* @param idCard 身份编号
* @param idcard 身份编号
* @return 生日(yyyy)
*/
public static Short getYearByIdCard(String idCard) {
final int len = idCard.length();
public static Short getYearByIdCard(String idcard) {
final int len = idcard.length();
if (len < CHINA_ID_MIN_LENGTH) {
return null;
} else if (len == CHINA_ID_MIN_LENGTH) {
idCard = convert15To18(idCard);
idcard = convert15To18(idcard);
}
return Short.valueOf(Objects.requireNonNull(idCard).substring(6, 10));
return Short.valueOf(Objects.requireNonNull(idcard).substring(6, 10));
}
/**
* 根据身份编号获取生日月只支持15或18位身份证号码
*
* @param idCard 身份编号
* @param idcard 身份编号
* @return 生日(MM)
*/
public static Short getMonthByIdCard(String idCard) {
final int len = idCard.length();
public static Short getMonthByIdCard(String idcard) {
final int len = idcard.length();
if (len < CHINA_ID_MIN_LENGTH) {
return null;
} else if (len == CHINA_ID_MIN_LENGTH) {
idCard = convert15To18(idCard);
idcard = convert15To18(idcard);
}
return Short.valueOf(Objects.requireNonNull(idCard).substring(10, 12));
return Short.valueOf(Objects.requireNonNull(idcard).substring(10, 12));
}
/**
* 根据身份编号获取生日天只支持15或18位身份证号码
*
* @param idCard 身份编号
* @param idcard 身份编号
* @return 生日(dd)
*/
public static Short getDayByIdCard(String idCard) {
final int len = idCard.length();
public static Short getDayByIdCard(String idcard) {
final int len = idcard.length();
if (len < CHINA_ID_MIN_LENGTH) {
return null;
} else if (len == CHINA_ID_MIN_LENGTH) {
idCard = convert15To18(idCard);
idcard = convert15To18(idcard);
}
return Short.valueOf(Objects.requireNonNull(idCard).substring(12, 14));
return Short.valueOf(Objects.requireNonNull(idcard).substring(12, 14));
}
/**
* 根据身份编号获取性别只支持15或18位身份证号码
*
* @param idCard 身份编号
* @param idcard 身份编号
* @return 性别(1 : 男 0 : 女)
*/
public static int getGenderByIdCard(String idCard) {
Assert.notBlank(idCard);
final int len = idCard.length();
public static int getGenderByIdCard(String idcard) {
Assert.notBlank(idcard);
final int len = idcard.length();
if (len < CHINA_ID_MIN_LENGTH) {
throw new IllegalArgumentException("ID Card length must be 15 or 18");
}
if (len == CHINA_ID_MIN_LENGTH) {
idCard = convert15To18(idCard);
idcard = convert15To18(idcard);
}
char sCardChar = Objects.requireNonNull(idCard).charAt(16);
char sCardChar = Objects.requireNonNull(idcard).charAt(16);
return (sCardChar % 2 != 0) ? 1 : 0;
}
/**
* 根据身份编号获取户籍省份只支持15或18位身份证号码
*
* @param idCard 身份编码
* @return 省级编码
* @param idcard 身份编码
* @return 省份名称
*/
public static String getProvinceByIdCard(String idCard) {
int len = idCard.length();
public static String getProvinceByIdCard(String idcard) {
int len = idcard.length();
if (len == CHINA_ID_MIN_LENGTH || len == CHINA_ID_MAX_LENGTH) {
String sProvinNum = idCard.substring(0, 2);
String sProvinNum = idcard.substring(0, 2);
return CITY_CODES.get(sProvinNum);
}
return null;
}
/**
* 根据身份编号获取户籍省份只支持15或18位身份证号码
*
* @param idcard 身份编码
* @return 市级编码。
*/
public static String getCityCodeByIdCard(String idcard) {
int len = idcard.length();
if (len == CHINA_ID_MIN_LENGTH || len == CHINA_ID_MAX_LENGTH) {
return idcard.substring(0, 5);
}
return null;
}
/**
* 隐藏指定位置的几个身份证号数字为“*”
*
* @param idCard 身份证号
* @param idcard 身份证号
* @param startInclude 开始位置(包含)
* @param endExclude 结束位置(不包含)
* @return 隐藏后的身份证号码
* @see StrUtil#hide(CharSequence, int, int)
* @since 3.2.2
*/
public static String hide(String idCard, int startInclude, int endExclude) {
return StrUtil.hide(idCard, startInclude, endExclude);
public static String hide(String idcard, int startInclude, int endExclude) {
return StrUtil.hide(idcard, startInclude, endExclude);
}
/**
* 获取身份证信息,包括身份、城市代码、生日、性别等
*
* @param idcard 15或18位身份证
* @return {@link Idcard}
* @since 5.4.3
*/
public static Idcard getIdcardInfo(String idcard){
return new Idcard(idcard);
}
// ----------------------------------------------------------------------------------- Private method start
@@ -584,4 +610,76 @@ public class IdcardUtil {
return iSum;
}
// ----------------------------------------------------------------------------------- Private method end
/**
* 身份证信息,包括身份、城市代码、生日、性别等
*
* @author looly
* @since 5.4.3
*/
public static class Idcard implements Serializable {
private static final long serialVersionUID = 1L;
private final String provinceCode;
private final String cityCode;
private final DateTime birthDate;
private final Integer gender;
/**
* 构造
*
* @param idcard 身份证号码
*/
public Idcard(String idcard) {
this.provinceCode = IdcardUtil.getProvinceByIdCard(idcard);
this.cityCode = IdcardUtil.getCityCodeByIdCard(idcard);
this.birthDate = IdcardUtil.getBirthDate(idcard);
this.gender = IdcardUtil.getGenderByIdCard(idcard);
}
/**
* 获取省份代码
*
* @return 省份代码
*/
public String getProvinceCode() {
return this.provinceCode;
}
/**
* 获取省份名称
*
* @return 省份代码
*/
public String getProvince() {
return CITY_CODES.get(this.provinceCode);
}
/**
* 获取省份代码
*
* @return 省份代码
*/
public String getCityCode() {
return this.cityCode;
}
/**
* 获得生日日期
*
* @return 生日日期
*/
public DateTime getBirthDate() {
return this.birthDate;
}
/**
* 获取性别代号,性别(1 : 男 0 : 女)
*
* @return 性别(1 : 男 0 : 女)
*/
public Integer getGender() {
return this.gender;
}
}
}

View File

@@ -13,6 +13,7 @@ import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.util.*;
/**
@@ -22,14 +23,33 @@ import java.util.*;
*/
public class ObjectUtil {
/**
* 比较两个对象是否相等,此方法是 {@link #equal(Object, Object)}的别名方法。<br>
* 相同的条件有两个,满足其一即可:<br>
* <ol>
* <li>obj1 == null &amp;&amp; obj2 == null</li>
* <li>obj1.equals(obj2)</li>
* <li>如果是BigDecimal比较0 == obj1.compareTo(obj2)</li>
* </ol>
*
* @param obj1 对象1
* @param obj2 对象2
* @return 是否相等
* @see #equal(Object, Object)
* @since 5.4.3
*/
public static boolean equals(Object obj1, Object obj2) {
return equal(obj1, obj2);
}
/**
* 比较两个对象是否相等。<br>
* 相同的条件有两个,满足其一即可:<br>
* <ol>
* <li>obj1 == null &amp;&amp; obj2 == null</li>
* <li>obj1.equals(obj2)</li>
* <li>如果是BigDecimal比较0 == obj1.compareTo(obj2)</li>
* </ol>
* 1. obj1 == null &amp;&amp; obj2 == null 2. obj1.equals(obj2)
*
* @param obj1 对象1
* @param obj2 对象2
@@ -37,7 +57,9 @@ public class ObjectUtil {
* @see Objects#equals(Object, Object)
*/
public static boolean equal(Object obj1, Object obj2) {
// return (obj1 != null) ? (obj1.equals(obj2)) : (obj2 == null);
if(obj1 instanceof BigDecimal && obj2 instanceof BigDecimal){
return NumberUtil.equals((BigDecimal)obj1, (BigDecimal)obj2);
}
return Objects.equals(obj1, obj2);
}

View File

@@ -160,9 +160,10 @@ public class PageUtil {
* @return 分页条
*/
public static int[] rainbow(int pageNo, int totalPage, int displayCount) {
boolean isEven = displayCount % 2 == 0;
int left = displayCount / 2;
int right = displayCount / 2;
// displayCount % 2
boolean isEven = (displayCount & 1) == 0;
int left = displayCount >> 1;
int right = displayCount >> 1;
int length = displayCount;
if (isEven) {

View File

@@ -123,7 +123,7 @@ public class RandomUtil {
}
/**
* 获得随机数[0, 2^32)
* 获得随机数int值
*
* @return 随机数
*/

View File

@@ -34,7 +34,7 @@ public class ServiceLoaderUtil {
while (iterator.hasNext()) {
try {
return iterator.next();
} catch (ServiceConfigurationError e) {
} catch (ServiceConfigurationError ignore) {
// ignore
}
}
@@ -76,7 +76,7 @@ public class ServiceLoaderUtil {
* @return 服务接口实现列表
*/
public static <T> ServiceLoader<T> load(Class<T> clazz, ClassLoader loader) {
return ServiceLoader.load(clazz, loader);
return ServiceLoader.load(clazz, ObjectUtil.defaultIfNull(loader, ClassLoaderUtil.getClassLoader()));
}
/**
@@ -101,6 +101,6 @@ public class ServiceLoaderUtil {
* @since 5.4.2
*/
public static <T> List<T> loadList(Class<T> clazz, ClassLoader loader) {
return ListUtil.list(false, load(clazz));
return ListUtil.list(false, load(clazz, loader));
}
}

View File

@@ -48,6 +48,7 @@ public class StrUtil {
public static final char C_BRACKET_START = CharUtil.BRACKET_START;
public static final char C_BRACKET_END = CharUtil.BRACKET_END;
public static final char C_COLON = CharUtil.COLON;
public static final char C_AT = CharUtil.AT;
public static final String SPACE = " ";
public static final String TAB = " ";
@@ -68,6 +69,7 @@ public class StrUtil {
public static final String BRACKET_START = "[";
public static final String BRACKET_END = "]";
public static final String COLON = ":";
public static final String AT = "@";
public static final String HTML_NBSP = "&nbsp;";
public static final String HTML_AMP = "&amp;";
@@ -599,19 +601,44 @@ public class StrUtil {
*
* @param str 被监测字符串
* @param prefix 开头字符串
* @param isIgnoreCase 是否忽略大小写
* @param ignoreCase 是否忽略大小写
* @return 是否以指定字符串开头
* @since 5.4.3
*/
public static boolean startWith(CharSequence str, CharSequence prefix, boolean isIgnoreCase) {
public static boolean startWith(CharSequence str, CharSequence prefix, boolean ignoreCase) {
return startWith(str, prefix, ignoreCase, false);
}
/**
* 是否以指定字符串开头<br>
* 如果给定的字符串和开头字符串都为null则返回true否则任意一个值为null返回false
*
* @param str 被监测字符串
* @param prefix 开头字符串
* @param ignoreCase 是否忽略大小写
* @param ignoreEquals 是否忽略字符串相等的情况
* @return 是否以指定字符串开头
* @since 5.4.3
*/
public static boolean startWith(CharSequence str, CharSequence prefix, boolean ignoreCase, boolean ignoreEquals) {
if (null == str || null == prefix) {
if(false == ignoreEquals){
return false;
}
return null == str && null == prefix;
}
if (isIgnoreCase) {
return str.toString().toLowerCase().startsWith(prefix.toString().toLowerCase());
boolean isStartWith;
if (ignoreCase) {
isStartWith = str.toString().toLowerCase().startsWith(prefix.toString().toLowerCase());
} else {
return str.toString().startsWith(prefix.toString());
isStartWith = str.toString().startsWith(prefix.toString());
}
if(isStartWith){
return (false == ignoreEquals) || (false == equals(str, prefix, ignoreCase));
}
return false;
}
/**
@@ -625,6 +652,17 @@ public class StrUtil {
return startWith(str, prefix, false);
}
/**
* 是否以指定字符串开头,忽略相等字符串的情况
*
* @param str 被监测字符串
* @param prefix 开头字符串
* @return 是否以指定字符串开头并且两个字符串不相等
*/
public static boolean startWithIgnoreEquals(CharSequence str, CharSequence prefix) {
return startWith(str, prefix, false, true);
}
/**
* 是否以指定字符串开头,忽略大小写
*
@@ -2301,13 +2339,13 @@ public class StrUtil {
* 转义{} format("this is \\{} for {}", "a", "b") =》 this is \{} for a<br>
* 转义\ format("this is \\\\{} for {}", "a", "b") =》 this is \a for b<br>
*
* @param template 文本模板,被替换的部分用 {} 表示
* @param template 文本模板,被替换的部分用 {} 表示如果模板为null返回"null"
* @param params 参数值
* @return 格式化后的文本
* @return 格式化后的文本如果模板为null返回"null"
*/
public static String format(CharSequence template, Object... params) {
if (null == template) {
return null;
return NULL;
}
if (ArrayUtil.isEmpty(params) || isBlank(template)) {
return template.toString();
@@ -2337,6 +2375,20 @@ public class StrUtil {
* @return 格式化后的文本
*/
public static String format(CharSequence template, Map<?, ?> map) {
return format(template, map, true);
}
/**
* 格式化文本,使用 {varName} 占位<br>
* map = {a: "aValue", b: "bValue"} format("{a} and {b}", map) ---=》 aValue and bValue
*
* @param template 文本模板,被替换的部分用 {key} 表示
* @param map 参数值对
* @param ignoreNull 是否忽略 {@code null} 值,忽略则 {@code null} 值对应的变量不被替换,否则替换为""
* @return 格式化后的文本
* @since 5.4.3
*/
public static String format(CharSequence template, Map<?, ?> map, boolean ignoreNull) {
if (null == template) {
return null;
}
@@ -2348,9 +2400,10 @@ public class StrUtil {
String value;
for (Entry<?, ?> entry : map.entrySet()) {
value = utf8Str(entry.getValue());
if (null != value) {
template2 = replace(template2, "{" + entry.getKey() + "}", value);
if (null == value && ignoreNull) {
continue;
}
template2 = replace(template2, "{" + entry.getKey() + "}", value);
}
return template2;
}
@@ -2633,7 +2686,7 @@ public class StrUtil {
}
final int length = str.length();
final StringBuilder sb = new StringBuilder();
final StrBuilder sb = new StrBuilder();
char c;
for (int i = 0; i < length; i++) {
c = str.charAt(i);
@@ -2642,12 +2695,12 @@ public class StrUtil {
// 遇到大写字母处理
final Character nextChar = (i < str.length() - 1) ? str.charAt(i + 1) : null;
if (null != preChar && Character.isUpperCase(preChar)) {
// 前一个字符为大写,则按照一个词对待
// 前一个字符为大写,则按照一个词对待例如AB
sb.append(c);
} else if (null != nextChar && Character.isUpperCase(nextChar)) {
// 后一个为写字母,按照一个词对待
} else if (null != nextChar && (false == Character.isLowerCase(nextChar))) {
// 后一个为非小写字母,按照一个词对待
if (null != preChar && symbol != preChar) {
// 前一个是非大写时按照新词对待,加连接符
// 前一个是非大写时按照新词对待,加连接符例如xAB
sb.append(symbol);
}
sb.append(c);
@@ -2660,8 +2713,11 @@ public class StrUtil {
sb.append(Character.toLowerCase(c));
}
} else {
if (sb.length() > 0 && Character.isUpperCase(sb.charAt(sb.length() - 1)) && symbol != c) {
// 当结果中前一个字母为大写,当前为小写,说明此字符为新词开始(连接符也表示新词)
if (symbol != c
&& sb.length() > 0
&& Character.isUpperCase(sb.charAt(-1))
&& Character.isLowerCase(c)) {
// 当结果中前一个字母为大写,当前为小写(非数字或字符),说明此字符为新词开始(连接符也表示新词)
sb.append(symbol);
}
// 小写或符号
@@ -3445,7 +3501,7 @@ public class StrUtil {
* @return 位置
*/
public static int indexOf(final CharSequence str, char searchChar, int start, int end) {
if(isEmpty(str)){
if (isEmpty(str)) {
return INDEX_NOT_FOUND;
}
final int len = str.length();
@@ -4360,12 +4416,12 @@ public class StrUtil {
* @param strs 多个元素
* @param <T> 元素类型
* @return 第一个非空元素,如果给定的数组为空或者都为空,返回{@code null}
* @since 5.4.1
* @see #isNotEmpty(CharSequence)
* @since 5.4.1
*/
@SuppressWarnings("unchecked")
public <T extends CharSequence> T firstNonEmpty(T... strs) {
return ArrayUtil.firstMatch(StrUtil::isNotEmpty, strs);
return ArrayUtil.firstMatch(StrUtil::isNotEmpty, strs);
}
/**
@@ -4374,8 +4430,8 @@ public class StrUtil {
* @param strs 多个元素
* @param <T> 元素类型
* @return 第一个非空元素,如果给定的数组为空或者都为空,返回{@code null}
* @since 5.4.1
* @see #isNotBlank(CharSequence)
* @since 5.4.1
*/
@SuppressWarnings("unchecked")
public <T extends CharSequence> T firstNonBlank(T... strs) {

View File

@@ -483,13 +483,7 @@ public class URLUtil {
* @throws UtilException 包装URISyntaxException
*/
public static String getPath(String uriStr) {
URI uri;
try {
uri = new URI(uriStr);
} catch (URISyntaxException e) {
throw new UtilException(e);
}
return uri.getPath();
return toURI(uriStr).getPath();
}
/**
@@ -509,7 +503,7 @@ public class URLUtil {
String path = null;
try {
// URL对象的getPath方法对于包含中文或空格的问题
path = URLUtil.toURI(url).getPath();
path = toURI(url).getPath();
} catch (UtilException e) {
// ignore
}
@@ -569,7 +563,7 @@ public class URLUtil {
location = encode(location);
}
try {
return new URI(location);
return new URI(StrUtil.trim(location));
} catch (URISyntaxException e) {
throw new UtilException(e);
}

View File

@@ -1116,6 +1116,8 @@ public class ZipUtil {
* @since 5.0.5
*/
private static File buildFile(File outFile, String fileName) {
// 替换Windows路径分隔符为Linux路径分隔符便于统一处理
fileName = fileName.replace('\\', '/');
if (false == FileUtil.isWindows()
// 检查文件名中是否包含"/",不考虑以"/"结尾的情况
&& fileName.lastIndexOf(CharUtil.SLASH, fileName.length() - 2) > 0) {

View File

@@ -1,8 +1,11 @@
package cn.hutool.core.comparator;
import cn.hutool.core.collection.ListUtil;
import org.junit.Assert;
import org.junit.Test;
import java.util.List;
public class CompareUtilTest {
@Test
@@ -13,4 +16,20 @@ public class CompareUtilTest {
compare = CompareUtil.compare(null, "a", false);
Assert.assertTrue(compare < 0);
}
@Test
public void comparingPinyin() {
List<String> list = ListUtil.toList("成都", "北京", "上海", "深圳");
List<String> ascendingOrderResult = ListUtil.of("北京", "成都", "上海", "深圳");
List<String> descendingOrderResult = ListUtil.of("深圳", "上海", "成都", "北京");
// 正序
list.sort(CompareUtil.comparingPinyin(e -> e));
Assert.assertEquals(list, ascendingOrderResult);
// 反序
list.sort(CompareUtil.comparingPinyin(e -> e, true));
Assert.assertEquals(list, descendingOrderResult);
}
}

View File

@@ -46,4 +46,11 @@ public class VersionComparatorTest {
int compare = VersionComparator.INSTANCE.compare("V0.0.20170102", "V0.0.20170101");
Assert.assertTrue(compare > 0);
}
@Test
public void equalsTest(){
VersionComparator first = new VersionComparator();
VersionComparator other = new VersionComparator();
Assert.assertFalse(first.equals(other));
}
}

View File

@@ -1,16 +1,15 @@
package cn.hutool.core.convert;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import cn.hutool.core.convert.impl.ArrayConverter;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Console;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
/**
* 类型转换工具单元测试<br>
@@ -35,6 +34,15 @@ public class ConvertToArrayTest {
Integer[] intArray2 = Convert.toIntArray(c);
Assert.assertArrayEquals(intArray2, new Integer[]{1,2,3,4,5});
}
@Test
public void toIntArrayTestIgnoreComponentErrorTest() {
String[] b = { "a", "1" };
final ArrayConverter arrayConverter = new ArrayConverter(Integer[].class, true);
Integer[] integerArray = (Integer[]) arrayConverter.convert(b, null);
Assert.assertArrayEquals(integerArray, new Integer[]{null, 1});
}
@Test
public void toLongArrayTest() {

View File

@@ -5,6 +5,7 @@ import cn.hutool.core.date.DateUtil;
import org.junit.Assert;
import org.junit.Test;
import java.math.BigDecimal;
import java.util.concurrent.atomic.AtomicLong;
public class ConvertToNumberTest {
@@ -31,4 +32,13 @@ public class ConvertToNumberTest {
assert date != null;
Assert.assertEquals(date.getTime(), dateLong.longValue());
}
@Test
public void toBigDecimalTest(){
BigDecimal bigDecimal = Convert.toBigDecimal("1.1f");
Assert.assertEquals(1.1f, bigDecimal.floatValue(), 1);
bigDecimal = Convert.toBigDecimal("1L");
Assert.assertEquals(1L, bigDecimal.longValue());
}
}

View File

@@ -1,10 +1,9 @@
package cn.hutool.core.date;
import cn.hutool.core.date.BetweenFormater.Level;
import org.junit.Assert;
import org.junit.Test;
import cn.hutool.core.date.BetweenFormater.Level;
public class BetweenFormaterTest {
@Test

View File

@@ -819,12 +819,4 @@ public class DateUtilTest {
final DateTime parse = DateUtil.parse(dt);
Assert.assertEquals("2020-06-03 12:32:12", parse.toString());
}
@Test
public void getBetweenMonthsTest() {
List<String> months1 = DateUtil.getBetweenMonths(new Date(), new Date());
Assert.assertEquals(1, months1.size());
List<String> months = DateUtil.getBetweenMonths(DateUtil.parse("2020-05-08 3:12:3"), new Date());
Assert.assertEquals(5, months.size());
}
}

View File

@@ -53,6 +53,24 @@ public class LocalDateTimeUtilTest {
Assert.assertEquals("2020-01-23T12:23:56", localDateTime.toString());
}
@Test
public void parseTest5() {
LocalDateTime localDateTime = LocalDateTimeUtil.parse("19940121183604", "yyyyMMddHHmmss");
Assert.assertEquals("1994-01-21T18:36:04", localDateTime.toString());
}
@Test
public void parseTest6() {
LocalDateTime localDateTime = LocalDateTimeUtil.parse("19940121183604682", "yyyyMMddHHmmssSSS");
Assert.assertEquals("1994-01-21T18:36:04.682", localDateTime.toString());
localDateTime = LocalDateTimeUtil.parse("1994012118360468", "yyyyMMddHHmmssSS");
Assert.assertEquals("1994-01-21T18:36:04.680", localDateTime.toString());
localDateTime = LocalDateTimeUtil.parse("199401211836046", "yyyyMMddHHmmssS");
Assert.assertEquals("1994-01-21T18:36:04.600", localDateTime.toString());
}
@Test
public void parseDateTest() {
LocalDate localDate = LocalDateTimeUtil.parseDate("2020-01-23");

View File

@@ -84,7 +84,7 @@ public class ImgUtilTest {
@Test
@Ignore
public void sliceByRowsAndColsTest() {
ImgUtil.sliceByRowsAndCols(FileUtil.file("e:/pic/1.png"), FileUtil.file("e:/pic/dest"), 10, 10);
ImgUtil.sliceByRowsAndCols(FileUtil.file("d:/test/logo.jpg"), FileUtil.file("d:/test/dest"), 1, 5);
}
@Test

View File

@@ -21,6 +21,12 @@ public class ConsoleTest {
Console.log("This is Console log for {}.", "test");
}
@Test
public void logTest2(){
Console.log("a", "b", "c");
Console.log((Object) "a", "b", "c");
}
@Test
public void printTest(){
@@ -29,6 +35,12 @@ public class ConsoleTest {
Console.log("This is Console print for {}.", "test");
}
@Test
public void printTest2(){
Console.print("a", "b", "c");
Console.print((Object) "a", "b", "c");
}
@Test
public void errorTest(){
@@ -39,6 +51,12 @@ public class ConsoleTest {
Console.error("This is Console error for {}.", "test");
}
@Test
public void errorTest2(){
Console.error("a", "b", "c");
Console.error((Object) "a", "b", "c");
}
@Test
@Ignore

View File

@@ -30,4 +30,17 @@ public class DictTest {
Assert.assertEquals(1, dict.get("A"));
Assert.assertEquals(1, dict.get("a"));
}
@Test
public void ofTest(){
Dict dict = Dict.of(
"RED", "#FF0000",
"GREEN", "#00FF00",
"BLUE", "#0000FF"
);
Assert.assertEquals("#FF0000", dict.get("RED"));
Assert.assertEquals("#00FF00", dict.get("GREEN"));
Assert.assertEquals("#0000FF", dict.get("BLUE"));
}
}

View File

@@ -0,0 +1,24 @@
package cn.hutool.core.lang.intern;
import cn.hutool.core.util.RandomUtil;
import org.junit.Assert;
import org.junit.Test;
public class InternUtilTest {
/**
* 检查规范字符串是否相同
*/
@SuppressWarnings("StringOperationCanBeSimplified")
@Test
public void weakTest(){
final Interner<String> interner = InternUtil.createWeakInterner();
String a1 = RandomUtil.randomString(RandomUtil.randomInt(100));
String a2 = new String(a1);
Assert.assertNotSame(a1, a2);
Assert.assertSame(interner.intern(a1), interner.intern(a2));
}
}

View File

@@ -0,0 +1,19 @@
package cn.hutool.core.math;
import org.junit.Assert;
import org.junit.Test;
public class CalculatorTest {
@Test
public void conversationTest(){
final double conversion = Calculator.conversion("(0*1--3)-5/-4-(3*(-2.13))");
Assert.assertEquals(10.64, conversion, 2);
}
@Test
public void conversationTest2(){
final double conversion = Calculator.conversion("77 * 12");
Assert.assertEquals(924.0, conversion, 2);
}
}

View File

@@ -1,5 +1,6 @@
package cn.hutool.core.net;
import cn.hutool.core.net.url.UrlBuilder;
import cn.hutool.core.net.url.UrlQuery;
import org.junit.Assert;
import org.junit.Test;
@@ -16,4 +17,13 @@ public class UrlQueryTest {
Assert.assertEquals("111==", parse.get("b"));
Assert.assertEquals("a=1&b=111==", parse.toString());
}
@Test
public void ofHttpWithoutEncodeTest(){
// charset为null表示不做编码
String url = "https://img-cloud.voc.com.cn/140/2020/09/03/c3d41b93e0d32138574af8e8b50928b376ca5ba61599127028157.png?imageMogr2/auto-orient/thumbnail/500&pid=259848";
final UrlBuilder urlBuilder = UrlBuilder.ofHttpWithoutEncode(url);
final String queryStr = urlBuilder.getQueryStr();
Assert.assertEquals("imageMogr2/auto-orient/thumbnail/500&pid=259848", queryStr);
}
}

View File

@@ -37,4 +37,11 @@ public class EscapeUtilTest {
String unescape = EscapeUtil.unescape(escape);
Assert.assertEquals(str, unescape);
}
@Test
public void escapeSinleQuotesTest(){
String str = "'some text with single quotes'";
final String s = EscapeUtil.escapeHtml4(str);
Assert.assertEquals(str, s);
}
}

View File

@@ -8,9 +8,8 @@ import java.util.List;
/**
* 字符串工具类单元测试
*
* @author Looly
*
* @author Looly
*/
public class StrUtilTest {
@@ -19,14 +18,14 @@ public class StrUtilTest {
String blank = "   ";
Assert.assertTrue(StrUtil.isBlank(blank));
}
@Test
public void trimTest() {
String blank = " 哈哈  ";
String trim = StrUtil.trim(blank);
Assert.assertEquals("哈哈", trim);
}
@Test
public void cleanBlankTest() {
// 包含:制表符、英文空格、不间断空白符、全角空格
@@ -39,7 +38,7 @@ public class StrUtilTest {
public void cutTest() {
String str = "aaabbbcccdddaadfdfsdfsdf0";
String[] cut = StrUtil.cut(str, 4);
Assert.assertArrayEquals(new String[] { "aaab", "bbcc", "cddd", "aadf", "dfsd", "fsdf", "0" }, cut);
Assert.assertArrayEquals(new String[]{"aaab", "bbcc", "cddd", "aadf", "dfsd", "fsdf", "0"}, cut);
}
@Test
@@ -59,20 +58,20 @@ public class StrUtilTest {
public void splitToLongTest() {
String str = "1,2,3,4, 5";
long[] longArray = StrUtil.splitToLong(str, ',');
Assert.assertArrayEquals(new long[] { 1, 2, 3, 4, 5 }, longArray);
Assert.assertArrayEquals(new long[]{1, 2, 3, 4, 5}, longArray);
longArray = StrUtil.splitToLong(str, ",");
Assert.assertArrayEquals(new long[] { 1, 2, 3, 4, 5 }, longArray);
Assert.assertArrayEquals(new long[]{1, 2, 3, 4, 5}, longArray);
}
@Test
public void splitToIntTest() {
String str = "1,2,3,4, 5";
int[] intArray = StrUtil.splitToInt(str, ',');
Assert.assertArrayEquals(new int[] { 1, 2, 3, 4, 5 }, intArray);
Assert.assertArrayEquals(new int[]{1, 2, 3, 4, 5}, intArray);
intArray = StrUtil.splitToInt(str, ",");
Assert.assertArrayEquals(new int[] { 1, 2, 3, 4, 5 }, intArray);
Assert.assertArrayEquals(new int[]{1, 2, 3, 4, 5}, intArray);
}
@Test
@@ -80,7 +79,7 @@ public class StrUtilTest {
String template = "你好,我是{name},我的电话是:{phone}";
String result = StrUtil.format(template, Dict.create().set("name", "张三").set("phone", "13888881111"));
Assert.assertEquals("你好我是张三我的电话是13888881111", result);
String result2 = StrUtil.format(template, Dict.create().set("name", "张三").set("phone", null));
Assert.assertEquals("你好,我是张三,我的电话是:{phone}", result2);
}
@@ -102,11 +101,11 @@ public class StrUtilTest {
str = "abcd123";
strip = StrUtil.strip(str, null, "567");
Assert.assertEquals("abcd123", strip);
Assert.assertEquals("", StrUtil.strip("a","a"));
Assert.assertEquals("", StrUtil.strip("a","a", "b"));
Assert.assertEquals("", StrUtil.strip("a", "a"));
Assert.assertEquals("", StrUtil.strip("a", "a", "b"));
}
@Test
public void stripIgnoreCaseTest() {
String str = "abcd123";
@@ -144,7 +143,7 @@ public class StrUtilTest {
Assert.assertEquals(2, StrUtil.indexOfIgnoreCase("aabaabaa", "", 2));
Assert.assertEquals(-1, StrUtil.indexOfIgnoreCase("abc", "", 9));
}
@Test
public void lastIndexOfTest() {
String a = "aabbccddcc";
@@ -180,17 +179,17 @@ public class StrUtilTest {
String result = StrUtil.replace("123", "2", "3");
Assert.assertEquals("133", result);
}
@Test
public void replaceTest3() {
String result = StrUtil.replace(",abcdef,", ",", "|");
Assert.assertEquals("|abcdef|", result);
}
@Test
public void replaceTest4() {
String a = "1039";
String result = StrUtil.padPre(a,8,"0"); //在字符串1039前补4个0
String result = StrUtil.padPre(a, 8, "0"); //在字符串1039前补4个0
Assert.assertEquals("00001039", result);
}
@@ -228,7 +227,7 @@ public class StrUtilTest {
String rightAnswer = StrUtil.subByCodePoint(test, 0, 3);
Assert.assertEquals("\uD83E\uDD14\uD83D\uDC4D\uD83C\uDF53", rightAnswer);
}
@Test
public void subBeforeTest() {
String a = "abcderghigh";
@@ -238,14 +237,14 @@ public class StrUtilTest {
Assert.assertEquals("abc", pre);
pre = StrUtil.subBefore(a, 'a', false);
Assert.assertEquals("", pre);
//找不到返回原串
pre = StrUtil.subBefore(a, 'k', false);
Assert.assertEquals(a, pre);
pre = StrUtil.subBefore(a, 'k', true);
Assert.assertEquals(a, pre);
}
@Test
public void subAfterTest() {
String a = "abcderghigh";
@@ -255,7 +254,7 @@ public class StrUtilTest {
Assert.assertEquals("erghigh", pre);
pre = StrUtil.subAfter(a, 'h', true);
Assert.assertEquals("", pre);
//找不到字符返回空串
pre = StrUtil.subAfter(a, 'k', false);
Assert.assertEquals("", pre);
@@ -306,20 +305,20 @@ public class StrUtilTest {
result = StrUtil.move(str, 7, 12, 0);
Assert.assertEquals("aaaaaaa22222bbbbbbb", result);
}
@Test
public void removePrefixIgnorecaseTest() {
String a = "aaabbb";
String prefix = "aaa";
Assert.assertEquals("bbb", StrUtil.removePrefixIgnoreCase(a, prefix));
prefix = "AAA";
Assert.assertEquals("bbb", StrUtil.removePrefixIgnoreCase(a, prefix));
prefix = "AAABBB";
Assert.assertEquals("", StrUtil.removePrefixIgnoreCase(a, prefix));
}
@Test
public void maxLengthTest() {
String text = "我是一段正文,很长的正文,需要截取的正文";
@@ -330,13 +329,13 @@ public class StrUtilTest {
str = StrUtil.maxLength(text, 50);
Assert.assertEquals(text, str);
}
@Test
public void toCamelCaseTest() {
String str = "Table_Test_Of_day";
String result = StrUtil.toCamelCase(str);
Assert.assertEquals("tableTestOfDay", result);
String str1 = "TableTestOfDay";
String result1 = StrUtil.toCamelCase(str1);
Assert.assertEquals("TableTestOfDay", result1);
@@ -344,30 +343,20 @@ public class StrUtilTest {
String abc1d = StrUtil.toCamelCase("abc_1d");
Assert.assertEquals("abc1d", abc1d);
}
@Test
public void toUnderLineCaseTest() {
String str = "Table_Test_Of_day";
String result = StrUtil.toUnderlineCase(str);
Assert.assertEquals("table_test_of_day", result);
String str1 = "_Table_Test_Of_day_";
String result1 = StrUtil.toUnderlineCase(str1);
Assert.assertEquals("_table_test_of_day_", result1);
String str2 = "_Table_Test_Of_DAY_";
String result2 = StrUtil.toUnderlineCase(str2);
Assert.assertEquals("_table_test_of_DAY_", result2);
String str3 = "_TableTestOfDAYtoday";
String result3 = StrUtil.toUnderlineCase(str3);
Assert.assertEquals("_table_test_of_DAY_today", result3);
String str4 = "HelloWorld_test";
String result4 = StrUtil.toUnderlineCase(str4);
Assert.assertEquals("hello_world_test", result4);
Dict.create()
.set("Table_Test_Of_day", "table_test_of_day")
.set("_Table_Test_Of_day_", "_table_test_of_day_")
.set("_Table_Test_Of_DAY_", "_table_test_of_DAY_")
.set("_TableTestOfDAYtoday", "_table_test_of_DAY_today")
.set("HelloWorld_test", "hello_world_test")
.set("H2", "H2")
.set("H#case", "H#case")
.forEach((key, value) -> Assert.assertEquals(value, StrUtil.toUnderlineCase(key)));
}
@Test
public void containsAnyTest() {
//字符
@@ -377,7 +366,7 @@ public class StrUtilTest {
Assert.assertFalse(containsAny);
containsAny = StrUtil.containsAny("aaabbbccc", 'd', 'c');
Assert.assertTrue(containsAny);
//字符串
containsAny = StrUtil.containsAny("aaabbbccc", "a", "d");
Assert.assertTrue(containsAny);
@@ -386,7 +375,7 @@ public class StrUtilTest {
containsAny = StrUtil.containsAny("aaabbbccc", "d", "c");
Assert.assertTrue(containsAny);
}
@Test
public void centerTest() {
Assert.assertNull(StrUtil.center(null, 10));
@@ -396,24 +385,24 @@ public class StrUtilTest {
Assert.assertEquals("abcd", StrUtil.center("abcd", 2));
Assert.assertEquals(" a ", StrUtil.center("a", 4));
}
@Test
public void padPreTest() {
Assert.assertNull(StrUtil.padPre(null, 10, ' '));
Assert.assertEquals("001", StrUtil.padPre("1", 3, '0'));
Assert.assertEquals("12", StrUtil.padPre("123", 2, '0'));
Assert.assertNull(StrUtil.padPre(null, 10, "AA"));
Assert.assertEquals("AB1", StrUtil.padPre("1", 3, "ABC"));
Assert.assertEquals("12", StrUtil.padPre("123", 2, "ABC"));
}
@Test
public void padAfterTest() {
Assert.assertNull(StrUtil.padAfter(null, 10, ' '));
Assert.assertEquals("100", StrUtil.padAfter("1", 3, '0'));
Assert.assertEquals("23", StrUtil.padAfter("123", 2, '0'));
Assert.assertNull(StrUtil.padAfter(null, 10, "ABC"));
Assert.assertEquals("1AB", StrUtil.padAfter("1", 3, "ABC"));
Assert.assertEquals("23", StrUtil.padAfter("123", 2, "ABC"));
@@ -421,13 +410,13 @@ public class StrUtilTest {
@Test
public void subBetweenAllTest() {
Assert.assertArrayEquals(new String[]{"yz","abc"},StrUtil.subBetweenAll("saho[yz]fdsadp[abc]a","[","]"));
Assert.assertArrayEquals(new String[]{"abc"}, StrUtil.subBetweenAll("saho[yzfdsadp[abc]a]","[","]"));
Assert.assertArrayEquals(new String[]{"abc", "abc"}, StrUtil.subBetweenAll("yabczyabcz","y","z"));
Assert.assertArrayEquals(new String[0], StrUtil.subBetweenAll(null,"y","z"));
Assert.assertArrayEquals(new String[0], StrUtil.subBetweenAll("","y","z"));
Assert.assertArrayEquals(new String[0], StrUtil.subBetweenAll("abc",null,"z"));
Assert.assertArrayEquals(new String[0], StrUtil.subBetweenAll("abc","y",null));
Assert.assertArrayEquals(new String[]{"yz", "abc"}, StrUtil.subBetweenAll("saho[yz]fdsadp[abc]a", "[", "]"));
Assert.assertArrayEquals(new String[]{"abc"}, StrUtil.subBetweenAll("saho[yzfdsadp[abc]a]", "[", "]"));
Assert.assertArrayEquals(new String[]{"abc", "abc"}, StrUtil.subBetweenAll("yabczyabcz", "y", "z"));
Assert.assertArrayEquals(new String[0], StrUtil.subBetweenAll(null, "y", "z"));
Assert.assertArrayEquals(new String[0], StrUtil.subBetweenAll("", "y", "z"));
Assert.assertArrayEquals(new String[0], StrUtil.subBetweenAll("abc", null, "z"));
Assert.assertArrayEquals(new String[0], StrUtil.subBetweenAll("abc", "y", null));
}
@Test
@@ -436,15 +425,15 @@ public class StrUtilTest {
String src1 = "/* \n* hutool */ asdas /* \n* hutool */";
String src2 = "/ * hutool */ asdas / * hutool */";
String[] results1 = StrUtil.subBetweenAll(src1,"/**","*/");
String[] results1 = StrUtil.subBetweenAll(src1, "/**", "*/");
Assert.assertEquals(0, results1.length);
String[] results2 = StrUtil.subBetweenAll(src2,"/*","*/");
String[] results2 = StrUtil.subBetweenAll(src2, "/*", "*/");
Assert.assertEquals(0, results2.length);
}
@Test
public void briefTest(){
public void briefTest() {
String str = RandomUtil.randomString(1000);
int maxLength = RandomUtil.randomInt(1000);
String brief = StrUtil.brief(str, maxLength);
@@ -460,11 +449,20 @@ public class StrUtilTest {
}
@Test
public void wrapAllTest(){
public void wrapAllTest() {
String[] strings = StrUtil.wrapAll("`", "`", StrUtil.splitToArray("1,2,3,4", ','));
Assert.assertEquals("[`1`, `2`, `3`, `4`]", StrUtil.utf8Str(strings));
strings = StrUtil.wrapAllWithPair("`", StrUtil.splitToArray("1,2,3,4", ','));
Assert.assertEquals("[`1`, `2`, `3`, `4`]", StrUtil.utf8Str(strings));
}
@Test
public void startWithTest(){
String a = "123";
String b = "123";
Assert.assertTrue(StrUtil.startWith(a, b));
Assert.assertFalse(StrUtil.startWithIgnoreEquals(a, b));
}
}

View File

@@ -84,4 +84,11 @@ public class URLUtilTest {
String encode2 = URLUtil.encodeQuery(body);
Assert.assertEquals("366466+-+%E5%89%AF%E6%9C%AC.jpg", encode2);
}
@Test
public void getPathTest(){
String url = " http://www.aaa.bbb/search?scope=ccc&q=ddd";
String path = URLUtil.getPath(url);
Assert.assertEquals("/search", path);
}
}

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.4.2</version>
<version>5.4.4-SNAPSHOT</version>
</parent>
<artifactId>hutool-cron</artifactId>

View File

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

View File

@@ -1,6 +1,5 @@
package cn.hutool.crypto;
import cn.hutool.core.util.HexUtil;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.params.ECDomainParameters;
@@ -16,8 +15,6 @@ import org.bouncycastle.math.ec.ECCurve;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.PrivateKey;
import java.security.PublicKey;
@@ -83,12 +80,7 @@ public class BCUtil {
// 根据曲线恢复公钥格式
final ECNamedCurveSpec ecSpec = new ECNamedCurveSpec(curveName, curve, x9ECParameters.getG(), x9ECParameters.getN());
try {
return KeyUtil.getKeyFactory("EC").generatePublic(new ECPublicKeySpec(point, ecSpec));
} catch (GeneralSecurityException e) {
throw new CryptoException(e);
}
return KeyUtil.generatePublicKey("EC", new ECPublicKeySpec(point, ecSpec));
}
/**
@@ -141,27 +133,17 @@ public class BCUtil {
* @since 5.2.0
*/
public static AsymmetricKeyParameter toParams(Key key) {
try {
if (key instanceof PrivateKey) {
return ECUtil.generatePrivateKeyParameter((PrivateKey) key);
} else if (key instanceof PublicKey) {
return ECUtil.generatePublicKeyParameter((PublicKey) key);
}
} catch (InvalidKeyException e) {
throw new CryptoException(e);
}
return null;
return ECKeyUtil.toParams(key);
}
/**
* 转换为 ECPrivateKeyParameters
*
* @param dHex 私钥d值16进制字符串
* @param d 私钥d值
* @return ECPrivateKeyParameters
*/
public static ECPrivateKeyParameters toSm2Params(String dHex) {
return toSm2Params(HexUtil.toBigInteger(dHex));
public static ECPrivateKeyParameters toSm2Params(String d) {
return ECKeyUtil.toSm2PrivateParams(d);
}
/**
@@ -172,7 +154,7 @@ public class BCUtil {
* @return ECPrivateKeyParameters
*/
public static ECPrivateKeyParameters toParams(String dHex, ECDomainParameters domainParameters) {
return toParams(new BigInteger(dHex, 16), domainParameters);
return ECKeyUtil.toPrivateParams(dHex, domainParameters);
}
/**
@@ -182,7 +164,7 @@ public class BCUtil {
* @return ECPrivateKeyParameters
*/
public static ECPrivateKeyParameters toSm2Params(byte[] d) {
return toSm2Params(new BigInteger(d));
return ECKeyUtil.toSm2PrivateParams(d);
}
/**
@@ -193,7 +175,7 @@ public class BCUtil {
* @return ECPrivateKeyParameters
*/
public static ECPrivateKeyParameters toParams(byte[] d, ECDomainParameters domainParameters) {
return toParams(new BigInteger(d), domainParameters);
return ECKeyUtil.toPrivateParams(d, domainParameters);
}
/**
@@ -203,7 +185,7 @@ public class BCUtil {
* @return ECPrivateKeyParameters
*/
public static ECPrivateKeyParameters toSm2Params(BigInteger d) {
return toParams(d, SmUtil.SM2_DOMAIN_PARAMS);
return ECKeyUtil.toSm2PrivateParams(d);
}
/**
@@ -214,10 +196,7 @@ public class BCUtil {
* @return ECPrivateKeyParameters
*/
public static ECPrivateKeyParameters toParams(BigInteger d, ECDomainParameters domainParameters) {
if(null == d){
return null;
}
return new ECPrivateKeyParameters(d, domainParameters);
return ECKeyUtil.toPrivateParams(d, domainParameters);
}
/**
@@ -229,10 +208,7 @@ public class BCUtil {
* @return ECPublicKeyParameters
*/
public static ECPublicKeyParameters toParams(BigInteger x, BigInteger y, ECDomainParameters domainParameters) {
if(null == x || null == y){
return null;
}
return toParams(x.toByteArray(), y.toByteArray(), domainParameters);
return ECKeyUtil.toPublicParams(x, y, domainParameters);
}
/**
@@ -243,7 +219,7 @@ public class BCUtil {
* @return ECPublicKeyParameters
*/
public static ECPublicKeyParameters toSm2Params(String xHex, String yHex) {
return toParams(xHex, yHex, SmUtil.SM2_DOMAIN_PARAMS);
return ECKeyUtil.toSm2PublicParams(xHex, yHex);
}
/**
@@ -255,7 +231,7 @@ public class BCUtil {
* @return ECPublicKeyParameters
*/
public static ECPublicKeyParameters toParams(String xHex, String yHex, ECDomainParameters domainParameters) {
return toParams(HexUtil.decodeHex(xHex), HexUtil.decodeHex(yHex), domainParameters);
return ECKeyUtil.toPublicParams(xHex, yHex, domainParameters);
}
/**
@@ -266,7 +242,7 @@ public class BCUtil {
* @return ECPublicKeyParameters
*/
public static ECPublicKeyParameters toSm2Params(byte[] xBytes, byte[] yBytes) {
return toParams(xBytes, yBytes, SmUtil.SM2_DOMAIN_PARAMS);
return ECKeyUtil.toSm2PublicParams(xBytes, yBytes);
}
/**
@@ -278,13 +254,7 @@ public class BCUtil {
* @return ECPublicKeyParameters
*/
public static ECPublicKeyParameters toParams(byte[] xBytes, byte[] yBytes, ECDomainParameters domainParameters) {
if(null == xBytes || null == yBytes){
return null;
}
final ECCurve curve = domainParameters.getCurve();
final int curveLength = getCurveLength(curve);
final byte[] encodedPubKey = encodePoint(xBytes, yBytes, curveLength);
return new ECPublicKeyParameters(curve.decodePoint(encodedPubKey), domainParameters);
return ECKeyUtil.toPublicParams(xBytes, yBytes, domainParameters);
}
/**
@@ -294,14 +264,7 @@ public class BCUtil {
* @return {@link ECPublicKeyParameters}或null
*/
public static ECPublicKeyParameters toParams(PublicKey publicKey) {
if (null == publicKey) {
return null;
}
try {
return (ECPublicKeyParameters) ECUtil.generatePublicKeyParameter(publicKey);
} catch (InvalidKeyException e) {
throw new CryptoException(e);
}
return ECKeyUtil.toPublicParams(publicKey);
}
/**
@@ -311,14 +274,7 @@ public class BCUtil {
* @return {@link ECPrivateKeyParameters}或null
*/
public static ECPrivateKeyParameters toParams(PrivateKey privateKey) {
if (null == privateKey) {
return null;
}
try {
return (ECPrivateKeyParameters) ECUtil.generatePrivateKeyParameter(privateKey);
} catch (InvalidKeyException e) {
throw new CryptoException(e);
}
return ECKeyUtil.toPrivateParams(privateKey);
}
/**
@@ -344,58 +300,4 @@ public class BCUtil {
public static PublicKey readPemPublicKey(InputStream pemStream) {
return PemUtil.readPemPublicKey(pemStream);
}
/**
* 将XY曲线点编码为bytes
*
* @param xBytes X坐标bytes
* @param yBytes Y坐标bytes
* @param curveLength 曲线编码后的长度
* @return 编码bytes
*/
private static byte[] encodePoint(byte[] xBytes, byte[] yBytes, int curveLength) {
xBytes = fixLength(curveLength, xBytes);
yBytes = fixLength(curveLength, yBytes);
final byte[] encodedPubKey = new byte[1 + xBytes.length + yBytes.length];
// 压缩类型:无压缩
encodedPubKey[0] = 0x04;
System.arraycopy(xBytes, 0, encodedPubKey, 1, xBytes.length);
System.arraycopy(yBytes, 0, encodedPubKey, 1 + xBytes.length, yBytes.length);
return encodedPubKey;
}
/**
* 获取Curve长度
*
* @param curve {@link ECCurve}
* @return Curve长度
*/
private static int getCurveLength(ECCurve curve) {
return (curve.getFieldSize() + 7) / 8;
}
/**
* 修正长度
*
* @param curveLength 修正后的长度
* @param src bytes
* @return 修正后的bytes
*/
private static byte[] fixLength(int curveLength, byte[] src) {
if (src.length == curveLength) {
return src;
}
byte[] result = new byte[curveLength];
if (src.length > curveLength) {
// 裁剪末尾的指定长度
System.arraycopy(src, src.length - result.length, result, 0, result.length);
} else {
// 放置于末尾
System.arraycopy(src, 0, result, result.length - src.length, src.length);
}
return result;
}
}

View File

@@ -0,0 +1,49 @@
package cn.hutool.crypto;
import javax.crypto.Cipher;
/**
* Cipher模式的枚举封装
*
* @author looly
* @since 5.4.3
*/
public enum CipherMode {
/**
* 加密模式
*/
encrypt(Cipher.ENCRYPT_MODE),
/**
* 解密模式
*/
decrypt(Cipher.DECRYPT_MODE),
/**
* 包装模式
*/
wrap(Cipher.WRAP_MODE),
/**
* 拆包模式
*/
unwrap(Cipher.UNWRAP_MODE);
/**
* 构造
*
* @param value 见{@link Cipher}
*/
CipherMode(int value) {
this.value = value;
}
private final int value;
/**
* 获取枚举值对应的int表示
*
* @return 枚举值对应的int表示
*/
public int getValue() {
return this.value;
}
}

View File

@@ -0,0 +1,262 @@
package cn.hutool.crypto;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.util.BigIntegers;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.PrivateKey;
import java.security.PublicKey;
/**
* EC密钥参数相关工具类封装
*
* @author looly
* @since 5.4.3
*/
public class ECKeyUtil {
/**
* 密钥转换为AsymmetricKeyParameter
*
* @param key PrivateKey或者PublicKey
* @return ECPrivateKeyParameters或者ECPublicKeyParameters
*/
public static AsymmetricKeyParameter toParams(Key key) {
if (key instanceof PrivateKey) {
return toPrivateParams((PrivateKey) key);
} else if (key instanceof PublicKey) {
return toPublicParams((PublicKey) key);
}
return null;
}
//--------------------------------------------------------------------------- Public Key
/**
* 转换为 ECPublicKeyParameters
*
* @param q 公钥Q值
* @return ECPublicKeyParameters
*/
public static ECPublicKeyParameters toSm2PublicParams(byte[] q) {
return toPublicParams(q, SmUtil.SM2_DOMAIN_PARAMS);
}
/**
* 转换为 ECPublicKeyParameters
*
* @param q 公钥Q值
* @return ECPublicKeyParameters
*/
public static ECPublicKeyParameters toSm2PublicParams(String q) {
return toPublicParams(q, SmUtil.SM2_DOMAIN_PARAMS);
}
/**
* 转换为SM2的ECPublicKeyParameters
*
* @param x 公钥X
* @param y 公钥Y
* @return ECPublicKeyParameters
*/
public static ECPublicKeyParameters toSm2PublicParams(String x, String y) {
return toPublicParams(x, y, SmUtil.SM2_DOMAIN_PARAMS);
}
/**
* 转换为SM2的ECPublicKeyParameters
*
* @param xBytes 公钥X
* @param yBytes 公钥Y
* @return ECPublicKeyParameters
*/
public static ECPublicKeyParameters toSm2PublicParams(byte[] xBytes, byte[] yBytes) {
return toPublicParams(xBytes, yBytes, SmUtil.SM2_DOMAIN_PARAMS);
}
/**
* 转换为ECPublicKeyParameters
*
* @param x 公钥X
* @param y 公钥Y
* @param domainParameters ECDomainParameters
* @return ECPublicKeyParameters
*/
public static ECPublicKeyParameters toPublicParams(String x, String y, ECDomainParameters domainParameters) {
return toPublicParams(SecureUtil.decode(x), SecureUtil.decode(y), domainParameters);
}
/**
* 转换为ECPublicKeyParameters
*
* @param xBytes 公钥X
* @param yBytes 公钥Y
* @param domainParameters ECDomainParameters曲线参数
* @return ECPublicKeyParameters
*/
public static ECPublicKeyParameters toPublicParams(byte[] xBytes, byte[] yBytes, ECDomainParameters domainParameters) {
return toPublicParams(BigIntegers.fromUnsignedByteArray(xBytes), BigIntegers.fromUnsignedByteArray(yBytes), domainParameters);
}
/**
* 转换为ECPublicKeyParameters
*
* @param x 公钥X
* @param y 公钥Y
* @param domainParameters ECDomainParameters
* @return ECPublicKeyParameters
*/
public static ECPublicKeyParameters toPublicParams(BigInteger x, BigInteger y, ECDomainParameters domainParameters) {
if (null == x || null == y) {
return null;
}
final ECCurve curve = domainParameters.getCurve();
return toPublicParams(curve.createPoint(x, y), domainParameters);
}
/**
* 转换为ECPublicKeyParameters
*
* @param pointEncoded 被编码的曲线坐标点
* @param domainParameters ECDomainParameters
* @return ECPublicKeyParameters
* @since 5.4.3
*/
public static ECPublicKeyParameters toPublicParams(String pointEncoded, ECDomainParameters domainParameters) {
final ECCurve curve = domainParameters.getCurve();
return toPublicParams(curve.decodePoint(SecureUtil.decode(pointEncoded)), domainParameters);
}
/**
* 转换为ECPublicKeyParameters
*
* @param pointEncoded 被编码的曲线坐标点
* @param domainParameters ECDomainParameters
* @return ECPublicKeyParameters
* @since 5.4.3
*/
public static ECPublicKeyParameters toPublicParams(byte[] pointEncoded, ECDomainParameters domainParameters) {
final ECCurve curve = domainParameters.getCurve();
return toPublicParams(curve.decodePoint(pointEncoded), domainParameters);
}
/**
* 转换为ECPublicKeyParameters
*
* @param point 曲线坐标点
* @param domainParameters ECDomainParameters
* @return ECPublicKeyParameters
* @since 5.4.3
*/
public static ECPublicKeyParameters toPublicParams(org.bouncycastle.math.ec.ECPoint point, ECDomainParameters domainParameters) {
return new ECPublicKeyParameters(point, domainParameters);
}
/**
* 公钥转换为 {@link ECPublicKeyParameters}
*
* @param publicKey 公钥传入null返回null
* @return {@link ECPublicKeyParameters}或null
*/
public static ECPublicKeyParameters toPublicParams(PublicKey publicKey) {
if (null == publicKey) {
return null;
}
try {
return (ECPublicKeyParameters) ECUtil.generatePublicKeyParameter(publicKey);
} catch (InvalidKeyException e) {
throw new CryptoException(e);
}
}
//--------------------------------------------------------------------------- Private Key
/**
* 转换为 ECPrivateKeyParameters
*
* @param d 私钥d值16进制字符串
* @return ECPrivateKeyParameters
*/
public static ECPrivateKeyParameters toSm2PrivateParams(String d) {
return toPrivateParams(d, SmUtil.SM2_DOMAIN_PARAMS);
}
/**
* 转换为 ECPrivateKeyParameters
*
* @param d 私钥d值
* @return ECPrivateKeyParameters
*/
public static ECPrivateKeyParameters toSm2PrivateParams(byte[] d) {
return toPrivateParams(d, SmUtil.SM2_DOMAIN_PARAMS);
}
/**
* 转换为 ECPrivateKeyParameters
*
* @param d 私钥d值
* @return ECPrivateKeyParameters
*/
public static ECPrivateKeyParameters toSm2PrivateParams(BigInteger d) {
return toPrivateParams(d, SmUtil.SM2_DOMAIN_PARAMS);
}
/**
* 转换为 ECPrivateKeyParameters
*
* @param d 私钥d值16进制字符串
* @param domainParameters ECDomainParameters
* @return ECPrivateKeyParameters
*/
public static ECPrivateKeyParameters toPrivateParams(String d, ECDomainParameters domainParameters) {
return toPrivateParams(BigIntegers.fromUnsignedByteArray(SecureUtil.decode(d)), domainParameters);
}
/**
* 转换为 ECPrivateKeyParameters
*
* @param d 私钥d值
* @param domainParameters ECDomainParameters
* @return ECPrivateKeyParameters
*/
public static ECPrivateKeyParameters toPrivateParams(byte[] d, ECDomainParameters domainParameters) {
return toPrivateParams(BigIntegers.fromUnsignedByteArray(d), domainParameters);
}
/**
* 转换为 ECPrivateKeyParameters
*
* @param d 私钥d值
* @param domainParameters ECDomainParameters
* @return ECPrivateKeyParameters
*/
public static ECPrivateKeyParameters toPrivateParams(BigInteger d, ECDomainParameters domainParameters) {
if (null == d) {
return null;
}
return new ECPrivateKeyParameters(d, domainParameters);
}
/**
* 私钥转换为 {@link ECPrivateKeyParameters}
*
* @param privateKey 私钥传入null返回null
* @return {@link ECPrivateKeyParameters}或null
*/
public static ECPrivateKeyParameters toPrivateParams(PrivateKey privateKey) {
if (null == privateKey) {
return null;
}
try {
return (ECPrivateKeyParameters) ECUtil.generatePrivateKeyParameter(privateKey);
} catch (InvalidKeyException e) {
throw new CryptoException(e);
}
}
}

View File

@@ -618,11 +618,19 @@ public class KeyUtil {
*/
public static String getAlgorithmAfterWith(String algorithm) {
Assert.notNull(algorithm, "algorithm must be not null !");
if(StrUtil.startWithIgnoreCase(algorithm, "ECIESWith")){
return "EC";
}
int indexOfWith = StrUtil.lastIndexOfIgnoreCase(algorithm, "with");
if (indexOfWith > 0) {
algorithm = StrUtil.subSuf(algorithm, indexOfWith + "with".length());
}
if ("ECDSA".equalsIgnoreCase(algorithm) || "SM2".equalsIgnoreCase(algorithm)) {
if ("ECDSA".equalsIgnoreCase(algorithm)
|| "SM2".equalsIgnoreCase(algorithm)
|| "ECIES".equalsIgnoreCase(algorithm)
) {
algorithm = "EC";
}
return algorithm;

View File

@@ -3,6 +3,7 @@ package cn.hutool.crypto.asymmetric;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.io.FastByteArrayOutputStream;
import cn.hutool.crypto.CryptoException;
import cn.hutool.crypto.KeyUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
@@ -10,38 +11,52 @@ import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.AlgorithmParameterSpec;
/**
* 非对称加密算法
*
*
* <pre>
* 1、签名使用私钥加密公钥解密。
* 用于让所有公钥所有者验证私钥所有者的身份并且用来防止私钥所有者发布的内容被篡改,但是不用来保证内容不被他人获得。
*
*
* 2、加密用公钥加密私钥解密。
* 用于向公钥所有者发布信息,这个信息可能被他人篡改,但是无法被他人获得。
* </pre>
*
* @author Looly
*
* @author Looly
*/
public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto> {
/** Cipher负责完成加密或解密工作 */
/**
* Cipher负责完成加密或解密工作
*/
protected Cipher cipher;
/** 加密的块大小 */
/**
* 加密的块大小
*/
protected int encryptBlockSize = -1;
/** 解密的块大小 */
/**
* 解密的块大小
*/
protected int decryptBlockSize = -1;
/**
* 算法参数
*/
private AlgorithmParameterSpec algorithmParameterSpec;
// ------------------------------------------------------------------ Constructor start
/**
* 构造,创建新的私钥公钥对
*
*
* @param algorithm {@link SymmetricAlgorithm}
*/
@SuppressWarnings("RedundantCast")
@@ -51,7 +66,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
/**
* 构造,创建新的私钥公钥对
*
*
* @param algorithm 算法
*/
@SuppressWarnings("RedundantCast")
@@ -62,10 +77,10 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
/**
* 构造 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
* 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密
*
* @param algorithm {@link SymmetricAlgorithm}
*
* @param algorithm {@link SymmetricAlgorithm}
* @param privateKeyStr 私钥Hex或Base64表示
* @param publicKeyStr 公钥Hex或Base64表示
* @param publicKeyStr 公钥Hex或Base64表示
*/
public AsymmetricCrypto(AsymmetricAlgorithm algorithm, String privateKeyStr, String publicKeyStr) {
this(algorithm.getValue(), SecureUtil.decode(privateKeyStr), SecureUtil.decode(publicKeyStr));
@@ -74,10 +89,10 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
/**
* 构造 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
* 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密
*
* @param algorithm {@link SymmetricAlgorithm}
*
* @param algorithm {@link SymmetricAlgorithm}
* @param privateKey 私钥
* @param publicKey 公钥
* @param publicKey 公钥
*/
public AsymmetricCrypto(AsymmetricAlgorithm algorithm, byte[] privateKey, byte[] publicKey) {
this(algorithm.getValue(), privateKey, publicKey);
@@ -86,10 +101,10 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
/**
* 构造 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
* 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密
*
* @param algorithm {@link SymmetricAlgorithm}
*
* @param algorithm {@link SymmetricAlgorithm}
* @param privateKey 私钥
* @param publicKey 公钥
* @param publicKey 公钥
* @since 3.1.1
*/
public AsymmetricCrypto(AsymmetricAlgorithm algorithm, PrivateKey privateKey, PublicKey publicKey) {
@@ -99,10 +114,10 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
/**
* 构造 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
* 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密
*
* @param algorithm 非对称加密算法
*
* @param algorithm 非对称加密算法
* @param privateKeyBase64 私钥Base64
* @param publicKeyBase64 公钥Base64
* @param publicKeyBase64 公钥Base64
*/
public AsymmetricCrypto(String algorithm, String privateKeyBase64, String publicKeyBase64) {
this(algorithm, Base64.decode(privateKeyBase64), Base64.decode(publicKeyBase64));
@@ -110,30 +125,30 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
/**
* 构造
*
* <p>
* 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
* 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密
*
* @param algorithm 算法
*
* @param algorithm 算法
* @param privateKey 私钥
* @param publicKey 公钥
* @param publicKey 公钥
*/
public AsymmetricCrypto(String algorithm, byte[] privateKey, byte[] publicKey) {
this(algorithm, //
SecureUtil.generatePrivateKey(algorithm, privateKey), //
SecureUtil.generatePublicKey(algorithm, publicKey)//
KeyUtil.generatePrivateKey(algorithm, privateKey), //
KeyUtil.generatePublicKey(algorithm, publicKey)//
);
}
/**
* 构造
*
* <p>
* 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
* 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密
*
* @param algorithm 算法
*
* @param algorithm 算法
* @param privateKey 私钥
* @param publicKey 公钥
* @param publicKey 公钥
* @since 3.1.1
*/
public AsymmetricCrypto(String algorithm, PrivateKey privateKey, PublicKey publicKey) {
@@ -143,7 +158,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
/**
* 获取加密块大小
*
*
* @return 加密块大小
*/
public int getEncryptBlockSize() {
@@ -152,7 +167,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
/**
* 设置加密块大小
*
*
* @param encryptBlockSize 加密块大小
*/
public void setEncryptBlockSize(int encryptBlockSize) {
@@ -161,7 +176,7 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
/**
* 获取解密块大小
*
*
* @return 解密块大小
*/
public int getDecryptBlockSize() {
@@ -170,13 +185,35 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
/**
* 设置解密块大小
*
*
* @param decryptBlockSize 解密块大小
*/
public void setDecryptBlockSize(int decryptBlockSize) {
this.decryptBlockSize = decryptBlockSize;
}
/**
* 获取{@link AlgorithmParameterSpec}<br>
* 在某些算法中需要特别的参数例如在ECIES中此处为IESParameterSpec
*
* @return {@link AlgorithmParameterSpec}
* @since 5.4.3
*/
public AlgorithmParameterSpec getAlgorithmParameterSpec() {
return algorithmParameterSpec;
}
/**
* 设置{@link AlgorithmParameterSpec}<br>
* 在某些算法中需要特别的参数例如在ECIES中此处为IESParameterSpec
*
* @param algorithmParameterSpec {@link AlgorithmParameterSpec}
* @since 5.4.3
*/
public void setAlgorithmParameterSpec(AlgorithmParameterSpec algorithmParameterSpec) {
this.algorithmParameterSpec = algorithmParameterSpec;
}
@Override
public AsymmetricCrypto init(String algorithm, PrivateKey privateKey, PublicKey publicKey) {
super.init(algorithm, privateKey, publicKey);
@@ -185,10 +222,11 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
}
// --------------------------------------------------------------------------------- Encrypt
/**
* 加密
*
* @param data 被加密的bytes
*
* @param data 被加密的bytes
* @param keyType 私钥或公钥 {@link KeyType}
* @return 加密后的bytes
*/
@@ -197,12 +235,12 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
final Key key = getKeyByType(keyType);
lock.lock();
try {
cipher.init(Cipher.ENCRYPT_MODE, key);
initCipher(Cipher.ENCRYPT_MODE, key);
if(this.encryptBlockSize < 0){
if (this.encryptBlockSize < 0) {
// 在引入BC库情况下自动获取块大小
final int blockSize = this.cipher.getBlockSize();
if(blockSize > 0){
if (blockSize > 0) {
this.encryptBlockSize = blockSize;
}
}
@@ -216,10 +254,11 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
}
// --------------------------------------------------------------------------------- Decrypt
/**
* 解密
*
* @param data 被解密的bytes
*
* @param data 被解密的bytes
* @param keyType 私钥或公钥 {@link KeyType}
* @return 解密后的bytes
*/
@@ -228,12 +267,12 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
final Key key = getKeyByType(keyType);
lock.lock();
try {
cipher.init(Cipher.DECRYPT_MODE, key);
initCipher(Cipher.DECRYPT_MODE, key);
if(this.decryptBlockSize < 0){
if (this.decryptBlockSize < 0) {
// 在引入BC库情况下自动获取块大小
final int blockSize = this.cipher.getBlockSize();
if(blockSize > 0){
if (blockSize > 0) {
this.decryptBlockSize = blockSize;
}
}
@@ -250,16 +289,28 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
/**
* 获得加密或解密器
*
*
* @return 加密或解密
* @deprecated 拼写错误,请使用{@link #getCipher()}
*/
@Deprecated
public Cipher getClipher() {
return cipher;
}
/**
* 获得加密或解密器
*
* @return 加密或解密
* @since 5.4.3
*/
public Cipher getCipher() {
return cipher;
}
/**
* 初始化{@link Cipher}默认尝试加载BC库
*
*
* @since 4.5.2
*/
protected void initCipher() {
@@ -268,13 +319,13 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
/**
* 加密或解密
*
* @param data 被加密或解密的内容数据
*
* @param data 被加密或解密的内容数据
* @param maxBlockSize 最大块(分段)大小
* @return 加密或解密后的数据
* @throws IllegalBlockSizeException 分段异常
* @throws BadPaddingException padding错误异常
* @throws IOException IO异常不会被触发
* @throws BadPaddingException padding错误异常
* @throws IOException IO异常不会被触发
*/
private byte[] doFinal(byte[] data, int maxBlockSize) throws IllegalBlockSizeException, BadPaddingException, IOException {
// 模长
@@ -291,19 +342,18 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
/**
* 分段加密或解密
*
* @param data 数据
*
* @param data 数据
* @param maxBlockSize 最大分段的段大小不能为小于1
* @return 加密或解密后的数据
* @throws IllegalBlockSizeException 分段异常
* @throws BadPaddingException padding错误异常
* @throws IOException IO异常不会被触发
* @throws BadPaddingException padding错误异常
* @throws IOException IO异常不会被触发
*/
private byte[] doFinalWithBlock(byte[] data, int maxBlockSize) throws IllegalBlockSizeException, BadPaddingException, IOException {
final int dataLength = data.length;
@SuppressWarnings("resource")
final FastByteArrayOutputStream out = new FastByteArrayOutputStream();
@SuppressWarnings("resource") final FastByteArrayOutputStream out = new FastByteArrayOutputStream();
int offSet = 0;
// 剩余长度
int remainLength = dataLength;
@@ -316,7 +366,23 @@ public class AsymmetricCrypto extends AbstractAsymmetricCrypto<AsymmetricCrypto>
offSet += blockSize;
remainLength = dataLength - offSet;
}
return out.toByteArray();
}
/**
* 初始化{@link Cipher}
*
* @param mode 模式,可选{@link Cipher#ENCRYPT_MODE}或者{@link Cipher#DECRYPT_MODE}
* @param key 密钥
* @throws InvalidAlgorithmParameterException 异常算法错误
* @throws InvalidKeyException 异常KEY错误
*/
private void initCipher(int mode, Key key) throws InvalidAlgorithmParameterException, InvalidKeyException {
if (null != this.algorithmParameterSpec) {
cipher.init(mode, key, this.algorithmParameterSpec);
} else {
cipher.init(mode, key);
}
}
}

View File

@@ -196,6 +196,6 @@ public class BaseAsymmetric<T extends BaseAsymmetric<T>> {
}
return this.publicKey;
}
throw new CryptoException("Uknown key type: " + type);
throw new CryptoException("Unsupported key type: " + type);
}
}

View File

@@ -1,5 +1,7 @@
package cn.hutool.crypto.asymmetric;
import javax.crypto.Cipher;
/**
* 密钥类型
*
@@ -7,5 +9,37 @@ package cn.hutool.crypto.asymmetric;
*
*/
public enum KeyType {
PrivateKey, PublicKey
/**
* 公钥
*/
PublicKey(Cipher.PUBLIC_KEY),
/**
* 私钥
*/
PrivateKey(Cipher.PRIVATE_KEY),
/**
* 密钥
*/
SecretKey(Cipher.SECRET_KEY);
/**
* 构造
*
* @param value 见{@link Cipher}
*/
KeyType(int value) {
this.value = value;
}
private final int value;
/**
* 获取枚举值对应的int表示
*
* @return 枚举值对应的int表示
*/
public int getValue() {
return this.value;
}
}

View File

@@ -499,8 +499,10 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
*/
private SM2Engine getEngine() {
if (null == this.engine) {
Assert.notNull(this.digest, "digest must be not null !");
this.engine = new SM2Engine(this.digest, this.mode);
}
this.digest.reset();
return this.engine;
}
@@ -511,8 +513,10 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
*/
private SM2Signer getSigner() {
if (null == this.signer) {
Assert.notNull(this.digest, "digest must be not null !");
this.signer = new SM2Signer(this.encoding, this.digest);
}
this.digest.reset();
return this.signer;
}
// ------------------------------------------------------------------------------------------------------------------------- Private method end

View File

@@ -1,16 +1,17 @@
package cn.hutool.crypto.symmetric;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.CryptoException;
import cn.hutool.crypto.SecureUtil;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
/**
* RC4加密解密算法实现<br>
@@ -97,6 +98,17 @@ public class RC4 implements Serializable {
return HexUtil.encodeHexStr(encrypt(data, charset));
}
/**
* 加密使用UTF-8编码
*
* @param data 被加密的字符串
* @return 加密后的Hex
* @since 5.4.4
*/
public String encryptHex(String data) {
return HexUtil.encodeHexStr(encrypt(data));
}
/**
* 加密
*
@@ -109,6 +121,18 @@ public class RC4 implements Serializable {
return Base64.encode(encrypt(data, charset));
}
/**
* 加密使用UTF-8编码
*
* @param data 被加密的字符串
* @return 加密后的Base64
* @since 5.4.4
*/
public String encryptBase64(String data) {
return Base64.encode(encrypt(data));
}
/**
* 解密
*
@@ -132,6 +156,30 @@ public class RC4 implements Serializable {
return decrypt(message, CharsetUtil.CHARSET_UTF_8);
}
/**
* 解密Hex16进制或Base64表示的字符串使用默认编码UTF-8
*
* @param message 消息
* @return 明文
* @since 5.4.4
*/
public String decrypt(String message) {
return decrypt(SecureUtil.decode(message));
}
/**
* 解密Hex16进制或Base64表示的字符串
*
* @param message 明文
* @param charset 解密后的charset
* @return 明文
* @since 5.4.4
*/
public String decrypt(String message, Charset charset) {
return StrUtil.str(decrypt(message), charset);
}
/**
* 加密或解密指定值,调用此方法前需初始化密钥
*
@@ -216,4 +264,4 @@ public class RC4 implements Serializable {
sbox[j] = temp;
}
//----------------------------------------------------------------------------------------------------------------------- Private method end
}
}

View File

@@ -31,4 +31,19 @@ public class KeyUtilTest {
final PublicKey rsaPublicKey = KeyUtil.getRSAPublicKey(aPrivate);
Assert.assertEquals(rsaPublicKey, keyPair.getPublic());
}
/**
* 测试EC和ECIES算法生成的KEY是一致的
*/
@Test
public void generateECIESKeyTest(){
final KeyPair ecies = KeyUtil.generateKeyPair("ECIES");
Assert.assertNotNull(ecies.getPrivate());
Assert.assertNotNull(ecies.getPublic());
byte[] privateKeyBytes = ecies.getPrivate().getEncoded();
final PrivateKey privateKey = KeyUtil.generatePrivateKey("EC", privateKeyBytes);
Assert.assertEquals(ecies.getPrivate(), privateKey);
}
}

View File

@@ -1,6 +1,7 @@
package cn.hutool.crypto.test.asymmetric;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.asymmetric.AsymmetricCrypto;
import cn.hutool.crypto.asymmetric.ECIES;
import cn.hutool.crypto.asymmetric.KeyType;
import org.junit.Assert;
@@ -12,6 +13,25 @@ public class ECIESTest {
public void eciesTest(){
final ECIES ecies = new ECIES();
doTest(ecies, ecies);
}
@Test
public void eciesTest2(){
final ECIES ecies = new ECIES();
final byte[] privateKeyBytes = ecies.getPrivateKey().getEncoded();
final ECIES ecies2 = new ECIES(privateKeyBytes, null);
doTest(ecies, ecies2);
}
/**
* 测试用例
*
* @param cryptoForEncrypt 加密的Crypto
* @param cryptoForDecrypt 解密的Crypto
*/
private void doTest(AsymmetricCrypto cryptoForEncrypt, AsymmetricCrypto cryptoForDecrypt){
String textBase = "我是一段特别长的测试";
StringBuilder text = new StringBuilder();
for (int i = 0; i < 10; i++) {
@@ -19,8 +39,9 @@ public class ECIESTest {
}
// 公钥加密,私钥解密
String encryptStr = ecies.encryptBase64(text.toString(), KeyType.PublicKey);
String decryptStr = StrUtil.utf8Str(ecies.decrypt(encryptStr, KeyType.PrivateKey));
String encryptStr = cryptoForEncrypt.encryptBase64(text.toString(), KeyType.PublicKey);
String decryptStr = StrUtil.utf8Str(cryptoForDecrypt.decrypt(encryptStr, KeyType.PrivateKey));
Assert.assertEquals(text.toString(), decryptStr);
}
}

View File

@@ -1,15 +1,19 @@
package cn.hutool.crypto.test.asymmetric;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.lang.Console;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.ECKeyUtil;
import cn.hutool.crypto.KeyUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.junit.Assert;
import org.junit.Test;
@@ -205,4 +209,38 @@ public class SM2Test {
String sign = "DCA0E80A7F46C93714B51C3EFC55A922BCEF7ECF0FE9E62B53BA6A7438B543A76C145A452CA9036F3CB70D7E6C67D4D9D7FE114E5367A2F6F5A4D39F2B10F3D6";
Assert.assertTrue(sm2.verifyHex(data, sign));
}
@Test
public void sm2PlainWithPointTest2() {
String d = "4BD9A450D7E68A5D7E08EB7A0BFA468FD3EB32B71126246E66249A73A9E4D44A";
String q = "04970AB36C3B870FBC04041087DB1BC36FB4C6E125B5EA406DB0EC3E2F80F0A55D8AFF28357A0BB215ADC2928BE76F1AFF869BF4C0A3852A78F3B827812C650AD3";
String data = "123456";
final ECPublicKeyParameters ecPublicKeyParameters = ECKeyUtil.toSm2PublicParams(q);
final ECPrivateKeyParameters ecPrivateKeyParameters = ECKeyUtil.toSm2PrivateParams(d);
final SM2 sm2 = new SM2(ecPrivateKeyParameters, ecPublicKeyParameters);
sm2.setMode(SM2Engine.Mode.C1C2C3);
final String encryptHex = sm2.encryptHex(data, KeyType.PublicKey);
Console.log(encryptHex);
final String decryptStr = sm2.decryptStr(encryptHex, KeyType.PrivateKey);
Assert.assertEquals(data, decryptStr);
}
@Test
public void encryptAndSignTest(){
SM2 sm2 = SmUtil.sm2();
String src = "Sm2Test";
byte[] data = sm2.encrypt(src, KeyType.PublicKey);
byte[] sign = sm2.sign(src.getBytes());
Assert.assertTrue(sm2.verify( src.getBytes(), sign));
byte[] dec = sm2.decrypt(data, KeyType.PrivateKey);
Assert.assertArrayEquals(dec, src.getBytes());
}
}

View File

@@ -17,4 +17,22 @@ public class AESTest {
Assert.assertEquals("d637735ae9e21ba50cb686b74fab8d2c", encryptHex);
}
@Test
public void encryptTest2() {
String content = "test中文";
AES aes = new AES(Mode.CTS, Padding.PKCS5Padding,
"0CoJUm6Qyw8W8jue".getBytes(), "0102030405060708".getBytes());
final String encryptHex = aes.encryptHex(content);
Assert.assertEquals("8dc9de7f050e86ca2c8261dde56dfec9", encryptHex);
}
@Test
public void encryptPKCS7Test() {
// 构建
AES aes = new AES(Mode.CBC.name(), "pkcs7padding",
"1234567890123456".getBytes(), "1234567890123456".getBytes());
String encryptHex = aes.encryptHex("123456");
Assert.assertEquals("d637735ae9e21ba50cb686b74fab8d2c", encryptHex);
}
}

View File

@@ -1,5 +1,6 @@
package cn.hutool.crypto.test.symmetric;
import cn.hutool.core.util.CharsetUtil;
import org.junit.Assert;
import org.junit.Test;
@@ -36,4 +37,35 @@ public class RC4Test {
String msg2 = rc4.decrypt(crypt2);
Assert.assertEquals(message2, msg2);
}
@Test
public void testDecryptWithHexMessage() {
String message = "这是第一个用来测试密文为十六进制字符串的消息!";
String key = "生成一个密钥";
RC4 rc4 = new RC4(key);
String encryptHex = rc4.encryptHex(message, CharsetUtil.CHARSET_UTF_8);
String msg = rc4.decrypt(encryptHex);
Assert.assertEquals(message, msg);
String message2 = "这是第二个用来测试密文为十六进制字符串的消息!";
String encryptHex2 = rc4.encryptHex(message2);
String msg2 = rc4.decrypt(encryptHex2);
Assert.assertEquals(message2, msg2);
}
@Test
public void testDecryptWithBase64Message() {
String message = "这是第一个用来测试密文为Base64编码的消息";
String key = "生成一个密钥";
RC4 rc4 = new RC4(key);
String encryptHex = rc4.encryptBase64(message, CharsetUtil.CHARSET_UTF_8);
String msg = rc4.decrypt(encryptHex);
Assert.assertEquals(message, msg);
String message2 = "这是第一个用来测试密文为Base64编码的消息";
String encryptHex2 = rc4.encryptBase64(message2);
String msg2 = rc4.decrypt(encryptHex2);
Assert.assertEquals(message2, msg2);
}
}

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