mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-05-09 23:51:34 +08:00
Merge remote-tracking branch 'upstream/v5-dev' into v5-dev
This commit is contained in:
commit
bb5b524418
30
CHANGELOG.md
30
CHANGELOG.md
@ -3,17 +3,45 @@
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
# 5.5.9 (2021-02-18)
|
||||
# 5.6.0 (2021-03-10)
|
||||
|
||||
### 新特性
|
||||
* 【poi 】 重要:不再兼容POI-3.x,增加兼容POI-5.x(issue#I35J6B@Gitee)
|
||||
* 【core 】 FileTypeUtil使用长匹配优先(pr#1457@Github)
|
||||
* 【core 】 IterUtil和CollUtil增加isEqualList方法(issue#I3A3PY@Gitee)
|
||||
* 【crypto 】 增加PBKDF2(issue#1416@Github)
|
||||
* 【core 】 增加FuncKeyMap(issue#1402@Github)
|
||||
* 【core 】 增加StrMatcher(issue#1379@Github)
|
||||
* 【core 】 NumberUtil增加factorial针对BigInterger方法(issue#1379@Github)
|
||||
* 【core 】 TreeNode增加equals方法(issue#1467@Github)
|
||||
|
||||
### Bug修复
|
||||
* 【socket 】 修复Client创建失败资源未释放问题。
|
||||
* 【core 】 修复DataSizeUtil中EB单位错误问题(issue#I39O7I@Gitee)
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
# 5.5.9 (2021-02-26)
|
||||
|
||||
### 新特性
|
||||
* 【crypto 】 PemUtil.readPemKey支持EC(pr#1366@Github)
|
||||
* 【extra 】 Ftp等cd方法增加同步(issue#1397@Github)
|
||||
* 【core 】 StrUtil增加endWithAnyIgnoreCase(issue#I37I0B@Gitee)
|
||||
* 【crypto 】 Sm2增加getD和getQ方法(issue#I37Z4C@Gitee)
|
||||
* 【cache 】 AbstractCache增加keySet方法(issue#I37Z4C@Gitee)
|
||||
* 【core 】 NumberWordFormatter增加formatSimple方法(pr#1436@Github)
|
||||
* 【crypto 】 增加读取openSSL生成的sm2私钥
|
||||
* 【crypto 】 增加众多方法,SM2兼容各类密钥格式(issue#I37Z75@Gitee)
|
||||
|
||||
### Bug修复
|
||||
* 【json 】 JSONUtil.isJson方法改变trim策略,解决特殊空白符导致判断失败问题
|
||||
* 【json 】 修复SQLEXception导致的栈溢出(issue#1399@Github)
|
||||
* 【extra 】 修复Ftp中异常参数没有传入问题(issue#1397@Github)
|
||||
* 【crypto 】 修复Sm2使用D构造空指针问题(issue#I37Z4C@Gitee)
|
||||
* 【poi 】 修复ExcelPicUtil中图表报错问题(issue#I38857@Gitee)
|
||||
* 【core 】 修复ListUtil.page方法返回空列表无法编辑问题(issue#1415@Github)
|
||||
* 【core 】 修复ListUtil.sub中step不通结果不一致问题(issue#1409@Github)
|
||||
* 【db 】 修复Condition转换参数值时未转换数字异常(issue#I38LTM@Gitee)
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
@ -125,19 +125,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.5.9</version>
|
||||
<version>5.6.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### Gradle
|
||||
```
|
||||
compile 'cn.hutool:hutool-all:5.5.9'
|
||||
compile 'cn.hutool:hutool-all:5.6.0'
|
||||
```
|
||||
|
||||
## Download
|
||||
|
||||
- [Maven1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.5.9/)
|
||||
- [Maven2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.5.9/)
|
||||
- [Maven1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.6.0/)
|
||||
- [Maven2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.6.0/)
|
||||
|
||||
> 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.
|
||||
|
@ -123,21 +123,21 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.5.9</version>
|
||||
<version>5.6.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### Gradle
|
||||
```
|
||||
compile 'cn.hutool:hutool-all:5.5.9'
|
||||
compile 'cn.hutool:hutool-all:5.6.0'
|
||||
```
|
||||
|
||||
### 非Maven项目
|
||||
|
||||
点击以下任一链接,下载`hutool-all-X.X.X.jar`即可:
|
||||
|
||||
- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.5.9/)
|
||||
- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.5.9/)
|
||||
- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.6.0/)
|
||||
- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.6.0/)
|
||||
|
||||
> 注意
|
||||
> Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。
|
||||
|
@ -1 +1 @@
|
||||
5.5.9
|
||||
5.6.0
|
||||
|
@ -1 +1 @@
|
||||
var version = '5.5.9'
|
||||
var version = '5.6.0'
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.9-SNAPSHOT</version>
|
||||
<version>5.6.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-all</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.9-SNAPSHOT</version>
|
||||
<version>5.6.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-aop</artifactId>
|
||||
|
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.9-SNAPSHOT</version>
|
||||
<version>5.6.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-bloomFilter</artifactId>
|
||||
|
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.9-SNAPSHOT</version>
|
||||
<version>5.6.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-bom</artifactId>
|
||||
|
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.9-SNAPSHOT</version>
|
||||
<version>5.6.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-cache</artifactId>
|
||||
|
@ -7,6 +7,7 @@ import cn.hutool.core.lang.func.Func0;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
@ -307,11 +308,22 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
||||
* @return this
|
||||
* @since 5.5.2
|
||||
*/
|
||||
@Override
|
||||
public AbstractCache<K, V> setListener(CacheListener<K, V> listener) {
|
||||
this.listener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回所有键
|
||||
*
|
||||
* @return 所有键
|
||||
* @since 5.5.9
|
||||
*/
|
||||
public Set<K> keySet(){
|
||||
return this.cacheMap.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象移除回调。默认无动作<br>
|
||||
* 子类可重写此方法用于监听移除事件,如果重写,listener将无效
|
||||
|
@ -48,7 +48,7 @@ public class LRUCache<K, V> extends AbstractCache<K, V> {
|
||||
// ---------------------------------------------------------------- prune
|
||||
|
||||
/**
|
||||
* 只清理超时对象,LRU的实现会交给<code>LinkedHashMap</code>
|
||||
* 只清理超时对象,LRU的实现会交给{@code LinkedHashMap}
|
||||
*/
|
||||
@Override
|
||||
protected int pruneCache() {
|
||||
|
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.9-SNAPSHOT</version>
|
||||
<version>5.6.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-captcha</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.9-SNAPSHOT</version>
|
||||
<version>5.6.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-core</artifactId>
|
||||
|
@ -60,7 +60,6 @@ public class CompareToBuilder implements Builder<Integer> {
|
||||
* 构造,构造后调用append方法增加比较项,然后调用{@link #toComparison()}获取结果
|
||||
*/
|
||||
public CompareToBuilder() {
|
||||
super();
|
||||
comparison = 0;
|
||||
}
|
||||
|
||||
|
@ -103,7 +103,7 @@ public class HashCodeBuilder implements Builder<Integer> {
|
||||
*
|
||||
* @since 2.3
|
||||
*/
|
||||
private static final ThreadLocal<Set<IDKey>> REGISTRY = new ThreadLocal<Set<IDKey>>();
|
||||
private static final ThreadLocal<Set<IDKey>> REGISTRY = new ThreadLocal<>();
|
||||
|
||||
/*
|
||||
* NOTE: we cannot store the actual objects in a HashSet, as that would use the very hashCode()
|
||||
|
@ -2780,11 +2780,11 @@ public class CollUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 取最大值
|
||||
* 取最小值
|
||||
*
|
||||
* @param <T> 元素类型
|
||||
* @param coll 集合
|
||||
* @return 最大值
|
||||
* @return 最小值
|
||||
* @see Collections#min(Collection)
|
||||
* @since 4.6.5
|
||||
*/
|
||||
@ -2988,4 +2988,25 @@ public class CollUtil {
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断两个{@link Collection} 是否元素和顺序相同,返回{@code true}的条件是:
|
||||
* <ul>
|
||||
* <li>两个{@link Collection}必须长度相同</li>
|
||||
* <li>两个{@link Collection}元素相同index的对象必须equals,满足{@link Objects#equals(Object, Object)}</li>
|
||||
* </ul>
|
||||
* 此方法来自Apache-Commons-Collections4。
|
||||
*
|
||||
* @param list1 列表1
|
||||
* @param list2 列表2
|
||||
* @return 是否相同
|
||||
* @since 5.6.0
|
||||
*/
|
||||
public static boolean isEqualList(final Collection<?> list1, final Collection<?> list2) {
|
||||
if (list1 == null || list2 == null || list1.size() != list2.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return IterUtil.isEqualList(list1, list2);
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
@ -612,7 +613,7 @@ public class IterUtil {
|
||||
/**
|
||||
* Enumeration转换为Iterator
|
||||
* <p>
|
||||
* Adapt the specified <code>Enumeration</code> to the <code>Iterator</code> interface
|
||||
* Adapt the specified {@code Enumeration} to the {@code Iterator} interface
|
||||
*
|
||||
* @param <E> 集合元素类型
|
||||
* @param e {@link Enumeration}
|
||||
@ -859,4 +860,39 @@ public class IterUtil {
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断两个{@link Iterable} 是否元素和顺序相同,返回{@code true}的条件是:
|
||||
* <ul>
|
||||
* <li>两个{@link Iterable}必须长度相同</li>
|
||||
* <li>两个{@link Iterable}元素相同index的对象必须equals,满足{@link Objects#equals(Object, Object)}</li>
|
||||
* </ul>
|
||||
* 此方法来自Apache-Commons-Collections4。
|
||||
*
|
||||
* @param list1 列表1
|
||||
* @param list2 列表2
|
||||
* @return 是否相同
|
||||
* @since 5.6.0
|
||||
*/
|
||||
public static boolean isEqualList(final Iterable<?> list1, final Iterable<?> list2) {
|
||||
if (list1 == list2) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final Iterator<?> it1 = list1.iterator();
|
||||
final Iterator<?> it2 = list2.iterator();
|
||||
Object obj1;
|
||||
Object obj2;
|
||||
while (it1.hasNext() && it2.hasNext()) {
|
||||
obj1 = it1.next();
|
||||
obj2 = it2.next();
|
||||
|
||||
if (false == Objects.equals(obj1, obj2)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 当两个Iterable长度不一致时返回false
|
||||
return false == (it1.hasNext() || it2.hasNext());
|
||||
}
|
||||
}
|
||||
|
@ -246,7 +246,7 @@ public class ListUtil {
|
||||
// 每页条目数大于总数直接返回所有
|
||||
if (resultSize <= pageSize) {
|
||||
if (pageNo < (PageUtil.getFirstPageNo() + 1)) {
|
||||
return Collections.unmodifiableList(list);
|
||||
return unmodifiable(list);
|
||||
} else {
|
||||
// 越界直接返回空
|
||||
return new ArrayList<>(0);
|
||||
@ -262,11 +262,11 @@ public class ListUtil {
|
||||
if (startEnd[1] > resultSize) {
|
||||
startEnd[1] = resultSize;
|
||||
if (startEnd[0] > startEnd[1]) {
|
||||
return empty();
|
||||
return new ArrayList<>(0);
|
||||
}
|
||||
}
|
||||
|
||||
return list.subList(startEnd[0], startEnd[1]);
|
||||
return sub(list, startEnd[0], startEnd[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -366,7 +366,8 @@ public class ListUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 截取集合的部分
|
||||
* 截取集合的部分<br>
|
||||
* 此方法与{@link List#subList(int, int)} 不同在于子列表是新的副本,操作子列表不会影响原列表。
|
||||
*
|
||||
* @param <T> 集合元素类型
|
||||
* @param list 被截取的数组
|
||||
@ -407,8 +408,8 @@ public class ListUtil {
|
||||
end = size;
|
||||
}
|
||||
|
||||
if (step <= 1) {
|
||||
return list.subList(start, end);
|
||||
if (step < 1) {
|
||||
step = 1;
|
||||
}
|
||||
|
||||
final List<T> result = new ArrayList<>();
|
||||
|
@ -21,7 +21,6 @@ public class ComparableComparator<E extends Comparable<? super E>> implements Co
|
||||
* 构造
|
||||
*/
|
||||
public ComparableComparator() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -963,6 +963,21 @@ public class Convert {
|
||||
return NumberWordFormatter.format(number);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将阿拉伯数字转为精简表示形式,例如:
|
||||
*
|
||||
* <pre>
|
||||
* 1200 -》 1.2k
|
||||
* </pre>
|
||||
*
|
||||
* @param number {@link Number}对象
|
||||
* @return 英文表达式
|
||||
* @since 5.5.9
|
||||
*/
|
||||
public static String numberToSimple(Number number) {
|
||||
return NumberWordFormatter.formatSimple(number.longValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* 将阿拉伯数字转为中文表达方式
|
||||
*
|
||||
|
@ -1,14 +1,13 @@
|
||||
package cn.hutool.core.convert;
|
||||
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
/**
|
||||
* 将浮点数类型的number转换成英语的表达方式 <br>
|
||||
* 参考博客:http://blog.csdn.net/eric_sunah/article/details/8713226
|
||||
*
|
||||
* @author Looly
|
||||
* @author Looly,totalo
|
||||
* @since 3.0.9
|
||||
*/
|
||||
public class NumberWordFormatter {
|
||||
@ -33,33 +32,37 @@ public class NumberWordFormatter {
|
||||
if (x != null) {
|
||||
return format(x.toString());
|
||||
} else {
|
||||
return "";
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将阿拉伯数字转化为简介计数单位,例如 2100 => 2.1k
|
||||
* 将阿拉伯数字转化为简洁计数单位,例如 2100 =》 2.1k
|
||||
* 范围默认只到w
|
||||
* @param value
|
||||
* @return
|
||||
*
|
||||
* @param value 被格式化的数字
|
||||
* @return 格式化后的数字
|
||||
* @since 5.5.9
|
||||
*/
|
||||
public static String formatValue(long value) {
|
||||
return formatValue(value, true);
|
||||
public static String formatSimple(long value) {
|
||||
return formatSimple(value, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将阿拉伯数字转化为简介计数单位,例如 2100 => 2.1k
|
||||
* 将阿拉伯数字转化为简介计数单位,例如 2100 =》 2.1k
|
||||
*
|
||||
* @param value 对应数字的值
|
||||
* @param isTwo 控制是否为k、w
|
||||
* @return
|
||||
* @param isTwo 控制是否为只为k、w,例如当为{@code false}时返回4.38m,{@code true}返回438.43w
|
||||
* @return 格式化后的数字
|
||||
* @since 5.5.9
|
||||
*/
|
||||
public static String formatValue(long value, boolean isTwo) {
|
||||
public static String formatSimple(long value, boolean isTwo) {
|
||||
if (value < 1000) {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
int index = -1;
|
||||
double res = value * 1.0d;
|
||||
while (res > 10 && (!isTwo || index < 1)) {
|
||||
double res = value;
|
||||
while (res > 10 && (false == isTwo || index < 1)) {
|
||||
if (res > 1000) {
|
||||
res = res / 1000;
|
||||
index++;
|
||||
@ -69,8 +72,7 @@ public class NumberWordFormatter {
|
||||
index++;
|
||||
}
|
||||
}
|
||||
DecimalFormat decimalFormat = new DecimalFormat("#.##");
|
||||
return String.format("%s%s", decimalFormat.format(res), NUMBER_SUFFIX[index]);
|
||||
return String.format("%s%s", NumberUtil.decimalFormat("#.##", res), NUMBER_SUFFIX[index]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -694,7 +694,6 @@ public class FastDatePrinter extends AbstractDateBasic implements DatePrinter {
|
||||
*
|
||||
*/
|
||||
UnpaddedMonthField() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -833,7 +832,6 @@ public class FastDatePrinter extends AbstractDateBasic implements DatePrinter {
|
||||
* Constructs an instance of {@code TwoDigitYearField}.
|
||||
*/
|
||||
TwoDigitYearField() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -873,7 +871,6 @@ public class FastDatePrinter extends AbstractDateBasic implements DatePrinter {
|
||||
* Constructs an instance of {@code TwoDigitMonthField}.
|
||||
*/
|
||||
TwoDigitMonthField() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -27,7 +27,6 @@ public final class FastStringWriter extends Writer {
|
||||
* @param initialSize 初始容量
|
||||
*/
|
||||
public FastStringWriter(int initialSize) {
|
||||
super();
|
||||
if (initialSize < 0) {
|
||||
initialSize = StrBuilder.DEFAULT_CAPACITY;
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentSkipListMap;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
@ -23,7 +23,15 @@ public class FileTypeUtil {
|
||||
private static final Map<String, String> FILE_TYPE_MAP;
|
||||
|
||||
static {
|
||||
FILE_TYPE_MAP = new ConcurrentHashMap<>();
|
||||
FILE_TYPE_MAP = new ConcurrentSkipListMap<>((s1, s2) -> {
|
||||
int len1 = s1.length();
|
||||
int len2 = s2.length();
|
||||
if (len1 == len2) {
|
||||
return s1.compareTo(s2);
|
||||
} else {
|
||||
return len2 - len1;
|
||||
}
|
||||
});
|
||||
|
||||
FILE_TYPE_MAP.put("ffd8ff", "jpg"); // JPEG (jpg)
|
||||
FILE_TYPE_MAP.put("89504e47", "png"); // PNG (png)
|
||||
@ -50,22 +58,23 @@ public class FileTypeUtil {
|
||||
FILE_TYPE_MAP.put("52494646e27807005741", "wav"); // Wave (wav)
|
||||
FILE_TYPE_MAP.put("52494646d07d60074156", "avi");
|
||||
FILE_TYPE_MAP.put("4d546864000000060001", "mid"); // MIDI (mid)
|
||||
FILE_TYPE_MAP.put("526172211a0700cf9073", "rar");// WinRAR
|
||||
FILE_TYPE_MAP.put("526172211a0700cf9073", "rar"); // WinRAR
|
||||
FILE_TYPE_MAP.put("235468697320636f6e66", "ini");
|
||||
FILE_TYPE_MAP.put("504B0304140000000800", "ofd"); // ofd文件 国标版式文件
|
||||
FILE_TYPE_MAP.put("504B03040a0000000000", "jar");
|
||||
FILE_TYPE_MAP.put("504B0304140008000800", "jar");
|
||||
// MS Excel 注意:word、msi 和 excel的文件头一样
|
||||
FILE_TYPE_MAP.put("d0cf11e0a1b11ae10", "xls");
|
||||
FILE_TYPE_MAP.put("504B0304", "zip");
|
||||
FILE_TYPE_MAP.put("4d5a9000030000000400", "exe");// 可执行文件
|
||||
FILE_TYPE_MAP.put("3c25402070616765206c", "jsp");// jsp文件
|
||||
FILE_TYPE_MAP.put("4d616e69666573742d56", "mf");// MF文件
|
||||
FILE_TYPE_MAP.put("7061636b616765207765", "java");// java文件
|
||||
FILE_TYPE_MAP.put("406563686f206f66660d", "bat");// bat文件
|
||||
FILE_TYPE_MAP.put("1f8b0800000000000000", "gz");// gz文件
|
||||
FILE_TYPE_MAP.put("cafebabe0000002e0041", "class");// bat文件
|
||||
FILE_TYPE_MAP.put("49545346030000006000", "chm");// bat文件
|
||||
FILE_TYPE_MAP.put("04000000010000001300", "mxp");// bat文件
|
||||
FILE_TYPE_MAP.put("4d5a9000030000000400", "exe"); // 可执行文件
|
||||
FILE_TYPE_MAP.put("3c25402070616765206c", "jsp"); // jsp文件
|
||||
FILE_TYPE_MAP.put("4d616e69666573742d56", "mf"); // MF文件
|
||||
FILE_TYPE_MAP.put("7061636b616765207765", "java"); // java文件
|
||||
FILE_TYPE_MAP.put("406563686f206f66660d", "bat"); // bat文件
|
||||
FILE_TYPE_MAP.put("1f8b0800000000000000", "gz"); // gz文件
|
||||
FILE_TYPE_MAP.put("cafebabe0000002e0041", "class"); // class文件
|
||||
FILE_TYPE_MAP.put("49545346030000006000", "chm"); // chm文件
|
||||
FILE_TYPE_MAP.put("04000000010000001300", "mxp"); // mxp文件
|
||||
FILE_TYPE_MAP.put("6431303a637265617465", "torrent");
|
||||
FILE_TYPE_MAP.put("6D6F6F76", "mov"); // Quicktime (mov)
|
||||
FILE_TYPE_MAP.put("FF575043", "wpd"); // WordPerfect (wpd)
|
||||
@ -85,7 +94,7 @@ public class FileTypeUtil {
|
||||
* @return 之前已经存在的文件扩展名
|
||||
*/
|
||||
public static String putFileType(String fileStreamHexHead, String extName) {
|
||||
return FILE_TYPE_MAP.put(fileStreamHexHead.toLowerCase(), extName);
|
||||
return FILE_TYPE_MAP.put(fileStreamHexHead, extName);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -95,7 +104,7 @@ public class FileTypeUtil {
|
||||
* @return 移除的文件扩展名
|
||||
*/
|
||||
public static String removeFileType(String fileStreamHexHead) {
|
||||
return FILE_TYPE_MAP.remove(fileStreamHexHead.toLowerCase());
|
||||
return FILE_TYPE_MAP.remove(fileStreamHexHead);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -19,7 +19,6 @@ public class MultiFileResource extends MultiResource{
|
||||
* @param files 文件资源列表
|
||||
*/
|
||||
public MultiFileResource(Collection<File> files) {
|
||||
super();
|
||||
add(files);
|
||||
}
|
||||
|
||||
@ -29,7 +28,6 @@ public class MultiFileResource extends MultiResource{
|
||||
* @param files 文件资源列表
|
||||
*/
|
||||
public MultiFileResource(File... files) {
|
||||
super();
|
||||
add(files);
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,7 @@ public enum DataUnit {
|
||||
*/
|
||||
TERABYTES("TB", DataSize.ofTerabytes(1));
|
||||
|
||||
public static final String[] UNIT_NAMES = new String[]{"B", "kB", "MB", "GB", "TB", "EB"};
|
||||
public static final String[] UNIT_NAMES = new String[]{"B", "kB", "MB", "GB", "TB", "PB", "EB"};
|
||||
|
||||
private final String suffix;
|
||||
|
||||
|
@ -30,7 +30,6 @@ public final class Holder<T> extends MutableObj<T>{
|
||||
* 构造
|
||||
*/
|
||||
public Holder() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -17,7 +17,6 @@ public class MutableBool implements Comparable<MutableBool>, Mutable<Boolean>, S
|
||||
* 构造,默认值0
|
||||
*/
|
||||
public MutableBool() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -25,7 +24,6 @@ public class MutableBool implements Comparable<MutableBool>, Mutable<Boolean>, S
|
||||
* @param value 值
|
||||
*/
|
||||
public MutableBool(final boolean value) {
|
||||
super();
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@ -35,7 +33,6 @@ public class MutableBool implements Comparable<MutableBool>, Mutable<Boolean>, S
|
||||
* @throws NumberFormatException 转为Boolean错误
|
||||
*/
|
||||
public MutableBool(final String value) throws NumberFormatException {
|
||||
super();
|
||||
this.value = Boolean.parseBoolean(value);
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,6 @@ public class MutableByte extends Number implements Comparable<MutableByte>, Muta
|
||||
* 构造,默认值0
|
||||
*/
|
||||
public MutableByte() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -25,7 +24,6 @@ public class MutableByte extends Number implements Comparable<MutableByte>, Muta
|
||||
* @param value 值
|
||||
*/
|
||||
public MutableByte(final byte value) {
|
||||
super();
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@ -43,7 +41,6 @@ public class MutableByte extends Number implements Comparable<MutableByte>, Muta
|
||||
* @throws NumberFormatException 转为Byte错误
|
||||
*/
|
||||
public MutableByte(final String value) throws NumberFormatException {
|
||||
super();
|
||||
this.value = Byte.parseByte(value);
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,6 @@ public class MutableDouble extends Number implements Comparable<MutableDouble>,
|
||||
* 构造,默认值0
|
||||
*/
|
||||
public MutableDouble() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -25,7 +24,6 @@ public class MutableDouble extends Number implements Comparable<MutableDouble>,
|
||||
* @param value 值
|
||||
*/
|
||||
public MutableDouble(final double value) {
|
||||
super();
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@ -43,7 +41,6 @@ public class MutableDouble extends Number implements Comparable<MutableDouble>,
|
||||
* @throws NumberFormatException 数字转换错误
|
||||
*/
|
||||
public MutableDouble(final String value) throws NumberFormatException {
|
||||
super();
|
||||
this.value = Double.parseDouble(value);
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,6 @@ public class MutableFloat extends Number implements Comparable<MutableFloat>, Mu
|
||||
* 构造,默认值0
|
||||
*/
|
||||
public MutableFloat() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -25,7 +24,6 @@ public class MutableFloat extends Number implements Comparable<MutableFloat>, Mu
|
||||
* @param value 值
|
||||
*/
|
||||
public MutableFloat(final float value) {
|
||||
super();
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@ -43,7 +41,6 @@ public class MutableFloat extends Number implements Comparable<MutableFloat>, Mu
|
||||
* @throws NumberFormatException 数字转换错误
|
||||
*/
|
||||
public MutableFloat(final String value) throws NumberFormatException {
|
||||
super();
|
||||
this.value = Float.parseFloat(value);
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,6 @@ public class MutableInt extends Number implements Comparable<MutableInt>, Mutabl
|
||||
* 构造,默认值0
|
||||
*/
|
||||
public MutableInt() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -25,7 +24,6 @@ public class MutableInt extends Number implements Comparable<MutableInt>, Mutabl
|
||||
* @param value 值
|
||||
*/
|
||||
public MutableInt(final int value) {
|
||||
super();
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@ -43,7 +41,6 @@ public class MutableInt extends Number implements Comparable<MutableInt>, Mutabl
|
||||
* @throws NumberFormatException 数字转换错误
|
||||
*/
|
||||
public MutableInt(final String value) throws NumberFormatException {
|
||||
super();
|
||||
this.value = Integer.parseInt(value);
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,6 @@ public class MutableLong extends Number implements Comparable<MutableLong>, Muta
|
||||
* 构造,默认值0
|
||||
*/
|
||||
public MutableLong() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -25,7 +24,6 @@ public class MutableLong extends Number implements Comparable<MutableLong>, Muta
|
||||
* @param value 值
|
||||
*/
|
||||
public MutableLong(final long value) {
|
||||
super();
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@ -43,7 +41,6 @@ public class MutableLong extends Number implements Comparable<MutableLong>, Muta
|
||||
* @throws NumberFormatException 数字转换错误
|
||||
*/
|
||||
public MutableLong(final String value) throws NumberFormatException {
|
||||
super();
|
||||
this.value = Long.parseLong(value);
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,6 @@ public class MutableObj<T> implements Mutable<T>, Serializable {
|
||||
* 构造,空值
|
||||
*/
|
||||
public MutableObj() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -26,7 +25,6 @@ public class MutableObj<T> implements Mutable<T>, Serializable {
|
||||
* @param value 值
|
||||
*/
|
||||
public MutableObj(final T value) {
|
||||
super();
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,6 @@ public class MutableShort extends Number implements Comparable<MutableShort>, Mu
|
||||
* 构造,默认值0
|
||||
*/
|
||||
public MutableShort() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -25,7 +24,6 @@ public class MutableShort extends Number implements Comparable<MutableShort>, Mu
|
||||
* @param value 值
|
||||
*/
|
||||
public MutableShort(final short value) {
|
||||
super();
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@ -43,7 +41,6 @@ public class MutableShort extends Number implements Comparable<MutableShort>, Mu
|
||||
* @throws NumberFormatException 转为Short错误
|
||||
*/
|
||||
public MutableShort(final String value) throws NumberFormatException {
|
||||
super();
|
||||
this.value = Short.parseShort(value);
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,6 @@ public class Tree<T> extends LinkedHashMap<String, Object> implements Node<T> {
|
||||
* @param treeNodeConfig TreeNode配置
|
||||
*/
|
||||
public Tree(TreeNodeConfig treeNodeConfig) {
|
||||
super();
|
||||
this.treeNodeConfig = ObjectUtil.defaultIfNull(
|
||||
treeNodeConfig, TreeNodeConfig.DEFAULT_CONFIG);
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package cn.hutool.core.lang.tree;
|
||||
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 树节点 每个属性都可以在{@link TreeNodeConfig}中被重命名<br>
|
||||
@ -129,4 +130,21 @@ public class TreeNode<T> implements Node<T> {
|
||||
this.extra = extra;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
TreeNode<?> treeNode = (TreeNode<?>) o;
|
||||
return Objects.equals(id, treeNode.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id);
|
||||
}
|
||||
}
|
||||
|
45
hutool-core/src/main/java/cn/hutool/core/map/FuncKeyMap.java
Normal file
45
hutool-core/src/main/java/cn/hutool/core/map/FuncKeyMap.java
Normal file
@ -0,0 +1,45 @@
|
||||
package cn.hutool.core.map;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 自定义函数Key风格的Map
|
||||
*
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
* @author Looly
|
||||
* @since 5.6.0
|
||||
*/
|
||||
public class FuncKeyMap<K, V> extends CustomKeyMap<K, V> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Function<Object, K> keyFunc;
|
||||
|
||||
// ------------------------------------------------------------------------- Constructor start
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param m Map
|
||||
* @param keyFunc 自定义KEY的函数
|
||||
*/
|
||||
public FuncKeyMap(Map<K, V> m, Function<Object, K> keyFunc) {
|
||||
super(m);
|
||||
}
|
||||
// ------------------------------------------------------------------------- Constructor end
|
||||
|
||||
/**
|
||||
* 将Key转为驼峰风格,如果key为字符串的话
|
||||
*
|
||||
* @param key KEY
|
||||
* @return 驼峰Key
|
||||
*/
|
||||
@Override
|
||||
protected Object customKey(Object key) {
|
||||
if (null != this.keyFunc) {
|
||||
return keyFunc.apply(key);
|
||||
}
|
||||
return key;
|
||||
}
|
||||
}
|
116
hutool-core/src/main/java/cn/hutool/core/text/StrMatcher.java
Normal file
116
hutool-core/src/main/java/cn/hutool/core/text/StrMatcher.java
Normal file
@ -0,0 +1,116 @@
|
||||
package cn.hutool.core.text;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 字符串模式匹配,使用${XXXXX}作为变量,例如:
|
||||
*
|
||||
* <pre>
|
||||
* pattern: ${name}-${age}-${gender}-${country}-${province}-${city}-${status}
|
||||
* text: "小明-19-男-中国-河南-郑州-已婚"
|
||||
* result: {name=小明, age=19, gender=男, country=中国, province=河南, city=郑州, status=已婚}
|
||||
* </pre>
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.6.0
|
||||
*/
|
||||
public class StrMatcher {
|
||||
|
||||
List<String> patterns;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param pattern 模式,变量用${XXX}占位
|
||||
*/
|
||||
public StrMatcher(String pattern) {
|
||||
this.patterns = parse(pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* 匹配并提取匹配到的内容
|
||||
* @param text 被匹配的文本
|
||||
* @return 匹配的map,key为变量名,value为匹配到的值
|
||||
*/
|
||||
public Map<String, String> match(String text) {
|
||||
final HashMap<String, String> result = MapUtil.newHashMap(true);
|
||||
int from = 0;
|
||||
String key = null;
|
||||
int to;
|
||||
for (String part : patterns) {
|
||||
if (StrUtil.isWrap(part, "${", "}")) {
|
||||
// 变量
|
||||
key = StrUtil.sub(part, 2, part.length() - 1);
|
||||
} else {
|
||||
to = text.indexOf(part, from);
|
||||
if(to < 0){
|
||||
//普通字符串未匹配到,说明整个模式不能匹配,返回空
|
||||
return MapUtil.empty();
|
||||
}
|
||||
if (null != key && to > from) {
|
||||
// 变量对应部分有内容
|
||||
result.put(key, text.substring(from, to));
|
||||
}
|
||||
// 下一个起始点是普通字符串的末尾
|
||||
from = to + part.length();
|
||||
key = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (null != key && from < text.length()) {
|
||||
// 变量对应部分有内容
|
||||
result.put(key, text.substring(from));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析表达式
|
||||
* @param pattern 表达式,使用${XXXX}作为变量占位符
|
||||
* @return 表达式
|
||||
*/
|
||||
private static List<String> parse(String pattern) {
|
||||
List<String> patterns = new ArrayList<>();
|
||||
final int length = pattern.length();
|
||||
char c = 0;
|
||||
char pre;
|
||||
boolean inVar = false;
|
||||
StrBuilder part = StrUtil.strBuilder();
|
||||
for (int i = 0; i < length; i++) {
|
||||
pre = c;
|
||||
c = pattern.charAt(i);
|
||||
if (inVar) {
|
||||
part.append(c);
|
||||
if ('}' == c) {
|
||||
// 变量结束
|
||||
inVar = false;
|
||||
patterns.add(part.toString());
|
||||
part.clear();
|
||||
}
|
||||
} else if ('{' == c && '$' == pre) {
|
||||
// 变量开始
|
||||
inVar = true;
|
||||
final String preText = part.subString(0, part.length() - 1);
|
||||
if (StrUtil.isNotEmpty(preText)) {
|
||||
patterns.add(preText);
|
||||
}
|
||||
part.reset().append(pre).append(c);
|
||||
} else {
|
||||
// 普通字符
|
||||
part.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
if (part.length() > 0) {
|
||||
patterns.add(part.toString());
|
||||
}
|
||||
return patterns;
|
||||
}
|
||||
}
|
@ -200,7 +200,7 @@ public class ExecutorBuilder implements Builder<ThreadPoolExecutor> {
|
||||
/**
|
||||
* 创建ExecutorBuilder,开始构建
|
||||
*
|
||||
* @return {@link ExecutorBuilder}
|
||||
* @return this
|
||||
*/
|
||||
public static ExecutorBuilder create() {
|
||||
return new ExecutorBuilder();
|
||||
@ -227,7 +227,7 @@ public class ExecutorBuilder implements Builder<ThreadPoolExecutor> {
|
||||
/**
|
||||
* 构建ThreadPoolExecutor
|
||||
*
|
||||
* @param builder {@link ExecutorBuilder}
|
||||
* @param builder this
|
||||
* @return {@link ThreadPoolExecutor}
|
||||
*/
|
||||
private static ThreadPoolExecutor build(ExecutorBuilder builder) {
|
||||
|
@ -294,7 +294,7 @@ public class ClassUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找指定Public方法 如果找不到对应的方法或方法不为public的则返回<code>null</code>
|
||||
* 查找指定Public方法 如果找不到对应的方法或方法不为public的则返回{@code null}
|
||||
*
|
||||
* @param clazz 类
|
||||
* @param methodName 方法名
|
||||
@ -341,7 +341,7 @@ public class ClassUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找指定类中的所有方法(包括非public方法),也包括父类和Object类的方法 找不到方法会返回<code>null</code>
|
||||
* 查找指定类中的所有方法(包括非public方法),也包括父类和Object类的方法 找不到方法会返回{@code null}
|
||||
*
|
||||
* @param clazz 被查找的类
|
||||
* @param methodName 方法名
|
||||
@ -356,7 +356,7 @@ public class ClassUtil {
|
||||
// ----------------------------------------------------------------------------------------- Field
|
||||
|
||||
/**
|
||||
* 查找指定类中的所有字段(包括非public字段), 字段不存在则返回<code>null</code>
|
||||
* 查找指定类中的所有字段(包括非public字段), 字段不存在则返回{@code null}
|
||||
*
|
||||
* @param clazz 被查找字段的类
|
||||
* @param fieldName 字段名
|
||||
@ -558,7 +558,7 @@ public class ClassUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较判断types1和types2两组类,如果types1中所有的类都与types2对应位置的类相同,或者是其父类或接口,则返回<code>true</code>
|
||||
* 比较判断types1和types2两组类,如果types1中所有的类都与types2对应位置的类相同,或者是其父类或接口,则返回{@code true}
|
||||
*
|
||||
* @param types1 类组1
|
||||
* @param types2 类组2
|
||||
@ -626,7 +626,7 @@ public class ClassUtil {
|
||||
* 非单例模式,如果是非静态方法,每次创建一个新对象
|
||||
*
|
||||
* @param <T> 对象类型
|
||||
* @param classNameWithMethodName 类名和方法名表达式,类名与方法名用<code>.</code>或<code>#</code>连接 例如:com.xiaoleilu.hutool.StrUtil.isEmpty 或 com.xiaoleilu.hutool.StrUtil#isEmpty
|
||||
* @param classNameWithMethodName 类名和方法名表达式,类名与方法名用{@code .}或{@code #}连接 例如:com.xiaoleilu.hutool.StrUtil.isEmpty 或 com.xiaoleilu.hutool.StrUtil#isEmpty
|
||||
* @param args 参数,必须严格对应指定方法的参数类型和数量
|
||||
* @return 返回结果
|
||||
*/
|
||||
|
@ -26,7 +26,7 @@ public class HexUtil {
|
||||
|
||||
/**
|
||||
* 判断给定字符串是否为16进制数<br>
|
||||
* 如果是,需要使用对应数字类型对象的<code>decode</code>方法解码<br>
|
||||
* 如果是,需要使用对应数字类型对象的{@code decode}方法解码<br>
|
||||
* 例如:{@code Integer.decode}方法解码int类型的16进制数字
|
||||
*
|
||||
* @param value 值
|
||||
@ -74,7 +74,7 @@ public class HexUtil {
|
||||
* 将字节数组转换为十六进制字符数组
|
||||
*
|
||||
* @param data byte[]
|
||||
* @param toLowerCase <code>true</code> 传换成小写格式 , <code>false</code> 传换成大写格式
|
||||
* @param toLowerCase {@code true} 传换成小写格式 , {@code false} 传换成大写格式
|
||||
* @return 十六进制char[]
|
||||
*/
|
||||
public static char[] encodeHex(byte[] data, boolean toLowerCase) {
|
||||
@ -116,7 +116,7 @@ public class HexUtil {
|
||||
* 将字节数组转换为十六进制字符串
|
||||
*
|
||||
* @param data byte[]
|
||||
* @param toLowerCase <code>true</code> 传换成小写格式 , <code>false</code> 传换成大写格式
|
||||
* @param toLowerCase {@code true} 传换成小写格式 , {@code false} 传换成大写格式
|
||||
* @return 十六进制String
|
||||
*/
|
||||
public static String encodeHexStr(byte[] data, boolean toLowerCase) {
|
||||
|
@ -1428,10 +1428,62 @@ public class NumberUtil {
|
||||
|
||||
// ------------------------------------------------------------------------------------------- others
|
||||
|
||||
/**
|
||||
* 计算阶乘
|
||||
* <p>
|
||||
* n! = n * (n-1) * ... * 2 * 1
|
||||
* </p>
|
||||
*
|
||||
* @param n 阶乘起始
|
||||
* @return 结果
|
||||
* @since 5.6.0
|
||||
*/
|
||||
public static BigInteger factorial(BigInteger n) {
|
||||
if(n.equals(BigInteger.ZERO)){
|
||||
return BigInteger.ONE;
|
||||
}
|
||||
return factorial(n, BigInteger.ZERO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算范围阶乘
|
||||
* <p>
|
||||
* factorial(start, end) = start * (start - 1) * ... * (end - 1)
|
||||
* factorial(start, end) = start * (start - 1) * ... * (end + 1)
|
||||
* </p>
|
||||
*
|
||||
* @param start 阶乘起始(包含)
|
||||
* @param end 阶乘结束,必须小于起始(不包括)
|
||||
* @return 结果
|
||||
* @since 5.6.0
|
||||
*/
|
||||
public static BigInteger factorial(BigInteger start, BigInteger end) {
|
||||
Assert.notNull(start, "Factorial start must be not null!");
|
||||
Assert.notNull(end, "Factorial end must be not null!");
|
||||
if(start.compareTo(BigInteger.ZERO) < 0 || end.compareTo(BigInteger.ZERO) < 0){
|
||||
throw new IllegalArgumentException(StrUtil.format("Factorial start and end both must be > 0, but got start={}, end={}", start, end));
|
||||
}
|
||||
|
||||
if (start.equals(BigInteger.ZERO)){
|
||||
start = BigInteger.ONE;
|
||||
}
|
||||
|
||||
if(end.compareTo(BigInteger.ONE) < 0){
|
||||
end = BigInteger.ONE;
|
||||
}
|
||||
|
||||
BigInteger result = start;
|
||||
end = end.add(BigInteger.ONE);
|
||||
while(start.compareTo(end) > 0) {
|
||||
start = start.subtract(BigInteger.ONE);
|
||||
result = result.multiply(start);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算范围阶乘
|
||||
* <p>
|
||||
* factorial(start, end) = start * (start - 1) * ... * (end + 1)
|
||||
* </p>
|
||||
*
|
||||
* @param start 阶乘起始(包含)
|
||||
|
@ -3,8 +3,6 @@ package cn.hutool.core.util;
|
||||
import cn.hutool.core.lang.PatternPool;
|
||||
import cn.hutool.core.lang.Validator;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
||||
/**
|
||||
* 手机号工具类
|
||||
|
@ -3,8 +3,6 @@ package cn.hutool.core.codec;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import cn.hutool.core.codec.BCD;
|
||||
|
||||
public class BCDTest {
|
||||
|
||||
@Test
|
||||
|
@ -11,6 +11,7 @@ import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
@ -694,4 +695,14 @@ public class CollUtilTest {
|
||||
|
||||
Assert.assertEquals(0, CollUtil.page(3, 5, objects).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subtractToListTest(){
|
||||
List<Long> list1 = Arrays.asList(1L, 2L, 3L);
|
||||
List<Long> list2 = Arrays.asList(2L, 3L);
|
||||
|
||||
List<Long> result = CollUtil.subtractToList(list1, list2);
|
||||
Assert.assertEquals(1, result.size());
|
||||
Assert.assertEquals(1L, result.get(0), 1);
|
||||
}
|
||||
}
|
||||
|
@ -101,4 +101,15 @@ public class ListUtilTest {
|
||||
int[] d1 = ListUtil.page(0,8,a).stream().mapToInt(Integer::valueOf).toArray();
|
||||
Assert.assertArrayEquals(new int[]{1,2,3,4,5},d1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subTest(){
|
||||
final List<Integer> of = ListUtil.of(1, 2, 3, 4);
|
||||
final List<Integer> sub = ListUtil.sub(of, 2, 4);
|
||||
sub.remove(0);
|
||||
|
||||
// 对子列表操作不影响原列表
|
||||
Assert.assertEquals(4, of.size());
|
||||
Assert.assertEquals(1, sub.size());
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import java.util.concurrent.TimeUnit;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
|
||||
/**
|
||||
|
@ -3,9 +3,6 @@ package cn.hutool.core.convert;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import cn.hutool.core.convert.Converter;
|
||||
import cn.hutool.core.convert.ConverterRegistry;
|
||||
|
||||
/**
|
||||
* ConverterRegistry 单元测试
|
||||
* @author Looly
|
||||
|
@ -12,14 +12,23 @@ public class NumberWordFormatTest {
|
||||
|
||||
String format2 = NumberWordFormatter.format("2100.00");
|
||||
Assert.assertEquals("TWO THOUSAND ONE HUNDRED AND CENTS ONLY", format2);
|
||||
}
|
||||
|
||||
String format3 = NumberWordFormatter.formatValue(4384324, false);
|
||||
Assert.assertEquals("4.38m", format3);
|
||||
@Test
|
||||
public void formatSimpleTest() {
|
||||
String format1 = NumberWordFormatter.formatSimple(1200, false);
|
||||
Assert.assertEquals("1.2k", format1);
|
||||
|
||||
String format4 = NumberWordFormatter.formatValue(4384324);
|
||||
String format2 = NumberWordFormatter.formatSimple(4384324, false);
|
||||
Assert.assertEquals("4.38m", format2);
|
||||
|
||||
String format3 = NumberWordFormatter.formatSimple(4384324, true);
|
||||
Assert.assertEquals("438.43w", format3);
|
||||
|
||||
String format4 = NumberWordFormatter.formatSimple(4384324);
|
||||
Assert.assertEquals("438.43w", format4);
|
||||
|
||||
String format5 = NumberWordFormatter.formatValue(438);
|
||||
String format5 = NumberWordFormatter.formatSimple(438);
|
||||
Assert.assertEquals("438", format5);
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,9 @@ import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 文件类型判断单元测试
|
||||
@ -41,4 +44,16 @@ public class FileTypeUtilTest {
|
||||
String type = FileTypeUtil.getType(file);
|
||||
Console.log(type);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void ofdTest() {
|
||||
File file = FileUtil.file("e:/test.ofd");
|
||||
String hex = IoUtil.readHex28Upper(FileUtil.getInputStream(file));
|
||||
Console.log(hex);
|
||||
String type = FileTypeUtil.getType(file);
|
||||
Console.log(type);
|
||||
Assert.assertEquals("ofd", type);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -51,7 +51,13 @@ public class DataSizeUtilTest {
|
||||
|
||||
@Test
|
||||
public void formatTest(){
|
||||
final String format = DataSizeUtil.format(Long.MAX_VALUE);
|
||||
Assert.assertEquals("8,192 EB", format);
|
||||
String format = DataSizeUtil.format(Long.MAX_VALUE);
|
||||
Assert.assertEquals("8 EB", format);
|
||||
|
||||
format = DataSizeUtil.format(1024L * 1024 * 1024 * 1024 * 1024);
|
||||
Assert.assertEquals("1 PB", format);
|
||||
|
||||
format = DataSizeUtil.format(1024L * 1024 * 1024 * 1024);
|
||||
Assert.assertEquals("1 TB", format);
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
package cn.hutool.core.lang;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class ClassScanerTest {
|
||||
|
||||
@Test
|
||||
|
@ -0,0 +1,35 @@
|
||||
package cn.hutool.core.text;
|
||||
|
||||
import cn.hutool.core.lang.Console;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class StrMatcherTest {
|
||||
|
||||
@Test
|
||||
public void matcherTest(){
|
||||
final StrMatcher strMatcher = new StrMatcher("${name}-${age}-${gender}-${country}-${province}-${city}-${status}");
|
||||
final Map<String, String> match = strMatcher.match("小明-19-男-中国-河南-郑州-已婚");
|
||||
Console.log(match);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matcherTest2(){
|
||||
// 当有无匹配项的时候,按照全不匹配对待
|
||||
final StrMatcher strMatcher = new StrMatcher("${name}-${age}-${gender}-${country}-${province}-${city}-${status}");
|
||||
final Map<String, String> match = strMatcher.match("小明-19-男-中国-河南-郑州");
|
||||
Assert.assertEquals(0, match.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matcherTest3(){
|
||||
// 当有无匹配项的时候,按照全不匹配对待
|
||||
final StrMatcher strMatcher = new StrMatcher("${name}经过${year}年");
|
||||
final Map<String, String> match = strMatcher.match("小明经过20年,成长为一个大人。");
|
||||
Console.log(match);
|
||||
Assert.assertEquals("小明", match.get("name"));
|
||||
Assert.assertEquals("20", match.get("year"));
|
||||
}
|
||||
}
|
@ -75,6 +75,7 @@ public class IdUtilTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void snowflakeBenchTest() {
|
||||
final Set<Long> set = new ConcurrentHashSet<>();
|
||||
final Snowflake snowflake = IdUtil.createSnowflake(1, 1);
|
||||
@ -105,6 +106,7 @@ public class IdUtilTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void snowflakeBenchTest2() {
|
||||
final Set<Long> set = new ConcurrentHashSet<>();
|
||||
|
||||
|
@ -5,6 +5,7 @@ import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.Set;
|
||||
|
||||
@ -308,6 +309,24 @@ public class NumberUtilTest {
|
||||
Assert.assertEquals(2432902008176640000L, NumberUtil.factorial(20, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void factorialTest2(){
|
||||
long factorial = NumberUtil.factorial(new BigInteger("0")).longValue();
|
||||
Assert.assertEquals(1, factorial);
|
||||
|
||||
Assert.assertEquals(1L, NumberUtil.factorial(new BigInteger("1")).longValue());
|
||||
Assert.assertEquals(1307674368000L, NumberUtil.factorial(new BigInteger("15")).longValue());
|
||||
Assert.assertEquals(2432902008176640000L, NumberUtil.factorial(20));
|
||||
|
||||
factorial = NumberUtil.factorial(new BigInteger("5"), new BigInteger("0")).longValue();
|
||||
Assert.assertEquals(120, factorial);
|
||||
factorial = NumberUtil.factorial(new BigInteger("5"), BigInteger.ONE).longValue();
|
||||
Assert.assertEquals(120, factorial);
|
||||
|
||||
Assert.assertEquals(5, NumberUtil.factorial(new BigInteger("5"), new BigInteger("4")).longValue());
|
||||
Assert.assertEquals(2432902008176640000L, NumberUtil.factorial(new BigInteger("20"), BigInteger.ZERO).longValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mulTest(){
|
||||
final BigDecimal mul = NumberUtil.mul(new BigDecimal("10"), null);
|
||||
|
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.9-SNAPSHOT</version>
|
||||
<version>5.6.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-cron</artifactId>
|
||||
|
@ -41,7 +41,7 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||
* 其中:
|
||||
*
|
||||
* <pre>
|
||||
* <strong>TaskLauncher</strong>:定时器每分钟调用一次(如果{@link Scheduler#isMatchSecond()}为<code>true</code>每秒调用一次),
|
||||
* <strong>TaskLauncher</strong>:定时器每分钟调用一次(如果{@link Scheduler#isMatchSecond()}为{@code true}每秒调用一次),
|
||||
* 负责检查<strong>TaskTable</strong>是否有匹配到此时间运行的Task
|
||||
* </pre>
|
||||
*
|
||||
@ -102,7 +102,7 @@ public class Scheduler implements Serializable {
|
||||
* 设置是否为守护线程<br>
|
||||
* 如果为true,则在调用{@link #stop()}方法后执行的定时任务立即结束,否则等待执行完毕才结束。默认非守护线程
|
||||
*
|
||||
* @param on <code>true</code>为守护线程,否则非守护线程
|
||||
* @param on {@code true}为守护线程,否则非守护线程
|
||||
* @return this
|
||||
* @throws CronException 定时任务已经启动抛出此异常
|
||||
*/
|
||||
@ -131,7 +131,7 @@ public class Scheduler implements Serializable {
|
||||
/**
|
||||
* 是否支持秒匹配
|
||||
*
|
||||
* @return <code>true</code>使用,<code>false</code>不使用
|
||||
* @return {@code true}使用,{@code false}不使用
|
||||
*/
|
||||
public boolean isMatchSecond() {
|
||||
return this.config.isMatchSecond();
|
||||
@ -140,7 +140,7 @@ public class Scheduler implements Serializable {
|
||||
/**
|
||||
* 设置是否支持秒匹配,默认不使用
|
||||
*
|
||||
* @param isMatchSecond <code>true</code>支持,<code>false</code>不支持
|
||||
* @param isMatchSecond {@code true}支持,{@code false}不支持
|
||||
* @return this
|
||||
*/
|
||||
public Scheduler setMatchSecond(boolean isMatchSecond) {
|
||||
@ -380,7 +380,7 @@ public class Scheduler implements Serializable {
|
||||
throw new CronException("Schedule is started!");
|
||||
}
|
||||
|
||||
// 无界线程池,确保每一个需要执行的线程都可以及时运行,同时复用已有现成避免线程重复创建
|
||||
// 无界线程池,确保每一个需要执行的线程都可以及时运行,同时复用已有线程避免线程重复创建
|
||||
this.threadExecutor = ExecutorBuilder.create().useSynchronousQueue().setThreadFactory(//
|
||||
ThreadFactoryBuilder.create().setNamePrefix("hutool-cron-").setDaemon(this.daemon).build()//
|
||||
).build();
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.9-SNAPSHOT</version>
|
||||
<version>5.6.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-crypto</artifactId>
|
||||
|
@ -1,5 +1,8 @@
|
||||
package cn.hutool.crypto;
|
||||
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
|
||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
|
||||
import org.bouncycastle.asn1.x9.X9ECParameters;
|
||||
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
|
||||
import org.bouncycastle.crypto.params.ECDomainParameters;
|
||||
@ -13,6 +16,7 @@ import org.bouncycastle.jce.spec.ECNamedCurveSpec;
|
||||
import org.bouncycastle.jce.spec.ECParameterSpec;
|
||||
import org.bouncycastle.math.ec.ECCurve;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.security.Key;
|
||||
@ -41,15 +45,28 @@ public class BCUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 编码压缩EC公钥(基于BouncyCastle)<br>
|
||||
* 编码压缩EC公钥(基于BouncyCastle),即Q值<br>
|
||||
* 见:https://www.cnblogs.com/xinzhao/p/8963724.html
|
||||
*
|
||||
* @param publicKey {@link PublicKey},必须为org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey
|
||||
* @return 压缩得到的X
|
||||
* @return 压缩得到的Q
|
||||
* @since 4.4.4
|
||||
*/
|
||||
public static byte[] encodeECPublicKey(PublicKey publicKey) {
|
||||
return ((BCECPublicKey) publicKey).getQ().getEncoded(true);
|
||||
return encodeECPublicKey(publicKey, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 编码压缩EC公钥(基于BouncyCastle),即Q值<br>
|
||||
* 见:https://www.cnblogs.com/xinzhao/p/8963724.html
|
||||
*
|
||||
* @param publicKey {@link PublicKey},必须为org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey
|
||||
* @param isCompressed 是否压缩
|
||||
* @return 得到的Q
|
||||
* @since 5.5.9
|
||||
*/
|
||||
public static byte[] encodeECPublicKey(PublicKey publicKey, boolean isCompressed) {
|
||||
return ((BCECPublicKey) publicKey).getQ().getEncoded(isCompressed);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -300,4 +317,37 @@ public class BCUtil {
|
||||
public static PublicKey readPemPublicKey(InputStream pemStream) {
|
||||
return PemUtil.readPemPublicKey(pemStream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Java中的PKCS#8格式私钥转换为OpenSSL支持的PKCS#1格式
|
||||
*
|
||||
* @param privateKey PKCS#8格式私钥
|
||||
* @return PKCS#1格式私钥
|
||||
* @since 5.5.9
|
||||
*/
|
||||
public static byte[] toPkcs1(PrivateKey privateKey){
|
||||
final PrivateKeyInfo pkInfo = PrivateKeyInfo.getInstance(privateKey.getEncoded());
|
||||
try {
|
||||
return pkInfo.parsePrivateKey().toASN1Primitive().getEncoded();
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Java中的X.509格式公钥转换为OpenSSL支持的PKCS#1格式
|
||||
*
|
||||
* @param publicKey X.509格式公钥
|
||||
* @return PKCS#1格式公钥
|
||||
* @since 5.5.9
|
||||
*/
|
||||
public static byte[] toPkcs1(PublicKey publicKey){
|
||||
final SubjectPublicKeyInfo spkInfo = SubjectPublicKeyInfo
|
||||
.getInstance(publicKey.getEncoded());
|
||||
try {
|
||||
return spkInfo.parsePublicKey().getEncoded();
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,30 @@
|
||||
package cn.hutool.crypto;
|
||||
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import org.bouncycastle.asn1.ASN1Encoding;
|
||||
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
|
||||
import org.bouncycastle.asn1.sec.ECPrivateKey;
|
||||
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
|
||||
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
|
||||
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.jcajce.spec.OpenSSHPrivateKeySpec;
|
||||
import org.bouncycastle.jcajce.spec.OpenSSHPublicKeySpec;
|
||||
import org.bouncycastle.math.ec.ECCurve;
|
||||
import org.bouncycastle.math.ec.ECPoint;
|
||||
import org.bouncycastle.math.ec.FixedPointCombMultiplier;
|
||||
import org.bouncycastle.util.BigIntegers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.Key;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.KeySpec;
|
||||
|
||||
/**
|
||||
* EC密钥参数相关工具类封装
|
||||
@ -38,7 +50,21 @@ public class ECKeyUtil {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据私钥参数获取公钥参数
|
||||
*
|
||||
* @param privateKeyParameters 私钥参数
|
||||
* @return 公钥参数
|
||||
* @since 5.5.9
|
||||
*/
|
||||
public static ECPublicKeyParameters getPublicParams(ECPrivateKeyParameters privateKeyParameters) {
|
||||
final ECDomainParameters domainParameters = privateKeyParameters.getParameters();
|
||||
final ECPoint q = new FixedPointCombMultiplier().multiply(domainParameters.getG(), privateKeyParameters.getD());
|
||||
return new ECPublicKeyParameters(q, domainParameters);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------- Public Key
|
||||
|
||||
/**
|
||||
* 转换为 ECPublicKeyParameters
|
||||
*
|
||||
@ -87,7 +113,7 @@ public class ECKeyUtil {
|
||||
* @param x 公钥X
|
||||
* @param y 公钥Y
|
||||
* @param domainParameters ECDomainParameters
|
||||
* @return ECPublicKeyParameters
|
||||
* @return ECPublicKeyParameters,x或y为{@code null}则返回{@code null}
|
||||
*/
|
||||
public static ECPublicKeyParameters toPublicParams(String x, String y, ECDomainParameters domainParameters) {
|
||||
return toPublicParams(SecureUtil.decode(x), SecureUtil.decode(y), domainParameters);
|
||||
@ -102,6 +128,9 @@ public class ECKeyUtil {
|
||||
* @return ECPublicKeyParameters
|
||||
*/
|
||||
public static ECPublicKeyParameters toPublicParams(byte[] xBytes, byte[] yBytes, ECDomainParameters domainParameters) {
|
||||
if (null == xBytes || null == yBytes) {
|
||||
return null;
|
||||
}
|
||||
return toPublicParams(BigIntegers.fromUnsignedByteArray(xBytes), BigIntegers.fromUnsignedByteArray(yBytes), domainParameters);
|
||||
}
|
||||
|
||||
@ -177,6 +206,7 @@ public class ECKeyUtil {
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------- Private Key
|
||||
|
||||
/**
|
||||
* 转换为 ECPrivateKeyParameters
|
||||
*
|
||||
@ -259,4 +289,108 @@ public class ECKeyUtil {
|
||||
throw new CryptoException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将SM2算法的{@link ECPrivateKey} 转换为 {@link PrivateKey}
|
||||
*
|
||||
* @param privateKey {@link ECPrivateKey}
|
||||
* @return {@link PrivateKey}
|
||||
*/
|
||||
public static PrivateKey toSm2PrivateKey(ECPrivateKey privateKey) {
|
||||
try {
|
||||
final PrivateKeyInfo info = new PrivateKeyInfo(
|
||||
new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, SmUtil.ID_SM2_PUBLIC_KEY_PARAM), privateKey);
|
||||
return KeyUtil.generatePrivateKey("SM2", info.getEncoded(ASN1Encoding.DER));
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建{@link OpenSSHPrivateKeySpec}
|
||||
*
|
||||
* @param key 私钥,需为PKCS#1格式
|
||||
* @return {@link OpenSSHPrivateKeySpec}
|
||||
* @since 5.5.9
|
||||
*/
|
||||
public static KeySpec createOpenSSHPrivateKeySpec(byte[] key) {
|
||||
return new OpenSSHPrivateKeySpec(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建{@link OpenSSHPublicKeySpec}
|
||||
*
|
||||
* @param key 公钥,需为PKCS#1格式
|
||||
* @return {@link OpenSSHPublicKeySpec}
|
||||
* @since 5.5.9
|
||||
*/
|
||||
public static KeySpec createOpenSSHPublicKeySpec(byte[] key) {
|
||||
return new OpenSSHPublicKeySpec(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试解析转换各种类型私钥为{@link ECPrivateKeyParameters},支持包括:
|
||||
*
|
||||
* <ul>
|
||||
* <li>D值</li>
|
||||
* <li>PKCS#8</li>
|
||||
* <li>PKCS#1</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param privateKeyBytes 私钥
|
||||
* @return {@link ECPrivateKeyParameters}
|
||||
* @since 5.5.9
|
||||
*/
|
||||
public static ECPrivateKeyParameters decodePrivateKeyParams(byte[] privateKeyBytes) {
|
||||
try {
|
||||
// 尝试D值
|
||||
return toSm2PrivateParams(privateKeyBytes);
|
||||
} catch (Exception ignore) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
PrivateKey privateKey;
|
||||
//尝试PKCS#8
|
||||
try {
|
||||
privateKey = KeyUtil.generatePrivateKey("sm2", privateKeyBytes);
|
||||
} catch (Exception ignore) {
|
||||
// 尝试PKCS#1
|
||||
privateKey = KeyUtil.generatePrivateKey("sm2", createOpenSSHPrivateKeySpec(privateKeyBytes));
|
||||
}
|
||||
|
||||
return toPrivateParams(privateKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试解析转换各种类型公钥为{@link ECPublicKeyParameters},支持包括:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Q值</li>
|
||||
* <li>X.509</li>
|
||||
* <li>PKCS#1</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param publicKeyBytes 公钥
|
||||
* @return {@link ECPublicKeyParameters}
|
||||
* @since 5.5.9
|
||||
*/
|
||||
public static ECPublicKeyParameters decodePublicKeyParams(byte[] publicKeyBytes) {
|
||||
try {
|
||||
// 尝试Q值
|
||||
return toSm2PublicParams(publicKeyBytes);
|
||||
} catch (Exception ignore) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
PublicKey publicKey;
|
||||
//尝试X.509
|
||||
try {
|
||||
publicKey = KeyUtil.generatePublicKey("sm2", publicKeyBytes);
|
||||
} catch (Exception ignore) {
|
||||
// 尝试PKCS#1
|
||||
publicKey = KeyUtil.generatePublicKey("sm2", createOpenSSHPublicKeySpec(publicKeyBytes));
|
||||
}
|
||||
|
||||
return toPublicParams(publicKey);
|
||||
}
|
||||
}
|
||||
|
@ -256,8 +256,8 @@ public class KeyUtil {
|
||||
* 采用PKCS#8规范,此规范定义了私钥信息语法和加密私钥语法<br>
|
||||
* 算法见:https://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#KeyFactory
|
||||
*
|
||||
* @param algorithm 算法
|
||||
* @param key 密钥,必须为DER编码存储
|
||||
* @param algorithm 算法,如RSA、EC、SM2等
|
||||
* @param key 密钥,PKCS#8格式
|
||||
* @return 私钥 {@link PrivateKey}
|
||||
*/
|
||||
public static PrivateKey generatePrivateKey(String algorithm, byte[] key) {
|
||||
@ -271,7 +271,7 @@ public class KeyUtil {
|
||||
* 生成私钥,仅用于非对称加密<br>
|
||||
* 算法见:https://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#KeyFactory
|
||||
*
|
||||
* @param algorithm 算法
|
||||
* @param algorithm 算法,如RSA、EC、SM2等
|
||||
* @param keySpec {@link KeySpec}
|
||||
* @return 私钥 {@link PrivateKey}
|
||||
* @since 3.1.1
|
||||
|
@ -12,6 +12,8 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Reader;
|
||||
import java.io.StringWriter;
|
||||
import java.io.Writer;
|
||||
import java.security.Key;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
@ -66,7 +68,8 @@ public class PemUtil {
|
||||
//private
|
||||
if (type.endsWith("EC PRIVATE KEY")) {
|
||||
return KeyUtil.generatePrivateKey("EC", object.getContent());
|
||||
}if (type.endsWith("PRIVATE KEY")) {
|
||||
}
|
||||
if (type.endsWith("PRIVATE KEY")) {
|
||||
return KeyUtil.generateRSAPrivateKey(object.getContent());
|
||||
}
|
||||
|
||||
@ -129,11 +132,38 @@ public class PemUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取OpenSSL生成的ANS1格式的Pem私钥文件,必须为PKCS#1格式
|
||||
*
|
||||
* @param keyStream 私钥pem流
|
||||
* @return {@link PrivateKey}
|
||||
*/
|
||||
public static PrivateKey readSm2PemPrivateKey(InputStream keyStream) {
|
||||
try{
|
||||
return KeyUtil.generatePrivateKey("sm2", ECKeyUtil.createOpenSSHPrivateKeySpec(readPem(keyStream)));
|
||||
} finally {
|
||||
IoUtil.close(keyStream);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将私钥或公钥转换为PEM格式的字符串
|
||||
* @param type 密钥类型(私钥、公钥、证书)
|
||||
* @param content 密钥内容
|
||||
* @return PEM内容
|
||||
* @since 5.5.9
|
||||
*/
|
||||
public static String toPem(String type, byte[] content) {
|
||||
final StringWriter stringWriter = new StringWriter();
|
||||
writePemObject(type, content, stringWriter);
|
||||
return stringWriter.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 写出pem密钥(私钥、公钥、证书)
|
||||
*
|
||||
* @param type 密钥类型(私钥、公钥、证书)
|
||||
* @param content 密钥内容
|
||||
* @param content 密钥内容,需为PKCS#1格式
|
||||
* @param keyStream pem流
|
||||
* @since 5.1.6
|
||||
*/
|
||||
@ -141,6 +171,18 @@ public class PemUtil {
|
||||
writePemObject(new PemObject(type, content), keyStream);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写出pem密钥(私钥、公钥、证书)
|
||||
*
|
||||
* @param type 密钥类型(私钥、公钥、证书)
|
||||
* @param content 密钥内容,需为PKCS#1格式
|
||||
* @param writer pemWriter
|
||||
* @since 5.5.9
|
||||
*/
|
||||
public static void writePemObject(String type, byte[] content, Writer writer) {
|
||||
writePemObject(new PemObject(type, content), writer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写出pem密钥(私钥、公钥、证书)
|
||||
*
|
||||
@ -149,14 +191,24 @@ public class PemUtil {
|
||||
* @since 5.1.6
|
||||
*/
|
||||
public static void writePemObject(PemObjectGenerator pemObject, OutputStream keyStream) {
|
||||
PemWriter writer = null;
|
||||
writePemObject(pemObject, IoUtil.getUtf8Writer(keyStream));
|
||||
}
|
||||
|
||||
/**
|
||||
* 写出pem密钥(私钥、公钥、证书)
|
||||
*
|
||||
* @param pemObject pem对象,包括密钥和密钥类型等信息
|
||||
* @param writer pemWriter
|
||||
* @since 5.5.9
|
||||
*/
|
||||
public static void writePemObject(PemObjectGenerator pemObject, Writer writer) {
|
||||
final PemWriter pemWriter = new PemWriter(writer);
|
||||
try {
|
||||
writer = new PemWriter(IoUtil.getUtf8Writer(keyStream));
|
||||
writer.writeObject(pemObject);
|
||||
pemWriter.writeObject(pemObject);
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
} finally {
|
||||
IoUtil.close(writer);
|
||||
IoUtil.close(pemWriter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import cn.hutool.crypto.digest.MD5;
|
||||
import cn.hutool.crypto.symmetric.AES;
|
||||
import cn.hutool.crypto.symmetric.DES;
|
||||
import cn.hutool.crypto.symmetric.DESede;
|
||||
import cn.hutool.crypto.symmetric.PBKDF2;
|
||||
import cn.hutool.crypto.symmetric.RC4;
|
||||
import cn.hutool.crypto.symmetric.SymmetricCrypto;
|
||||
|
||||
@ -1048,4 +1049,16 @@ public final class SecureUtil {
|
||||
public static void disableBouncyCastle() {
|
||||
GlobalBouncyCastleProvider.setUseBouncyCastle(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* PBKDF2加密密码
|
||||
*
|
||||
* @param password 密码
|
||||
* @param salt 盐
|
||||
* @return 盐,一般为16位
|
||||
* @since 5.6.0
|
||||
*/
|
||||
public static String pbkdf2(char[] password, byte[] salt){
|
||||
return new PBKDF2().encryptHex(password, salt);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package cn.hutool.crypto;
|
||||
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.crypto.asymmetric.SM2;
|
||||
import cn.hutool.crypto.digest.HMac;
|
||||
import cn.hutool.crypto.digest.HmacAlgorithm;
|
||||
@ -9,13 +10,13 @@ import cn.hutool.crypto.digest.mac.BCHMacEngine;
|
||||
import cn.hutool.crypto.digest.mac.MacEngine;
|
||||
import cn.hutool.crypto.symmetric.SM4;
|
||||
import cn.hutool.crypto.symmetric.SymmetricCrypto;
|
||||
import org.bouncycastle.asn1.ASN1EncodableVector;
|
||||
import org.bouncycastle.asn1.ASN1Integer;
|
||||
import org.bouncycastle.asn1.ASN1Sequence;
|
||||
import org.bouncycastle.asn1.DERSequence;
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||
import org.bouncycastle.asn1.gm.GMNamedCurves;
|
||||
import org.bouncycastle.crypto.digests.SM3Digest;
|
||||
import org.bouncycastle.crypto.params.ECDomainParameters;
|
||||
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
|
||||
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
|
||||
import org.bouncycastle.crypto.signers.StandardDSAEncoding;
|
||||
import org.bouncycastle.util.Arrays;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
|
||||
@ -23,16 +24,26 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
|
||||
/**
|
||||
* SM国密算法工具类<br>
|
||||
* 此工具类依赖org.bouncycastle:bcpkix-jdk15on
|
||||
* 此工具类依赖org.bouncycastle:bcprov-jdk15to18
|
||||
*
|
||||
* <p>封装包括:</p>
|
||||
* <ul>
|
||||
* <li>SM2 椭圆曲线非对称加密和签名</li>
|
||||
* <li>SM3 杂凑算法</li>
|
||||
* <li>SM4 对称加密</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author looly
|
||||
* @since 4.3.2
|
||||
*/
|
||||
public class SmUtil {
|
||||
|
||||
private final static int RS_LEN = 32;
|
||||
/**
|
||||
* SM2默认曲线
|
||||
*/
|
||||
@ -40,13 +51,11 @@ public class SmUtil {
|
||||
/**
|
||||
* SM2推荐曲线参数(来自https://github.com/ZZMarquis/gmhelper)
|
||||
*/
|
||||
public static final ECDomainParameters SM2_DOMAIN_PARAMS;
|
||||
|
||||
private final static int RS_LEN = 32;
|
||||
|
||||
static {
|
||||
SM2_DOMAIN_PARAMS = BCUtil.toDomainParams(GMNamedCurves.getByName(SM2_CURVE_NAME));
|
||||
}
|
||||
public static final ECDomainParameters SM2_DOMAIN_PARAMS = BCUtil.toDomainParams(GMNamedCurves.getByName(SM2_CURVE_NAME));
|
||||
/**
|
||||
* SM2国密算法公钥参数的Oid标识
|
||||
*/
|
||||
public static final ASN1ObjectIdentifier ID_SM2_PUBLIC_KEY_PARAM = new ASN1ObjectIdentifier("1.2.156.10197.1.301");
|
||||
|
||||
/**
|
||||
* 创建SM2算法对象<br>
|
||||
@ -76,14 +85,42 @@ public class SmUtil {
|
||||
* 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
|
||||
* 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密
|
||||
*
|
||||
* @param privateKey 私钥
|
||||
* @param publicKey 公钥
|
||||
* @param privateKey 私钥,必须使用PKCS#8规范
|
||||
* @param publicKey 公钥,必须使用X509规范
|
||||
* @return {@link SM2}
|
||||
*/
|
||||
public static SM2 sm2(byte[] privateKey, byte[] publicKey) {
|
||||
return new SM2(privateKey, publicKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建SM2算法对象<br>
|
||||
* 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
|
||||
* 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密
|
||||
*
|
||||
* @param privateKey 私钥
|
||||
* @param publicKey 公钥
|
||||
* @return {@link SM2}
|
||||
* @since 5.5.9
|
||||
*/
|
||||
public static SM2 sm2(PrivateKey privateKey, PublicKey publicKey) {
|
||||
return new SM2(privateKey, publicKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建SM2算法对象<br>
|
||||
* 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
|
||||
* 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密
|
||||
*
|
||||
* @param privateKeyParams 私钥参数
|
||||
* @param publicKeyParams 公钥参数
|
||||
* @return {@link SM2}
|
||||
* @since 5.5.9
|
||||
*/
|
||||
public static SM2 sm2(ECPrivateKeyParameters privateKeyParams, ECPublicKeyParameters publicKeyParams) {
|
||||
return new SM2(privateKeyParams, publicKeyParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* SM3加密<br>
|
||||
* 例:<br>
|
||||
@ -194,27 +231,28 @@ public class SmUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* BC的SM3withSM2签名得到的结果的rs是asn1格式的,这个方法转化成直接拼接r||s<br>
|
||||
* 来自:https://blog.csdn.net/pridas/article/details/86118774
|
||||
* BC的SM3withSM2签名得到的结果的rs是asn1格式的,这个方法转化成直接拼接r||s
|
||||
*
|
||||
* @param rsDer rs in asn1 format
|
||||
* @return sign result in plain byte array
|
||||
* @since 4.5.0
|
||||
*/
|
||||
public static byte[] rsAsn1ToPlain(byte[] rsDer) {
|
||||
ASN1Sequence seq = ASN1Sequence.getInstance(rsDer);
|
||||
byte[] r = bigIntToFixedLengthBytes(ASN1Integer.getInstance(seq.getObjectAt(0)).getValue());
|
||||
byte[] s = bigIntToFixedLengthBytes(ASN1Integer.getInstance(seq.getObjectAt(1)).getValue());
|
||||
byte[] result = new byte[RS_LEN * 2];
|
||||
System.arraycopy(r, 0, result, 0, r.length);
|
||||
System.arraycopy(s, 0, result, RS_LEN, s.length);
|
||||
final BigInteger[] decode;
|
||||
try {
|
||||
decode = StandardDSAEncoding.INSTANCE.decode(SM2_DOMAIN_PARAMS.getN(), rsDer);
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
|
||||
return result;
|
||||
final byte[] r = bigIntToFixedLengthBytes(decode[0]);
|
||||
final byte[] s = bigIntToFixedLengthBytes(decode[1]);
|
||||
|
||||
return ArrayUtil.addAll(r, s);
|
||||
}
|
||||
|
||||
/**
|
||||
* BC的SM3withSM2验签需要的rs是asn1格式的,这个方法将直接拼接r||s的字节数组转化成asn1格式<br>
|
||||
* 来自:https://blog.csdn.net/pridas/article/details/86118774
|
||||
* BC的SM3withSM2验签需要的rs是asn1格式的,这个方法将直接拼接r||s的字节数组转化成asn1格式
|
||||
*
|
||||
* @param sign in plain byte array
|
||||
* @return rs result in asn1 format
|
||||
@ -226,11 +264,8 @@ public class SmUtil {
|
||||
}
|
||||
BigInteger r = new BigInteger(1, Arrays.copyOfRange(sign, 0, RS_LEN));
|
||||
BigInteger s = new BigInteger(1, Arrays.copyOfRange(sign, RS_LEN, RS_LEN * 2));
|
||||
ASN1EncodableVector v = new ASN1EncodableVector();
|
||||
v.add(new ASN1Integer(r));
|
||||
v.add(new ASN1Integer(s));
|
||||
try {
|
||||
return new DERSequence(v).getEncoded("DER");
|
||||
return StandardDSAEncoding.INSTANCE.encode(SM2_DOMAIN_PARAMS.getN(), r, s);
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.HexUtil;
|
||||
import cn.hutool.crypto.BCUtil;
|
||||
import cn.hutool.crypto.CryptoException;
|
||||
import cn.hutool.crypto.KeyUtil;
|
||||
import cn.hutool.crypto.ECKeyUtil;
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import org.bouncycastle.crypto.CipherParameters;
|
||||
import org.bouncycastle.crypto.Digest;
|
||||
@ -62,8 +62,8 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
|
||||
* 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
|
||||
* 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密
|
||||
*
|
||||
* @param privateKeyStr 私钥Hex或Base64表示
|
||||
* @param publicKeyStr 公钥Hex或Base64表示
|
||||
* @param privateKeyStr 私钥Hex或Base64表示,必须使用PKCS#8规范
|
||||
* @param publicKeyStr 公钥Hex或Base64表示,必须使用X509规范
|
||||
*/
|
||||
public SM2(String privateKeyStr, String publicKeyStr) {
|
||||
this(SecureUtil.decode(privateKeyStr), SecureUtil.decode(publicKeyStr));
|
||||
@ -74,13 +74,13 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
|
||||
* 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
|
||||
* 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密
|
||||
*
|
||||
* @param privateKey 私钥
|
||||
* @param publicKey 公钥
|
||||
* @param privateKey 私钥,可以使用PKCS#8、D值或PKCS#1规范
|
||||
* @param publicKey 公钥,可以使用X509、Q值或PKCS#1规范
|
||||
*/
|
||||
public SM2(byte[] privateKey, byte[] publicKey) {
|
||||
this(//
|
||||
KeyUtil.generatePrivateKey(ALGORITHM_SM2, privateKey), //
|
||||
KeyUtil.generatePublicKey(ALGORITHM_SM2, publicKey)//
|
||||
this(
|
||||
ECKeyUtil.decodePrivateKeyParams(privateKey),
|
||||
ECKeyUtil.decodePublicKeyParams(publicKey)
|
||||
);
|
||||
}
|
||||
|
||||
@ -135,8 +135,8 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
|
||||
* 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
|
||||
* 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密
|
||||
*
|
||||
* @param privateKeyParams 私钥
|
||||
* @param publicKeyParams 公钥
|
||||
* @param privateKeyParams 私钥,可以为null
|
||||
* @param publicKeyParams 公钥,可以为null
|
||||
*/
|
||||
public SM2(ECPrivateKeyParameters privateKeyParams, ECPublicKeyParameters publicKeyParams) {
|
||||
super(ALGORITHM_SM2, null, null);
|
||||
@ -274,7 +274,8 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 用私钥对信息生成数字签名
|
||||
* 用私钥对信息生成数字签名,签名格式为ASN1<br>
|
||||
* * 在硬件签名中,返回结果为R+S,可以通过调用{@link cn.hutool.crypto.SmUtil#rsAsn1ToPlain(byte[])}方法转换之。
|
||||
*
|
||||
* @param data 加密数据
|
||||
* @return 签名
|
||||
@ -295,7 +296,8 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 用私钥对信息生成数字签名
|
||||
* 用私钥对信息生成数字签名,签名格式为ASN1<br>
|
||||
* 在硬件签名中,返回结果为R+S,可以通过调用{@link cn.hutool.crypto.SmUtil#rsAsn1ToPlain(byte[])}方法转换之。
|
||||
*
|
||||
* @param data 被签名的数据数据
|
||||
* @param id 可以为null,若为null,则默认withId为字节数组:"1234567812345678".getBytes()
|
||||
@ -471,6 +473,27 @@ public class SM2 extends AbstractAsymmetricCrypto<SM2> {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得私钥D值(编码后的私钥)
|
||||
*
|
||||
* @return D值
|
||||
* @since 5.5.9
|
||||
*/
|
||||
public byte[] getD() {
|
||||
return this.privateKeyParams.getD().toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得公钥Q值(编码后的公钥)
|
||||
*
|
||||
* @param isCompressed 是否压缩
|
||||
* @return Q值
|
||||
* @since 5.5.9
|
||||
*/
|
||||
public byte[] getQ(boolean isCompressed) {
|
||||
return this.publicKeyParams.getQ().getEncoded(isCompressed);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------------------------------- Private method start
|
||||
|
||||
/**
|
||||
|
@ -1,7 +1,7 @@
|
||||
package cn.hutool.crypto.digest;
|
||||
|
||||
/**
|
||||
* SM3算法
|
||||
* SM3杂凑算法
|
||||
*
|
||||
* @author looly
|
||||
* @since 4.6.8
|
||||
|
@ -0,0 +1,66 @@
|
||||
package cn.hutool.crypto.symmetric;
|
||||
|
||||
import cn.hutool.core.util.HexUtil;
|
||||
import cn.hutool.crypto.KeyUtil;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
|
||||
/**
|
||||
* PBKDF2应用一个伪随机函数以导出密钥,PBKDF2简单而言就是将salted hash进行多次重复计算。
|
||||
* 参考:https://blog.csdn.net/huoji555/article/details/83659687
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
public class PBKDF2 {
|
||||
|
||||
private String algorithm = "PBKDF2WithHmacSHA1";
|
||||
//生成密文的长度
|
||||
private int keyLength = 512;
|
||||
|
||||
//迭代次数
|
||||
private int iterationCount = 1000;
|
||||
|
||||
/**
|
||||
* 构造,算法PBKDF2WithHmacSHA1,盐长度16,密文长度512,迭代次数1000
|
||||
*/
|
||||
public PBKDF2() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param algorithm 算法,一般为PBKDF2WithXXX
|
||||
* @param keyLength 生成密钥长度,默认512
|
||||
* @param iterationCount 迭代次数,默认1000
|
||||
*/
|
||||
public PBKDF2(String algorithm, int keyLength, int iterationCount) {
|
||||
this.algorithm = algorithm;
|
||||
this.keyLength = keyLength;
|
||||
this.iterationCount = iterationCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密
|
||||
*
|
||||
* @param password 密码
|
||||
* @param salt 盐
|
||||
* @return 加密后的密码
|
||||
*/
|
||||
public byte[] encrypt(char[] password, byte[] salt) {
|
||||
final PBEKeySpec pbeKeySpec = new PBEKeySpec(password, salt, iterationCount, keyLength);
|
||||
final SecretKey secretKey = KeyUtil.generateKey(algorithm, pbeKeySpec);
|
||||
return secretKey.getEncoded();
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密
|
||||
*
|
||||
* @param password 密码
|
||||
* @param salt 盐
|
||||
* @return 加密后的密码
|
||||
*/
|
||||
public String encryptHex(char[] password, byte[] salt) {
|
||||
return HexUtil.encodeHexStr(encrypt(password, salt));
|
||||
}
|
||||
}
|
@ -1,12 +1,17 @@
|
||||
package cn.hutool.crypto.test;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.resource.ResourceUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.crypto.PemUtil;
|
||||
import cn.hutool.crypto.asymmetric.KeyType;
|
||||
import cn.hutool.crypto.asymmetric.RSA;
|
||||
import cn.hutool.crypto.asymmetric.SM2;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
|
||||
@ -45,7 +50,31 @@ public class PemUtilTest {
|
||||
|
||||
@Test
|
||||
public void readECPrivateKeyTest() {
|
||||
PrivateKey privateKey = PemUtil.readPemPrivateKey(ResourceUtil.getStream("test_ec_private_key.pem"));
|
||||
Assert.assertNotNull(privateKey);
|
||||
PrivateKey privateKey = PemUtil.readSm2PemPrivateKey(ResourceUtil.getStream("test_ec_private_key.pem"));
|
||||
SM2 sm2 = new SM2(privateKey, null);
|
||||
sm2.usePlainEncoding();
|
||||
|
||||
//需要签名的明文,得到明文对应的字节数组
|
||||
byte[] dataBytes = "我是一段测试aaaa".getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
byte[] sign = sm2.sign(dataBytes, null);
|
||||
// 64位签名
|
||||
Assert.assertEquals(64, sign.length);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void readECPrivateKeyTest2() {
|
||||
// https://gitee.com/loolly/hutool/issues/I37Z75
|
||||
byte[] d = PemUtil.readPem(FileUtil.getInputStream("d:/test/keys/priv.key"));
|
||||
byte[] publicKey = PemUtil.readPem(FileUtil.getInputStream("d:/test/keys/pub.key"));
|
||||
|
||||
SM2 sm2 = new SM2(d, publicKey);
|
||||
sm2.usePlainEncoding();
|
||||
|
||||
String content = "我是Hanley.";
|
||||
byte[] sign = sm2.sign(StrUtil.utf8Bytes(content));
|
||||
boolean verify = sm2.verify(StrUtil.utf8Bytes(content), sign);
|
||||
Assert.assertTrue(verify);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
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;
|
||||
@ -13,10 +12,11 @@ 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.bouncycastle.jcajce.spec.OpenSSHPrivateKeySpec;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
@ -31,7 +31,6 @@ public class SM2Test {
|
||||
@Test
|
||||
public void generateKeyPairTest() {
|
||||
KeyPair pair = SecureUtil.generateKeyPair("SM2");
|
||||
Console.log(HexUtil.encodeHexStr(pair.getPublic().getEncoded()));
|
||||
Assert.assertNotNull(pair.getPrivate());
|
||||
Assert.assertNotNull(pair.getPublic());
|
||||
}
|
||||
@ -114,14 +113,44 @@ public class SM2Test {
|
||||
Assert.assertEquals(text.toString(), decryptStr2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sm2SignTest(){
|
||||
//需要签名的明文,得到明文对应的字节数组
|
||||
byte[] dataBytes = "我是一段测试aaaa".getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
//指定的私钥
|
||||
String privateKeyHex = "1ebf8b341c695ee456fd1a41b82645724bc25d79935437d30e7e4b0a554baa5e";
|
||||
final SM2 sm2 = new SM2(privateKeyHex, null, null);
|
||||
sm2.usePlainEncoding();
|
||||
byte[] sign = sm2.sign(dataBytes, null);
|
||||
// 64位签名
|
||||
Assert.assertEquals(64, sign.length);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sm2VerifyTest(){
|
||||
//指定的公钥
|
||||
String publicKeyHex = "04db9629dd33ba568e9507add5df6587a0998361a03d3321948b448c653c2c1b7056434884ab6f3d1c529501f166a336e86f045cea10dffe58aa82ea13d7253763";
|
||||
//需要加密的明文,得到明文对应的字节数组
|
||||
byte[] dataBytes = "我是一段测试aaaa".getBytes(StandardCharsets.UTF_8);
|
||||
//签名值
|
||||
String signHex = "2881346e038d2ed706ccdd025f2b1dafa7377d5cf090134b98756fafe084dddbcdba0ab00b5348ed48025195af3f1dda29e819bb66aa9d4d088050ff148482a1";
|
||||
|
||||
final SM2 sm2 = new SM2(null, publicKeyHex);
|
||||
sm2.usePlainEncoding();
|
||||
|
||||
boolean verify = sm2.verify(dataBytes, HexUtil.decodeHex(signHex));
|
||||
Assert.assertTrue(verify);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sm2SignAndVerifyTest() {
|
||||
String content = "我是Hanley.";
|
||||
|
||||
final SM2 sm2 = SmUtil.sm2();
|
||||
|
||||
byte[] sign = sm2.sign(content.getBytes());
|
||||
boolean verify = sm2.verify(content.getBytes(), sign);
|
||||
byte[] sign = sm2.sign(StrUtil.utf8Bytes(content));
|
||||
boolean verify = sm2.verify(StrUtil.utf8Bytes(content), sign);
|
||||
Assert.assertTrue(verify);
|
||||
}
|
||||
|
||||
@ -144,8 +173,8 @@ public class SM2Test {
|
||||
|
||||
final SM2 sm2 = new SM2(pair.getPrivate(), pair.getPublic());
|
||||
|
||||
byte[] sign = sm2.sign(content.getBytes());
|
||||
boolean verify = sm2.verify(content.getBytes(), sign);
|
||||
byte[] sign = sm2.sign(content.getBytes(StandardCharsets.UTF_8));
|
||||
boolean verify = sm2.verify(content.getBytes(StandardCharsets.UTF_8), sign);
|
||||
Assert.assertTrue(verify);
|
||||
}
|
||||
|
||||
@ -160,8 +189,8 @@ public class SM2Test {
|
||||
HexUtil.encodeHexStr(pair.getPublic().getEncoded())//
|
||||
);
|
||||
|
||||
byte[] sign = sm2.sign(content.getBytes());
|
||||
boolean verify = sm2.verify(content.getBytes(), sign);
|
||||
byte[] sign = sm2.sign(content.getBytes(StandardCharsets.UTF_8));
|
||||
boolean verify = sm2.verify(content.getBytes(StandardCharsets.UTF_8), sign);
|
||||
Assert.assertTrue(verify);
|
||||
}
|
||||
|
||||
@ -207,8 +236,12 @@ public class SM2Test {
|
||||
// 生成的签名是64位
|
||||
sm2.usePlainEncoding();
|
||||
|
||||
|
||||
String sign = "DCA0E80A7F46C93714B51C3EFC55A922BCEF7ECF0FE9E62B53BA6A7438B543A76C145A452CA9036F3CB70D7E6C67D4D9D7FE114E5367A2F6F5A4D39F2B10F3D6";
|
||||
Assert.assertTrue(sm2.verifyHex(data, sign));
|
||||
|
||||
String sign2 = sm2.signHex(data, id);
|
||||
Assert.assertTrue(sm2.verifyHex(data, sign2));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -218,13 +251,9 @@ public class SM2Test {
|
||||
|
||||
String data = "123456";
|
||||
|
||||
final ECPublicKeyParameters ecPublicKeyParameters = ECKeyUtil.toSm2PublicParams(q);
|
||||
final ECPrivateKeyParameters ecPrivateKeyParameters = ECKeyUtil.toSm2PrivateParams(d);
|
||||
|
||||
final SM2 sm2 = new SM2(ecPrivateKeyParameters, ecPublicKeyParameters);
|
||||
final SM2 sm2 = new SM2(d, q);
|
||||
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);
|
||||
@ -236,11 +265,50 @@ public class SM2Test {
|
||||
|
||||
String src = "Sm2Test";
|
||||
byte[] data = sm2.encrypt(src, KeyType.PublicKey);
|
||||
byte[] sign = sm2.sign(src.getBytes());
|
||||
byte[] sign = sm2.sign(src.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
Assert.assertTrue(sm2.verify( src.getBytes(), sign));
|
||||
Assert.assertTrue(sm2.verify( src.getBytes(StandardCharsets.UTF_8), sign));
|
||||
|
||||
byte[] dec = sm2.decrypt(data, KeyType.PrivateKey);
|
||||
Assert.assertArrayEquals(dec, src.getBytes());
|
||||
Assert.assertArrayEquals(dec, src.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPublicKeyByPrivateKeyTest(){
|
||||
// issue#I38SDP,openSSL生成的PKCS#1格式私钥
|
||||
String priKey = "MHcCAQEEIE29XqAFV/rkJbnJzCoQRJLTeAHG2TR0h9ZCWag0+ZMEoAoGCCqBHM9VAYItoUQDQgAESkOzNigIsH5ehFvr9y" +
|
||||
"QNQ66genyOrm+Q4umCA4aWXPeRzmcTAWSlTineiReTFN2lqor2xaulT8u3a4w3AM/F6A==";
|
||||
|
||||
PrivateKey privateKey = KeyUtil.generatePrivateKey("sm2", new OpenSSHPrivateKeySpec(SecureUtil.decode(priKey)));
|
||||
final ECPrivateKeyParameters privateKeyParameters = ECKeyUtil.toPrivateParams(privateKey);
|
||||
|
||||
final SM2 sm2 = new SM2(privateKeyParameters, ECKeyUtil.getPublicParams(privateKeyParameters));
|
||||
|
||||
String src = "Sm2Test";
|
||||
byte[] data = sm2.encrypt(src, KeyType.PublicKey);
|
||||
byte[] sign = sm2.sign(src.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
Assert.assertTrue(sm2.verify( src.getBytes(StandardCharsets.UTF_8), sign));
|
||||
|
||||
byte[] dec = sm2.decrypt(data, KeyType.PrivateKey);
|
||||
Assert.assertArrayEquals(dec, src.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readPublicKeyTest(){
|
||||
String priKey = "MHcCAQEEIE29XqAFV/rkJbnJzCoQRJLTeAHG2TR0h9ZCWag0+ZMEoAoGCCqBHM9VAYItoUQDQgAESkOzNigIsH5ehFvr9y" +
|
||||
"QNQ66genyOrm+Q4umCA4aWXPeRzmcTAWSlTineiReTFN2lqor2xaulT8u3a4w3AM/F6A==";
|
||||
String pubKey = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAESkOzNigIsH5ehFvr9yQNQ66genyOrm+Q4umCA4aWXPeRzmcTAWSlTineiReTFN2lqor2xaulT8u3a4w3AM/F6A==";
|
||||
|
||||
SM2 sm2 = SmUtil.sm2(priKey, pubKey);
|
||||
|
||||
String src = "Sm2Test中文";
|
||||
byte[] data = sm2.encrypt(src, KeyType.PublicKey);
|
||||
byte[] sign = sm2.sign(src.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
Assert.assertTrue(sm2.verify( src.getBytes(StandardCharsets.UTF_8), sign));
|
||||
|
||||
byte[] dec = sm2.decrypt(data, KeyType.PrivateKey);
|
||||
Assert.assertArrayEquals(dec, src.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,15 @@
|
||||
package cn.hutool.crypto.test.symmetric;
|
||||
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class PBKDF2Test {
|
||||
|
||||
@Test
|
||||
public void encryptTest(){
|
||||
final String s = SecureUtil.pbkdf2("123456".toCharArray(), RandomUtil.randomBytes(16));
|
||||
Assert.assertEquals(128, s.length());
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.9-SNAPSHOT</version>
|
||||
<version>5.6.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-db</artifactId>
|
||||
|
@ -91,7 +91,6 @@ public class Entity extends Dict {
|
||||
|
||||
// --------------------------------------------------------------- Constructor start
|
||||
public Entity() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -101,7 +100,6 @@ public class Entity extends Dict {
|
||||
*/
|
||||
|
||||
public Entity(String tableName) {
|
||||
super();
|
||||
this.tableName = tableName;
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.text.StrSpliter;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.CharUtil;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.util.Arrays;
|
||||
@ -444,7 +445,7 @@ public class Condition extends CloneSupport<Condition> {
|
||||
return;
|
||||
}
|
||||
|
||||
valueStr = valueStr.trim();
|
||||
valueStr = StrUtil.trim(valueStr);
|
||||
|
||||
// 处理null
|
||||
if (StrUtil.endWithIgnoreCase(valueStr, "null")) {
|
||||
@ -463,7 +464,7 @@ public class Condition extends CloneSupport<Condition> {
|
||||
}
|
||||
}
|
||||
|
||||
List<String> strs = StrUtil.split(valueStr, StrUtil.C_SPACE, 2);
|
||||
final List<String> strs = StrUtil.split(valueStr, StrUtil.C_SPACE, 2);
|
||||
if (strs.size() < 2) {
|
||||
return;
|
||||
}
|
||||
@ -472,7 +473,9 @@ public class Condition extends CloneSupport<Condition> {
|
||||
final String firstPart = strs.get(0).trim().toUpperCase();
|
||||
if (OPERATORS.contains(firstPart)) {
|
||||
this.operator = firstPart;
|
||||
this.value = strs.get(1).trim();
|
||||
// 比较符号后跟大部分为数字,此处做转换(IN不做转换)
|
||||
final String valuePart = strs.get(1);
|
||||
this.value = isOperatorIn() ? valuePart : tryToNumber(valuePart);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -526,5 +529,23 @@ public class Condition extends CloneSupport<Condition> {
|
||||
}
|
||||
return value.substring(from, to);
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试转换为数字,转换失败返回字符串
|
||||
*
|
||||
* @param value 被转换的字符串值
|
||||
* @return 转换后的值
|
||||
*/
|
||||
private static Object tryToNumber(String value){
|
||||
value = StrUtil.trim(value);
|
||||
if(false == NumberUtil.isNumber(value)){
|
||||
return value;
|
||||
}
|
||||
try{
|
||||
return NumberUtil.parseNumber(value);
|
||||
} catch (Exception ignore){
|
||||
return value;
|
||||
}
|
||||
}
|
||||
// ----------------------------------------------------------------------------------------------- Private method end
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package cn.hutool.db;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import cn.hutool.db.Entity;
|
||||
import cn.hutool.db.pojo.User;
|
||||
|
||||
/**
|
||||
|
@ -6,10 +6,6 @@ import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.db.Db;
|
||||
import cn.hutool.db.Entity;
|
||||
import cn.hutool.db.Page;
|
||||
import cn.hutool.db.PageResult;
|
||||
|
||||
/**
|
||||
* PostgreSQL 单元测试
|
||||
|
@ -6,10 +6,6 @@ import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.db.Db;
|
||||
import cn.hutool.db.Entity;
|
||||
import cn.hutool.db.Page;
|
||||
import cn.hutool.db.PageResult;
|
||||
|
||||
/**
|
||||
* SQL Server操作单元测试
|
||||
|
@ -52,4 +52,18 @@ public class ConditionTest {
|
||||
conditionBetween.setPlaceHolder(false);
|
||||
Assert.assertEquals("user BETWEEN 12 AND 13", conditionBetween.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseTest(){
|
||||
final Condition age = Condition.parse("age", "< 10");
|
||||
Assert.assertEquals("age < ?", age.toString());
|
||||
// issue I38LTM
|
||||
Assert.assertSame(Long.class, age.getValue().getClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseInTest(){
|
||||
final Condition age = Condition.parse("age", "in 1,2,3");
|
||||
Assert.assertEquals("age IN (?,?,?)", age.toString());
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.9-SNAPSHOT</version>
|
||||
<version>5.6.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-dfa</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.9-SNAPSHOT</version>
|
||||
<version>5.6.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-extra</artifactId>
|
||||
|
@ -110,6 +110,8 @@ public class Ftp extends AbstractFtp {
|
||||
* @param user 用户名
|
||||
* @param password 密码
|
||||
* @param charset 编码
|
||||
* @param serverLanguageCode 服务器语言
|
||||
* @param systemKey 系统关键字
|
||||
* @param mode 模式
|
||||
*/
|
||||
public Ftp(String host, int port, String user, String password, Charset charset, String serverLanguageCode, String systemKey, FtpMode mode) {
|
||||
@ -341,11 +343,11 @@ public class Ftp extends AbstractFtp {
|
||||
* @throws FtpException 路径不存在
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public FTPFile[] lsFiles(String path) throws FtpException, IORuntimeException{
|
||||
public FTPFile[] lsFiles(String path) throws FtpException, IORuntimeException {
|
||||
String pwd = null;
|
||||
if (StrUtil.isNotBlank(path)) {
|
||||
pwd = pwd();
|
||||
if(false == cd(path)){
|
||||
if (false == cd(path)) {
|
||||
throw new FtpException("Change dir to [{}] error, maybe path not exist!", path);
|
||||
}
|
||||
}
|
||||
@ -364,7 +366,7 @@ public class Ftp extends AbstractFtp {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mkdir(String dir) throws IORuntimeException{
|
||||
public boolean mkdir(String dir) throws IORuntimeException {
|
||||
try {
|
||||
return this.client.makeDirectory(dir);
|
||||
} catch (IOException e) {
|
||||
@ -379,7 +381,7 @@ public class Ftp extends AbstractFtp {
|
||||
* @return 状态int,服务端不同,返回不同
|
||||
* @since 5.4.3
|
||||
*/
|
||||
public int stat(String path) throws IORuntimeException{
|
||||
public int stat(String path) throws IORuntimeException {
|
||||
try {
|
||||
return this.client.stat(path);
|
||||
} catch (IOException e) {
|
||||
@ -394,7 +396,7 @@ public class Ftp extends AbstractFtp {
|
||||
* @return 是否存在
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public boolean existFile(String path) throws IORuntimeException{
|
||||
public boolean existFile(String path) throws IORuntimeException {
|
||||
FTPFile[] ftpFileArr;
|
||||
try {
|
||||
ftpFileArr = client.listFiles(path);
|
||||
@ -405,11 +407,11 @@ public class Ftp extends AbstractFtp {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delFile(String path) throws IORuntimeException{
|
||||
public boolean delFile(String path) throws IORuntimeException {
|
||||
final String pwd = pwd();
|
||||
final String fileName = FileUtil.getName(path);
|
||||
final String dir = StrUtil.removeSuffix(path, fileName);
|
||||
if(false == cd(dir)){
|
||||
if (false == cd(dir)) {
|
||||
throw new FtpException("Change dir to [{}] error, maybe dir not exist!", path);
|
||||
}
|
||||
|
||||
@ -426,7 +428,7 @@ public class Ftp extends AbstractFtp {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delDir(String dirPath) throws IORuntimeException{
|
||||
public boolean delDir(String dirPath) throws IORuntimeException {
|
||||
FTPFile[] dirs;
|
||||
try {
|
||||
dirs = client.listFiles(dirPath);
|
||||
@ -490,7 +492,7 @@ public class Ftp extends AbstractFtp {
|
||||
* @return 是否上传成功
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public boolean upload(String path, String fileName, File file) throws IORuntimeException{
|
||||
public boolean upload(String path, String fileName, File file) throws IORuntimeException {
|
||||
try (InputStream in = FileUtil.getInputStream(file)) {
|
||||
return upload(path, fileName, in);
|
||||
} catch (IOException e) {
|
||||
@ -513,7 +515,7 @@ public class Ftp extends AbstractFtp {
|
||||
* @return 是否上传成功
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public boolean upload(String path, String fileName, InputStream fileStream) throws IORuntimeException{
|
||||
public boolean upload(String path, String fileName, InputStream fileStream) throws IORuntimeException {
|
||||
try {
|
||||
client.setFileType(FTPClient.BINARY_FILE_TYPE);
|
||||
} catch (IOException e) {
|
||||
@ -594,7 +596,7 @@ public class Ftp extends AbstractFtp {
|
||||
* @param outFile 输出文件或目录
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public void download(String path, String fileName, File outFile) throws IORuntimeException{
|
||||
public void download(String path, String fileName, File outFile) throws IORuntimeException {
|
||||
if (outFile.isDirectory()) {
|
||||
outFile = new File(outFile, fileName);
|
||||
}
|
||||
@ -626,16 +628,16 @@ public class Ftp extends AbstractFtp {
|
||||
* @param fileName 文件名
|
||||
* @param out 输出位置
|
||||
* @param fileNameCharset 文件名编码
|
||||
* @since 5.5.7
|
||||
* @throws IORuntimeException IO异常
|
||||
* @since 5.5.7
|
||||
*/
|
||||
public void download(String path, String fileName, OutputStream out, Charset fileNameCharset) throws IORuntimeException{
|
||||
public void download(String path, String fileName, OutputStream out, Charset fileNameCharset) throws IORuntimeException {
|
||||
String pwd = null;
|
||||
if (this.backToPwd) {
|
||||
pwd = pwd();
|
||||
}
|
||||
|
||||
if(false == cd(path)){
|
||||
if (false == cd(path)) {
|
||||
throw new FtpException("Change dir to [{}] error, maybe dir not exist!", path);
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,6 @@ public class UserPassAuthenticator extends Authenticator {
|
||||
* @param pass 密码
|
||||
*/
|
||||
public UserPassAuthenticator(String user, String pass) {
|
||||
super();
|
||||
this.user = user;
|
||||
this.pass = pass;
|
||||
}
|
||||
|
@ -39,7 +39,6 @@ public class Connector {
|
||||
* @param password 密码
|
||||
*/
|
||||
public Connector(String host, int port, String user, String password) {
|
||||
super();
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.user = user;
|
||||
|
@ -53,7 +53,6 @@ public class AviatorTest {
|
||||
Bar[] bars = new Bar[1];
|
||||
|
||||
public Foo(final int i, final float f, final Date date) {
|
||||
super();
|
||||
this.i = i;
|
||||
this.f = f;
|
||||
this.date = date;
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.9-SNAPSHOT</version>
|
||||
<version>5.6.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-http</artifactId>
|
||||
|
@ -378,6 +378,7 @@ public class HttpServerResponse extends HttpServerBase {
|
||||
* 返回文件给客户端(文件下载)
|
||||
*
|
||||
* @param file 写出的文件对象
|
||||
* @param fileName 文件名
|
||||
* @return this
|
||||
* @since 5.5.8
|
||||
*/
|
||||
@ -417,6 +418,7 @@ public class HttpServerResponse extends HttpServerBase {
|
||||
* 返回文件数据给客户端(文件下载)
|
||||
*
|
||||
* @param in 需要返回客户端的内容
|
||||
* @param length 长度
|
||||
* @param contentType 返回的类型
|
||||
* @param fileName 文件名
|
||||
* @since 5.2.7
|
||||
|
@ -28,7 +28,6 @@ public class CustomProtocolsSSLFactory extends SSLSocketFactory {
|
||||
* @throws NoSuchAlgorithmException NoSuchAlgorithmException
|
||||
*/
|
||||
public CustomProtocolsSSLFactory(String... protocols) throws KeyManagementException, NoSuchAlgorithmException {
|
||||
super();
|
||||
this.protocols = protocols;
|
||||
this.base = SSLSocketFactoryBuilder.create().build();
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ import java.security.NoSuchAlgorithmException;
|
||||
public class DefaultSSLFactory extends CustomProtocolsSSLFactory {
|
||||
|
||||
public DefaultSSLFactory() throws KeyManagementException, NoSuchAlgorithmException {
|
||||
super();
|
||||
}
|
||||
|
||||
}
|
@ -4,8 +4,6 @@ import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.io.StreamProgress;
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
@ -3,8 +3,6 @@ package cn.hutool.http;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
|
||||
/**
|
||||
* Html单元测试
|
||||
*
|
||||
|
@ -318,11 +318,4 @@ public class HttpUtilTest {
|
||||
final String s = HttpUtil.get(url);
|
||||
Console.log(s);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tjhrTest(){
|
||||
String url = "https://www.51tjhr.com";
|
||||
final String s = HttpUtil.get(url);
|
||||
Console.log(s);
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package cn.hutool.http;
|
||||
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
|
@ -1,9 +1,6 @@
|
||||
package cn.hutool.http;
|
||||
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.http.Header;
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.9-SNAPSHOT</version>
|
||||
<version>5.6.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-json</artifactId>
|
||||
|
@ -5,8 +5,8 @@ import cn.hutool.core.util.StrUtil;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 用于定义<code>null</code>,与Javascript中null相对应<br>
|
||||
* Java中的<code>null</code>值在js中表示为undefined。
|
||||
* 用于定义{@code null},与Javascript中null相对应<br>
|
||||
* Java中的{@code null}值在js中表示为undefined。
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
@ -14,15 +14,15 @@ public class JSONNull implements Serializable{
|
||||
private static final long serialVersionUID = 2633815155870764938L;
|
||||
|
||||
/**
|
||||
* <code>NULL</code> 对象用于减少歧义来表示Java 中的<code>null</code> <br>
|
||||
* <code>NULL.equals(null)</code> 返回 <code>true</code>. <br>
|
||||
* <code>NULL.toString()</code> 返回 <code>"null"</code>.
|
||||
* {@code NULL} 对象用于减少歧义来表示Java 中的{@code null} <br>
|
||||
* {@code NULL.equals(null)} 返回 {@code true}. <br>
|
||||
* {@code NULL.toString()} 返回 {@code "null"}.
|
||||
*/
|
||||
public static final JSONNull NULL = new JSONNull();
|
||||
|
||||
/**
|
||||
* A Null object is equal to the null value and to itself.
|
||||
* 对象与其本身和<code>null</code>值相等
|
||||
* 对象与其本身和{@code null}值相等
|
||||
*
|
||||
* @param object An object to test for nullness.
|
||||
* @return true if the object parameter is the JSONObject.NULL object or null.
|
||||
|
@ -35,7 +35,7 @@ import java.util.ResourceBundle;
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public final class JSONUtil {
|
||||
public class JSONUtil {
|
||||
|
||||
// -------------------------------------------------------------------- Pause start
|
||||
|
||||
|
@ -62,7 +62,7 @@ public class XML {
|
||||
/**
|
||||
* 转换XML为JSONObject
|
||||
* 转换过程中一些信息可能会丢失,JSON中无法区分节点和属性,相同的节点将被处理为JSONArray。
|
||||
* Content text may be placed in a "content" member. Comments, prologs, DTDs, and <code><[ [ ]]></code> are ignored.
|
||||
* Content text may be placed in a "content" member. Comments, prologs, DTDs, and {@code <[ [ ]]>} are ignored.
|
||||
*
|
||||
* @param string The source string.
|
||||
* @return A JSONObject containing the structured data from the XML string.
|
||||
@ -75,7 +75,7 @@ public class XML {
|
||||
/**
|
||||
* 转换XML为JSONObject
|
||||
* 转换过程中一些信息可能会丢失,JSON中无法区分节点和属性,相同的节点将被处理为JSONArray。
|
||||
* Content text may be placed in a "content" member. Comments, prologs, DTDs, and <code><[ [ ]]></code> are ignored.
|
||||
* Content text may be placed in a "content" member. Comments, prologs, DTDs, and {@code <[ [ ]]>} are ignored.
|
||||
* All values are converted as strings, for 1, 01, 29.0 will not be coerced to numbers but will instead be the exact value as seen in the XML document.
|
||||
*
|
||||
* @param string The source string.
|
||||
|
@ -75,7 +75,6 @@ public class ResultDto<T> implements Serializable {
|
||||
* @param result the result
|
||||
*/
|
||||
public ResultDto(int code, String message, T result) {
|
||||
super();
|
||||
this.code(code).message(message).result(result);
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,7 @@ public class CaseReport {
|
||||
/**
|
||||
* 包含的测试步骤报告
|
||||
*/
|
||||
private List<StepReport> stepReports = new ArrayList<StepReport>();
|
||||
private List<StepReport> stepReports = new ArrayList<>();
|
||||
|
||||
public List<StepReport> getStepReports() {
|
||||
return stepReports;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user