Merge remote-tracking branch 'upstream/v5-dev' into v5-dev

This commit is contained in:
totalo 2021-03-10 14:53:49 +08:00
commit bb5b524418
201 changed files with 1959 additions and 1047 deletions

View File

@ -3,17 +3,45 @@
-------------------------------------------------------------------------------------------------------------
# 5.5.9 (2021-02-18)
# 5.6.0 (2021-03-10)
### 新特性
* 【poi 】 重要不再兼容POI-3.x增加兼容POI-5.xissue#I35J6B@Gitee
* 【core 】 FileTypeUtil使用长匹配优先pr#1457@Github
* 【core 】 IterUtil和CollUtil增加isEqualList方法issue#I3A3PY@Gitee
* 【crypto 】 增加PBKDF2issue#1416@Github
* 【core 】 增加FuncKeyMapissue#1402@Github
* 【core 】 增加StrMatcherissue#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支持ECpr#1366@Github
* 【extra 】 Ftp等cd方法增加同步issue#1397@Github
* 【core 】 StrUtil增加endWithAnyIgnoreCaseissue#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
-------------------------------------------------------------------------------------------------------------

View File

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

View File

@ -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平台没有测试不能保证所有工具类或工具方法可用。

View File

@ -1 +1 @@
5.5.9
5.6.0

View File

@ -1 +1 @@
var version = '5.5.9'
var version = '5.6.0'

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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将无效

View File

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

View File

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

View File

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

View File

@ -60,7 +60,6 @@ public class CompareToBuilder implements Builder<Integer> {
* 构造构造后调用append方法增加比较项然后调用{@link #toComparison()}获取结果
*/
public CompareToBuilder() {
super();
comparison = 0;
}

View File

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

View File

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

View File

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

View File

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

View File

@ -21,7 +21,6 @@ public class ComparableComparator<E extends Comparable<? super E>> implements Co
* 构造
*/
public ComparableComparator() {
super();
}
/**

View File

@ -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());
}
/**
* 将阿拉伯数字转为中文表达方式
*

View File

@ -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 控制是否为kw
* @return
* @param isTwo 控制是否为只为kw例如当为{@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]);
}
/**

View File

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

View File

@ -27,7 +27,6 @@ public final class FastStringWriter extends Writer {
* @param initialSize 初始容量
*/
public FastStringWriter(int initialSize) {
super();
if (initialSize < 0) {
initialSize = StrBuilder.DEFAULT_CAPACITY;
}

View File

@ -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 注意wordmsi 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);
}
/**

View File

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

View File

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

View File

@ -30,7 +30,6 @@ public final class Holder<T> extends MutableObj<T>{
* 构造
*/
public Holder() {
super();
}
/**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View 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 匹配的mapkey为变量名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;
}
}

View File

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

View File

@ -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 返回结果
*/

View File

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

View File

@ -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 阶乘起始包含

View File

@ -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;
/**
* 手机号工具类

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 算法如RSAECSM2等
* @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 算法如RSAECSM2等
* @param keySpec {@link KeySpec}
* @return 私钥 {@link PrivateKey}
* @since 3.1.1

View File

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

View File

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

View File

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

View File

@ -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#8D值或PKCS#1规范
* @param publicKey 公钥可以使用X509Q值或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
/**

View File

@ -1,7 +1,7 @@
package cn.hutool.crypto.digest;
/**
* SM3算法
* SM3杂凑算法
*
* @author looly
* @since 4.6.8

View File

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

View File

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

View File

@ -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#I38SDPopenSSL生成的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));
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 单元测试

View File

@ -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操作单元测试

View File

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

View File

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

View File

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

View File

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

View File

@ -21,7 +21,6 @@ public class UserPassAuthenticator extends Authenticator {
* @param pass 密码
*/
public UserPassAuthenticator(String user, String pass) {
super();
this.user = user;
this.pass = pass;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,7 +12,6 @@ import java.security.NoSuchAlgorithmException;
public class DefaultSSLFactory extends CustomProtocolsSSLFactory {
public DefaultSSLFactory() throws KeyManagementException, NoSuchAlgorithmException {
super();
}
}

View File

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

View File

@ -3,8 +3,6 @@ package cn.hutool.http;
import org.junit.Assert;
import org.junit.Test;
import cn.hutool.http.HtmlUtil;
/**
* Html单元测试
*

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -35,7 +35,7 @@ import java.util.ResourceBundle;
*
* @author Looly
*/
public final class JSONUtil {
public class JSONUtil {
// -------------------------------------------------------------------- Pause start

View File

@ -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>&lt;[ [ ]]&gt;</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>&lt;[ [ ]]&gt;</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.

View File

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

View File

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