mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-05-09 23:51:34 +08:00
Merge branch 'v5-dev' of https://gitee.com/GuoZG0328/hutool into v5-dev
This commit is contained in:
commit
477ae4a9fc
15
CHANGELOG.md
15
CHANGELOG.md
@ -3,7 +3,7 @@
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
# 5.7.17 (2021-11-20)
|
||||
# 5.7.17 (2021-11-30)
|
||||
|
||||
### 🐣新特性
|
||||
* 【core 】 增加AsyncUtil(pr#457@Gitee)
|
||||
@ -21,11 +21,24 @@
|
||||
* 【core 】 ResourceClassLoader增加缓存(pr#1959@Github)
|
||||
* 【crypto 】 增加CipherWrapper,增加setRandom(issue#1958@Github)
|
||||
* 【core 】 Opt增加ofTry方法(pr#1956@Github)
|
||||
* 【core 】 DateUtil.toIntSecond标记为弃用(issue#I4JHPR@Gitee)
|
||||
* 【db 】 Db.executeBatch标记一个重载为弃用(issue#I4JIPH@Gitee)
|
||||
* 【core 】 增加CharSequenceUtil.subPreGbk重载(issue#I4JO2E@Gitee)
|
||||
* 【core 】 ReflectUtil.getMethod排除桥接方法(pr#1965@Github)
|
||||
* 【http 】 completeFileNameFromHeader在使用path为路径时,自动解码(issue#I4K0FS@Gitee)
|
||||
* 【core 】 CopyOptions增加override配置(issue#I4JQ1N@Gitee)
|
||||
* 【poi 】 SheetRidReader可以获取所有sheet名(issue#I4JA3M@Gitee)
|
||||
* 【core 】 AsyncUtil.waitAny增加返回值(pr#473@Gitee)
|
||||
* 【core 】 Calculator.compare改为private(issue#1982@Github)
|
||||
*
|
||||
### 🐞Bug修复
|
||||
* 【core 】 修复FileResource构造fileName参数无效问题(issue#1942@Github)
|
||||
* 【cache 】 修复WeakCache键值强关联导致的无法回收问题(issue#1953@Github)
|
||||
* 【core 】 修复ZipUtil相对路径父路径获取null问题(issue#1961@Github)
|
||||
* 【http 】 修复HttpUtil.normalizeParams未判空导致的问题(issue#1975@Github)
|
||||
* 【poi 】 修复读取日期类型的自定义样式单元格时间结果为1899年问题(pr#1977@Github)
|
||||
* 【poi 】 修复SoapClient参数未使用问题
|
||||
* 【core 】 修复HashUtil.cityHash128参数未使用问题
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
@ -209,6 +209,7 @@ Hutool欢迎任何人为Hutool添砖加瓦,贡献代码,不过维护者是
|
||||
2. Hutool的缩进按照Eclipse(~~不要跟我说IDEA多好用,维护者非常懒,学不会~~,IDEA真香,改了Eclipse快捷键后舒服多了)默认(tab)缩进,所以请遵守(不要和我争执空格与tab的问题,这是一个病人的习惯)。
|
||||
3. 新加的方法不要使用第三方库的方法,Hutool遵循无依赖原则(除非在extra模块中加方法工具)。
|
||||
4. 请pull request到`v5-dev`分支。Hutool在5.x版本后使用了新的分支:`v5-master`是主分支,表示已经发布中央库的版本,这个分支不允许pr,也不允许修改。
|
||||
5. 我们如果关闭了你的issue或pr,请不要诧异,这是我们保持问题处理整洁的一种方式,你依旧可以继续讨论,当有讨论结果时我们会重新打开。
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
|
@ -121,18 +121,19 @@ public class PropDesc {
|
||||
|
||||
/**
|
||||
* 检查属性是否可读(即是否可以通过{@link #getValue(Object)}获取到值)
|
||||
*
|
||||
* @param checkTransient 是否检查Transient关键字或注解
|
||||
* @return 是否可读
|
||||
* @since 5.4.2
|
||||
*/
|
||||
public boolean isReadable(boolean checkTransient){
|
||||
public boolean isReadable(boolean checkTransient) {
|
||||
// 检查是否有getter方法或是否为public修饰
|
||||
if(null == this.getter && false == ModifierUtil.isPublic(this.field)){
|
||||
if (null == this.getter && false == ModifierUtil.isPublic(this.field)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查transient关键字和@Transient注解
|
||||
if(checkTransient && isTransientForGet()){
|
||||
if (checkTransient && isTransientForGet()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -164,7 +165,7 @@ public class PropDesc {
|
||||
* 首先调用字段对应的Getter方法获取值,如果Getter方法不存在,则判断字段如果为public,则直接获取字段值
|
||||
*
|
||||
* @param bean Bean对象
|
||||
* @param targetType 返回属性值需要转换的类型,null表示不转换
|
||||
* @param targetType 返回属性值需要转换的类型,null表示不转换
|
||||
* @param ignoreError 是否忽略错误,包括转换错误和注入错误
|
||||
* @return this
|
||||
* @since 5.4.2
|
||||
@ -190,18 +191,19 @@ public class PropDesc {
|
||||
|
||||
/**
|
||||
* 检查属性是否可读(即是否可以通过{@link #getValue(Object)}获取到值)
|
||||
*
|
||||
* @param checkTransient 是否检查Transient关键字或注解
|
||||
* @return 是否可读
|
||||
* @since 5.4.2
|
||||
*/
|
||||
public boolean isWritable(boolean checkTransient){
|
||||
public boolean isWritable(boolean checkTransient) {
|
||||
// 检查是否有getter方法或是否为public修饰
|
||||
if(null == this.setter && false == ModifierUtil.isPublic(this.field)){
|
||||
if (null == this.setter && false == ModifierUtil.isPublic(this.field)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查transient关键字和@Transient注解
|
||||
if(checkTransient && isTransientForSet()){
|
||||
if (checkTransient && isTransientForSet()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -239,7 +241,28 @@ public class PropDesc {
|
||||
* @since 5.4.2
|
||||
*/
|
||||
public PropDesc setValue(Object bean, Object value, boolean ignoreNull, boolean ignoreError) {
|
||||
if (ignoreNull && null == value) {
|
||||
return setValue(bean, value, ignoreNull, ignoreError, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置属性值,可以自动转换字段类型为目标类型
|
||||
*
|
||||
* @param bean Bean对象
|
||||
* @param value 属性值,可以为任意类型
|
||||
* @param ignoreNull 是否忽略{@code null}值,true表示忽略
|
||||
* @param ignoreError 是否忽略错误,包括转换错误和注入错误
|
||||
* @param override 是否覆盖目标值,如果不覆盖,会先读取bean的值,非{@code null}则写,否则忽略。如果覆盖,则不判断直接写
|
||||
* @return this
|
||||
* @since 5.7.17
|
||||
*/
|
||||
public PropDesc setValue(Object bean, Object value, boolean ignoreNull, boolean ignoreError, boolean override) {
|
||||
if (null == value && ignoreNull) {
|
||||
return this;
|
||||
}
|
||||
|
||||
// issue#I4JQ1N@Gitee
|
||||
// 非覆盖模式下,如果目标值存在,则跳过
|
||||
if (false == override && null != getValue(bean)) {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -140,13 +140,44 @@ public class BeanCopier<T> implements Copier<T>, Serializable {
|
||||
* Map转Map
|
||||
*
|
||||
* @param source 源Map
|
||||
* @param dest 目标Map
|
||||
* @param targetMap 目标Map
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private void mapToMap(Map source, Map dest) {
|
||||
if (null != dest && null != source) {
|
||||
dest.putAll(source);
|
||||
}
|
||||
private void mapToMap(Map source, Map targetMap) {
|
||||
source.forEach((key, value)->{
|
||||
final CopyOptions copyOptions = this.copyOptions;
|
||||
final HashSet<String> ignoreSet = (null != copyOptions.ignoreProperties) ? CollUtil.newHashSet(copyOptions.ignoreProperties) : null;
|
||||
|
||||
// issue#I4JQ1N@Gitee
|
||||
// 非覆盖模式下,如果目标值存在,则跳过
|
||||
if(false == copyOptions.override && null != targetMap.get(key)){
|
||||
return;
|
||||
}
|
||||
|
||||
if(key instanceof CharSequence){
|
||||
if (CollUtil.contains(ignoreSet, key)) {
|
||||
// 目标属性值被忽略或值提供者无此key时跳过
|
||||
return;
|
||||
}
|
||||
|
||||
// 对key做映射,映射后为null的忽略之
|
||||
key = copyOptions.editFieldName(copyOptions.getMappedFieldName(key.toString(), false));
|
||||
if(null == key){
|
||||
return;
|
||||
}
|
||||
|
||||
value = copyOptions.editFieldValue(key.toString(), value);
|
||||
}
|
||||
|
||||
|
||||
if ((null == value && copyOptions.ignoreNullValue) || source == value) {
|
||||
// 当允许跳过空时,跳过
|
||||
//值不能为bean本身,防止循环引用,此类也跳过
|
||||
return;
|
||||
}
|
||||
|
||||
targetMap.put(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -158,11 +189,11 @@ public class BeanCopier<T> implements Copier<T>, Serializable {
|
||||
*/
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private void beanToMap(Object bean, Map targetMap) {
|
||||
final HashSet<String> ignoreSet = (null != copyOptions.ignoreProperties) ? CollUtil.newHashSet(copyOptions.ignoreProperties) : null;
|
||||
final CopyOptions copyOptions = this.copyOptions;
|
||||
final HashSet<String> ignoreSet = (null != copyOptions.ignoreProperties) ? CollUtil.newHashSet(copyOptions.ignoreProperties) : null;
|
||||
|
||||
BeanUtil.descForEach(bean.getClass(), (prop)->{
|
||||
if(false == prop.isReadable(copyOptions.isTransientSupport())){
|
||||
if(false == prop.isReadable(copyOptions.transientSupport)){
|
||||
// 忽略的属性跳过之
|
||||
return;
|
||||
}
|
||||
@ -178,6 +209,12 @@ public class BeanCopier<T> implements Copier<T>, Serializable {
|
||||
return;
|
||||
}
|
||||
|
||||
// issue#I4JQ1N@Gitee
|
||||
// 非覆盖模式下,如果目标值存在,则跳过
|
||||
if(false == copyOptions.override && null != targetMap.get(key)){
|
||||
return;
|
||||
}
|
||||
|
||||
Object value;
|
||||
try {
|
||||
value = prop.getValue(bean);
|
||||
@ -230,7 +267,7 @@ public class BeanCopier<T> implements Copier<T>, Serializable {
|
||||
|
||||
// 遍历目标bean的所有属性
|
||||
BeanUtil.descForEach(actualEditable, (prop)->{
|
||||
if(false == prop.isWritable(this.copyOptions.isTransientSupport())){
|
||||
if(false == prop.isWritable(this.copyOptions.transientSupport)){
|
||||
// 字段不可写,跳过之
|
||||
return;
|
||||
}
|
||||
@ -270,7 +307,7 @@ public class BeanCopier<T> implements Copier<T>, Serializable {
|
||||
return;
|
||||
}
|
||||
|
||||
prop.setValue(bean, value, copyOptions.ignoreNullValue, copyOptions.ignoreError);
|
||||
prop.setValue(bean, value, copyOptions.ignoreNullValue, copyOptions.ignoreError, copyOptions.override);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -65,7 +65,11 @@ public class CopyOptions implements Serializable {
|
||||
/**
|
||||
* 是否支持transient关键字修饰和@Transient注解,如果支持,被修饰的字段或方法对应的字段将被忽略。
|
||||
*/
|
||||
private boolean transientSupport = true;
|
||||
protected boolean transientSupport = true;
|
||||
/**
|
||||
* 是否覆盖目标值,如果不覆盖,会先读取目标对象的值,非{@code null}则写,否则忽略。如果覆盖,则不判断直接写
|
||||
*/
|
||||
protected boolean override = true;
|
||||
|
||||
/**
|
||||
* 创建拷贝选项
|
||||
@ -259,7 +263,9 @@ public class CopyOptions implements Serializable {
|
||||
*
|
||||
* @return 是否支持
|
||||
* @since 5.4.2
|
||||
* @deprecated 无需此方法,内部使用直接调用属性
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean isTransientSupport() {
|
||||
return this.transientSupport;
|
||||
}
|
||||
@ -276,6 +282,18 @@ public class CopyOptions implements Serializable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否覆盖目标值,如果不覆盖,会先读取目标对象的值,非{@code null}则写,否则忽略。如果覆盖,则不判断直接写
|
||||
*
|
||||
* @param override 是否覆盖目标值
|
||||
* @return this
|
||||
* @since 5.7.17
|
||||
*/
|
||||
public CopyOptions setOverride(boolean override) {
|
||||
this.override = override;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得映射后的字段名<br>
|
||||
* 当非反向,则根据源字段名获取目标字段名,反之根据目标字段名获取源字段名。
|
||||
|
@ -1291,6 +1291,46 @@ public class CollUtil {
|
||||
return filter(collection, StrUtil::isNotBlank);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除集合中的多个元素,并将结果存放到指定的集合
|
||||
* 此方法直接修改原集合
|
||||
*
|
||||
* @param <T> 集合类型
|
||||
* @param <E> 集合元素类型
|
||||
* @param resultCollection 存放移除结果的集合
|
||||
* @param targetCollection 被操作移除元素的集合
|
||||
* @param predicate 用于是否移除判断的过滤器
|
||||
*/
|
||||
public static <T extends Collection<E>, E> T removeWithAddIf(T targetCollection, T resultCollection, Predicate<? super E> predicate) {
|
||||
Objects.requireNonNull(predicate);
|
||||
final Iterator<E> each = targetCollection.iterator();
|
||||
while (each.hasNext()) {
|
||||
E next = each.next();
|
||||
if (predicate.test(next)) {
|
||||
resultCollection.add(next);
|
||||
each.remove();
|
||||
}
|
||||
}
|
||||
return resultCollection;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除集合中的多个元素,并将结果存放到生成的新集合中后返回<br>
|
||||
* 此方法直接修改原集合
|
||||
*
|
||||
* @param <T> 集合类型
|
||||
* @param <E> 集合元素类型
|
||||
* @param targetCollection 被操作移除元素的集合
|
||||
* @param predicate 用于是否移除判断的过滤器
|
||||
* @return 移除结果的集合
|
||||
* @since 5.7.17
|
||||
*/
|
||||
public static <T extends Collection<E>, E> List<E> removeWithAddIf(T targetCollection, Predicate<? super E> predicate) {
|
||||
final List<E> removed = new ArrayList<>();
|
||||
removeWithAddIf(targetCollection, removed, predicate);
|
||||
return removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过Editor抽取集合元素中的某些值返回为新列表<br>
|
||||
* 例如提供的是一个Bean列表,通过Editor接口实现获取某个字段值,返回这个字段值组成的新列表
|
||||
|
@ -1,7 +1,7 @@
|
||||
package cn.hutool.core.collection;
|
||||
|
||||
/**
|
||||
* 集合相关工具类,包括数组,是{@link CollUtil} 的别名工具类类
|
||||
* 集合相关工具类,包括数组,是 {@link CollUtil} 的别名工具类
|
||||
*
|
||||
* @author xiaoleilu
|
||||
* @see CollUtil
|
||||
|
@ -56,7 +56,8 @@ public class RingIndexUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过cas操作 实现对指定值内的回环累加
|
||||
* 通过cas操作 实现对指定值内的回环累加<br>
|
||||
* 此方法一般用于大量数据完成回环累加(如数据库中的值大于int最大值)
|
||||
*
|
||||
* @param modulo 回环周期值
|
||||
* @param atomicLong 原子操作类
|
||||
|
@ -1624,7 +1624,9 @@ public class DateUtil extends CalendarUtil {
|
||||
*
|
||||
* @param date 日期
|
||||
* @return int
|
||||
* @deprecated 2022年后结果溢出,此方法废弃
|
||||
*/
|
||||
@Deprecated
|
||||
public static int toIntSecond(Date date) {
|
||||
return Integer.parseInt(DateUtil.format(date, "yyMMddHHmm"));
|
||||
}
|
||||
|
@ -253,8 +253,10 @@ public class Opt<T> {
|
||||
* String hutool = Opt.ofBlankAble("hutool").mapOrElse(String::toUpperCase, () -> Console.log("yes")).mapOrElse(String::intern, () -> Console.log("Value is not present~")).get();
|
||||
* }</pre>
|
||||
*
|
||||
* @param <U> map后新的类型
|
||||
* @param mapper 包裹里的值存在时的操作
|
||||
* @param emptyAction 包裹里的值不存在时的操作
|
||||
* @return 新的类型的Opt
|
||||
* @throws NullPointerException 如果包裹里的值存在时,执行的操作为 {@code null}, 或者包裹里的值不存在时的操作为 {@code null},则抛出{@code NPE}
|
||||
*/
|
||||
public <U> Opt<U> mapOrElse(Function<? super T, ? extends U> mapper, VoidFunc0 emptyAction) {
|
||||
|
@ -73,7 +73,7 @@ public class LambdaUtil {
|
||||
* @param func 需要解析的 lambda 对象
|
||||
* @return 返回解析后的结果
|
||||
*/
|
||||
private static <T> SerializedLambda _resolve(Serializable func) {
|
||||
private static SerializedLambda _resolve(Serializable func) {
|
||||
return cache.get(func.getClass().getName(), () -> ReflectUtil.invoke(func, "writeReplace"));
|
||||
}
|
||||
}
|
||||
|
@ -154,7 +154,7 @@ public class Calculator {
|
||||
* @param peek peek
|
||||
* @return 优先级
|
||||
*/
|
||||
public boolean compare(char cur, char peek) {// 如果是peek优先级高于cur,返回true,默认都是peek优先级要低
|
||||
private boolean compare(char cur, char peek) {// 如果是peek优先级高于cur,返回true,默认都是peek优先级要低
|
||||
final int offset = 40;
|
||||
if(cur == '%'){
|
||||
// %优先级最高
|
||||
@ -165,7 +165,7 @@ public class Calculator {
|
||||
peek = 47;
|
||||
}
|
||||
|
||||
return operatPriority[(peek) - offset] >= operatPriority[(cur) - offset];
|
||||
return operatPriority[peek - offset] >= operatPriority[cur - offset];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1979,19 +1979,32 @@ public class CharSequenceUtil {
|
||||
* 截取部分字符串,这里一个汉字的长度认为是2
|
||||
*
|
||||
* @param str 字符串
|
||||
* @param len 切割的位置
|
||||
* @param len bytes切割到的位置(包含)
|
||||
* @param suffix 切割后加上后缀
|
||||
* @return 切割后的字符串
|
||||
* @since 3.1.1
|
||||
*/
|
||||
public static String subPreGbk(CharSequence str, int len, CharSequence suffix) {
|
||||
return subPreGbk(str, len, true) + suffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* 截取部分字符串,这里一个汉字的长度认为是2<br>
|
||||
* 可以自定义halfUp,如len为10,如果截取后最后一个字符是半个字符,{@code true}表示保留,则长度是11,否则长度9
|
||||
*
|
||||
* @param str 字符串
|
||||
* @param len bytes切割到的位置(包含)
|
||||
* @param halfUp 遇到截取一半的GBK字符,是否保留。
|
||||
* @return 切割后的字符串
|
||||
* @since 5.7.17
|
||||
*/
|
||||
public static String subPreGbk(CharSequence str, int len, boolean halfUp) {
|
||||
if (isEmpty(str)) {
|
||||
return str(str);
|
||||
}
|
||||
|
||||
byte[] b;
|
||||
int counterOfDoubleByte = 0;
|
||||
b = str.toString().getBytes(CharsetUtil.CHARSET_GBK);
|
||||
final byte[] b = bytes(str, CharsetUtil.CHARSET_GBK);
|
||||
if (b.length <= len) {
|
||||
return str.toString();
|
||||
}
|
||||
@ -2002,9 +2015,13 @@ public class CharSequenceUtil {
|
||||
}
|
||||
|
||||
if (counterOfDoubleByte % 2 != 0) {
|
||||
len += 1;
|
||||
if(halfUp){
|
||||
len += 1;
|
||||
}else{
|
||||
len -= 1;
|
||||
}
|
||||
}
|
||||
return new String(b, 0, len, CharsetUtil.CHARSET_GBK) + suffix;
|
||||
return new String(b, 0, len, CharsetUtil.CHARSET_GBK);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2534,7 +2551,12 @@ public class CharSequenceUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较两个字符串是否相等。
|
||||
* 比较两个字符串是否相等,规则如下
|
||||
* <ul>
|
||||
* <li>str1和str2都为{@code null}</li>
|
||||
* <li>忽略大小写使用{@link String#equalsIgnoreCase(String)}判断相等</li>
|
||||
* <li>不忽略大小写使用{@link String#contentEquals(CharSequence)}判断相等</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param str1 要比较的字符串1
|
||||
* @param str2 要比较的字符串2
|
||||
|
@ -470,7 +470,6 @@ public class StrBuilder implements CharSequence, Appendable, Serializable {
|
||||
/**
|
||||
* 生成字符串
|
||||
*/
|
||||
@SuppressWarnings("NullableProblems")
|
||||
@Override
|
||||
public String toString() {
|
||||
return toString(false);
|
||||
|
@ -30,12 +30,15 @@ public class AsyncUtil {
|
||||
/**
|
||||
* 等待任意一个任务执行完毕,包裹了异常
|
||||
*
|
||||
* @param <T> 任务返回值类型
|
||||
* @param tasks 并行任务
|
||||
* @return 执行结束的任务返回值
|
||||
* @throws UndeclaredThrowableException 未受检异常
|
||||
*/
|
||||
public static void waitAny(CompletableFuture<?>... tasks) {
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T waitAny(CompletableFuture<?>... tasks) {
|
||||
try {
|
||||
CompletableFuture.anyOf(tasks).get();
|
||||
return (T) CompletableFuture.anyOf(tasks).get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new ThreadException(e);
|
||||
}
|
||||
@ -44,8 +47,8 @@ public class AsyncUtil {
|
||||
/**
|
||||
* 获取异步任务结果,包裹了异常
|
||||
*
|
||||
* @param task 异步任务
|
||||
* @param <T> 任务返回值类型
|
||||
* @param task 异步任务
|
||||
* @return 任务返回值
|
||||
* @throws RuntimeException 未受检异常
|
||||
*/
|
||||
|
@ -2,6 +2,9 @@ package cn.hutool.core.thread;
|
||||
|
||||
import cn.hutool.core.date.TimeInterval;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 高并发测试工具类
|
||||
*
|
||||
@ -12,11 +15,14 @@ import cn.hutool.core.date.TimeInterval;
|
||||
* ct.test(() -> {
|
||||
* // 需要并发测试的业务代码
|
||||
* });
|
||||
*
|
||||
* Console.log(ct.getInterval());
|
||||
* ct.close();
|
||||
* </pre>
|
||||
*
|
||||
* @author kwer
|
||||
*/
|
||||
public class ConcurrencyTester {
|
||||
public class ConcurrencyTester implements Closeable {
|
||||
private final SyncFinisher sf;
|
||||
private final TimeInterval timeInterval;
|
||||
private long interval;
|
||||
@ -31,7 +37,8 @@ public class ConcurrencyTester {
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行测试
|
||||
* 执行测试<br>
|
||||
* 执行测试后不会关闭线程池,可以调用{@link #close()}释放线程池
|
||||
*
|
||||
* @param runnable 要测试的内容
|
||||
* @return this
|
||||
@ -40,9 +47,9 @@ public class ConcurrencyTester {
|
||||
this.sf.clearWorker();
|
||||
|
||||
timeInterval.start();
|
||||
this.sf//
|
||||
.addRepeatWorker(runnable)//
|
||||
.setBeginAtSameTime(true)// 同时开始
|
||||
this.sf
|
||||
.addRepeatWorker(runnable)
|
||||
.setBeginAtSameTime(true)
|
||||
.start();
|
||||
|
||||
this.interval = timeInterval.interval();
|
||||
@ -74,4 +81,9 @@ public class ConcurrencyTester {
|
||||
public long getInterval() {
|
||||
return this.interval;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
this.sf.close();
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ package cn.hutool.core.thread;
|
||||
|
||||
import cn.hutool.core.exceptions.UtilException;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
@ -25,7 +27,7 @@ import java.util.concurrent.ExecutorService;
|
||||
* @author Looly
|
||||
* @since 4.1.15
|
||||
*/
|
||||
public class SyncFinisher {
|
||||
public class SyncFinisher implements Closeable {
|
||||
|
||||
private final Set<Worker> workers;
|
||||
private final int threadSize;
|
||||
@ -173,6 +175,11 @@ public class SyncFinisher {
|
||||
return endLatch.getCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* 工作者,为一个线程
|
||||
*
|
||||
|
@ -543,6 +543,6 @@ public class HashUtil {
|
||||
* @since 5.2.5
|
||||
*/
|
||||
public static long[] cityHash128(byte[] data, Number128 seed) {
|
||||
return CityHash.hash128(data).getLongArray();
|
||||
return CityHash.hash128(data, seed).getLongArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -154,7 +154,7 @@ public class IdcardUtil {
|
||||
* @return 是否有效
|
||||
*/
|
||||
public static boolean isValidCard(String idCard) {
|
||||
if(StrUtil.isBlank(idCard)){
|
||||
if (StrUtil.isBlank(idCard)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -201,6 +201,21 @@ public class IdcardUtil {
|
||||
* <li>余数只可能有0 1 2 3 4 5 6 7 8 9 10这11个数字。其分别对应的最后一位身份证的号码为1 0 X 9 8 7 6 5 4 3 2</li>
|
||||
* <li>通过上面得知如果余数是2,就会在身份证的第18位数字上出现罗马数字的Ⅹ。如果余数是10,身份证的最后一位号码就是2</li>
|
||||
* </ol>
|
||||
* <ol>
|
||||
* <li>香港人在大陆的身份证,【810000】开头;同样可以直接获取到 性别、出生日期</li>
|
||||
* <li>81000019980902013X: 文绎循 男 1998-09-02</li>
|
||||
* <li>810000201011210153: 辛烨 男 2010-11-21</li>
|
||||
* </ol>
|
||||
* <ol>
|
||||
* <li>澳门人在大陆的身份证,【820000】开头;同样可以直接获取到 性别、出生日期</li>
|
||||
* <li>820000200009100032: 黄敬杰 男 2000-09-10</li>
|
||||
* </ol>
|
||||
* <ol>
|
||||
* <li>台湾人在大陆的身份证,【830000】开头;同样可以直接获取到 性别、出生日期</li>
|
||||
* <li>830000200209060065: 王宜妃 女 2002-09-06</li>
|
||||
* <li>830000194609150010: 苏建文 男 1946-09-14</li>
|
||||
* <li>83000019810715006X: 刁婉琇 女 1981-07-15</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param idcard 待验证的身份证
|
||||
* @return 是否有效的18位身份证,忽略x的大小写
|
||||
@ -237,7 +252,7 @@ public class IdcardUtil {
|
||||
* <li>通过上面得知如果余数是2,就会在身份证的第18位数字上出现罗马数字的Ⅹ。如果余数是10,身份证的最后一位号码就是2</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param idcard 待验证的身份证
|
||||
* @param idcard 待验证的身份证
|
||||
* @param ignoreCase 是否忽略大小写。{@code true}则忽略X大小写,否则严格匹配大写。
|
||||
* @return 是否有效的18位身份证
|
||||
* @since 5.5.7
|
||||
@ -558,7 +573,7 @@ public class IdcardUtil {
|
||||
*/
|
||||
public static String getProvinceByIdCard(String idcard) {
|
||||
final String code = getProvinceCodeByIdCard(idcard);
|
||||
if(StrUtil.isNotBlank(code)){
|
||||
if (StrUtil.isNotBlank(code)) {
|
||||
return CITY_CODES.get(code);
|
||||
}
|
||||
return null;
|
||||
@ -599,7 +614,7 @@ public class IdcardUtil {
|
||||
* @return {@link Idcard}
|
||||
* @since 5.4.3
|
||||
*/
|
||||
public static Idcard getIdcardInfo(String idcard){
|
||||
public static Idcard getIdcardInfo(String idcard) {
|
||||
return new Idcard(idcard);
|
||||
}
|
||||
|
||||
@ -743,6 +758,7 @@ public class IdcardUtil {
|
||||
|
||||
/**
|
||||
* 获取年龄
|
||||
*
|
||||
* @return 年龄
|
||||
*/
|
||||
public int getAge() {
|
||||
|
@ -84,7 +84,7 @@ public class ReflectUtil {
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> Constructor<T>[] getConstructors(Class<T> beanClass) throws SecurityException {
|
||||
Assert.notNull(beanClass);
|
||||
return (Constructor<T>[]) CONSTRUCTORS_CACHE.get(beanClass, ()->getConstructorsDirectly(beanClass));
|
||||
return (Constructor<T>[]) CONSTRUCTORS_CACHE.get(beanClass, () -> getConstructorsDirectly(beanClass));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -173,7 +173,7 @@ public class ReflectUtil {
|
||||
*/
|
||||
public static Field[] getFields(Class<?> beanClass) throws SecurityException {
|
||||
Assert.notNull(beanClass);
|
||||
return FIELDS_CACHE.get(beanClass, ()->getFieldsDirectly(beanClass, true));
|
||||
return FIELDS_CACHE.get(beanClass, () -> getFieldsDirectly(beanClass, true));
|
||||
}
|
||||
|
||||
|
||||
@ -498,11 +498,9 @@ public class ReflectUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找指定方法 如果找不到对应的方法则返回{@code null}
|
||||
*
|
||||
* <p>
|
||||
* 此方法为精准获取方法名,即方法名和参数数量和类型必须一致,否则返回{@code null}。
|
||||
* </p>
|
||||
* 查找指定方法 如果找不到对应的方法则返回{@code null}<br>
|
||||
* 此方法为精准获取方法名,即方法名和参数数量和类型必须一致,否则返回{@code null}。<br>
|
||||
* 如果查找的方法有多个同参数类型重载,查找第一个找到的方法
|
||||
*
|
||||
* @param clazz 类,如果为{@code null}返回{@code null}
|
||||
* @param ignoreCase 是否忽略大小写
|
||||
@ -520,10 +518,11 @@ public class ReflectUtil {
|
||||
final Method[] methods = getMethods(clazz);
|
||||
if (ArrayUtil.isNotEmpty(methods)) {
|
||||
for (Method method : methods) {
|
||||
if (StrUtil.equals(methodName, method.getName(), ignoreCase)) {
|
||||
if (ClassUtil.isAllAssignableFrom(method.getParameterTypes(), paramTypes)) {
|
||||
return method;
|
||||
}
|
||||
if (StrUtil.equals(methodName, method.getName(), ignoreCase)
|
||||
&& ClassUtil.isAllAssignableFrom(method.getParameterTypes(), paramTypes)
|
||||
//排除桥接方法,pr#1965@Github
|
||||
&& false == method.isBridge()) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -586,7 +585,9 @@ public class ReflectUtil {
|
||||
final Method[] methods = getMethods(clazz);
|
||||
if (ArrayUtil.isNotEmpty(methods)) {
|
||||
for (Method method : methods) {
|
||||
if (StrUtil.equals(methodName, method.getName(), ignoreCase)) {
|
||||
if (StrUtil.equals(methodName, method.getName(), ignoreCase)
|
||||
// 排除桥接方法
|
||||
&& false == method.isBridge()) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
@ -635,7 +636,7 @@ public class ReflectUtil {
|
||||
*/
|
||||
public static Method[] getMethods(Class<?> beanClass) throws SecurityException {
|
||||
Assert.notNull(beanClass);
|
||||
return METHODS_CACHE.get(beanClass, ()-> getMethodsDirectly(beanClass, true));
|
||||
return METHODS_CACHE.get(beanClass, () -> getMethodsDirectly(beanClass, true));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,50 @@
|
||||
package cn.hutool.core.bean.copier;
|
||||
|
||||
import lombok.Data;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class BeanCopierTest {
|
||||
|
||||
/**
|
||||
* 测试在非覆盖模式下,目标对象有值则不覆盖
|
||||
*/
|
||||
@Test
|
||||
public void beanToBeanNotOverrideTest() {
|
||||
final A a = new A();
|
||||
a.setValue("123");
|
||||
final B b = new B();
|
||||
b.setValue("abc");
|
||||
|
||||
final BeanCopier<B> copier = BeanCopier.create(a, b, CopyOptions.create().setOverride(false));
|
||||
copier.copy();
|
||||
|
||||
Assert.assertEquals("abc", b.getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试在覆盖模式下,目标对象值被覆盖
|
||||
*/
|
||||
@Test
|
||||
public void beanToBeanOverrideTest() {
|
||||
final A a = new A();
|
||||
a.setValue("123");
|
||||
final B b = new B();
|
||||
b.setValue("abc");
|
||||
|
||||
final BeanCopier<B> copier = BeanCopier.create(a, b, CopyOptions.create());
|
||||
copier.copy();
|
||||
|
||||
Assert.assertEquals("123", b.getValue());
|
||||
}
|
||||
|
||||
@Data
|
||||
private static class A {
|
||||
private String value;
|
||||
}
|
||||
|
||||
@Data
|
||||
private static class B {
|
||||
private String value;
|
||||
}
|
||||
}
|
@ -39,6 +39,23 @@ public class CollUtilTest {
|
||||
Assert.assertFalse(CollUtil.contains(list, s -> s.startsWith("d")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveWithAddIf() {
|
||||
ArrayList<Integer> list = CollUtil.newArrayList(1, 2, 3);
|
||||
ArrayList<Integer> exceptRemovedList = CollUtil.newArrayList(2, 3);
|
||||
ArrayList<Integer> exceptResultList = CollUtil.newArrayList(1);
|
||||
|
||||
List<Integer> resultList = CollUtil.removeWithAddIf(list, ele -> 1 == ele);
|
||||
Assert.assertEquals(list, exceptRemovedList);
|
||||
Assert.assertEquals(resultList, exceptResultList);
|
||||
|
||||
list = CollUtil.newArrayList(1, 2, 3);
|
||||
resultList = new ArrayList<>();
|
||||
CollUtil.removeWithAddIf(list, resultList, ele -> 1 == ele);
|
||||
Assert.assertEquals(list, exceptRemovedList);
|
||||
Assert.assertEquals(resultList, exceptResultList);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPadLeft() {
|
||||
List<String> srcList = CollUtil.newArrayList();
|
||||
@ -285,6 +302,27 @@ public class CollUtilTest {
|
||||
Assert.assertEquals(CollUtil.newArrayList("b", "c"), filtered);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void filterRemoveTest() {
|
||||
ArrayList<String> list = CollUtil.newArrayList("a", "b", "c");
|
||||
|
||||
List<String> removed = new ArrayList<>();
|
||||
ArrayList<String> filtered = CollUtil.filter(list, t -> {
|
||||
if("a".equals(t)){
|
||||
removed.add(t);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
Assert.assertEquals(1, removed.size());
|
||||
Assert.assertEquals("a", removed.get(0));
|
||||
|
||||
// 原地过滤
|
||||
Assert.assertSame(list, filtered);
|
||||
Assert.assertEquals(CollUtil.newArrayList("b", "c"), filtered);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeNullTest() {
|
||||
ArrayList<String> list = CollUtil.newArrayList("a", "b", "c", null, "", " ");
|
||||
|
@ -1,6 +1,7 @@
|
||||
package cn.hutool.core.math;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
public class CalculatorTest {
|
||||
@ -28,4 +29,12 @@ public class CalculatorTest {
|
||||
final double conversion = Calculator.conversion("(88*66/23)%26+45%9");
|
||||
Assert.assertEquals((88D * 66 / 23) % 26, conversion, 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void conversationTest5(){
|
||||
// https://github.com/dromara/hutool/issues/1984
|
||||
final double conversion = Calculator.conversion("((1/1) / (1/1) -1) * 100");
|
||||
Assert.assertEquals((88D * 66 / 23) % 26, conversion, 2);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package cn.hutool.core.text;
|
||||
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
@ -65,4 +66,16 @@ public class CharSequenceUtilTest {
|
||||
index = CharSequenceUtil.indexOf("abc123", 'b', 0, 3);
|
||||
Assert.assertEquals(1, index);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subPreGbkTest(){
|
||||
// https://gitee.com/dromara/hutool/issues/I4JO2E
|
||||
String s = "华硕K42Intel酷睿i31代2G以下独立显卡不含机械硬盘固态硬盘120GB-192GB4GB-6GB";
|
||||
|
||||
String v = CharSequenceUtil.subPreGbk(s, 40, false);
|
||||
Assert.assertEquals(39, v.getBytes(CharsetUtil.CHARSET_GBK).length);
|
||||
|
||||
v = CharSequenceUtil.subPreGbk(s, 40, true);
|
||||
Assert.assertEquals(41, v.getBytes(CharsetUtil.CHARSET_GBK).length);
|
||||
}
|
||||
}
|
||||
|
@ -182,6 +182,12 @@ public class ArrayUtilTest {
|
||||
Assert.assertEquals(9, range[9]);
|
||||
}
|
||||
|
||||
@Test(expected = NegativeArraySizeException.class)
|
||||
public void rangeMinTest() {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
ArrayUtil.range(0, Integer.MIN_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maxTest() {
|
||||
int max = ArrayUtil.max(1, 2, 13, 4, 5);
|
||||
|
@ -103,6 +103,18 @@ public class IdcardUtilTest {
|
||||
Assert.assertTrue(isValidCard18);
|
||||
isValidCard18 = IdcardUtil.isValidCard18("33010219200403064X");
|
||||
Assert.assertTrue(isValidCard18);
|
||||
|
||||
// 香港人在大陆身份证
|
||||
isValidCard18 = IdcardUtil.isValidCard18("81000019980902013X");
|
||||
Assert.assertTrue(isValidCard18);
|
||||
|
||||
// 澳门人在大陆身份证
|
||||
isValidCard18 = IdcardUtil.isValidCard18("820000200009100032");
|
||||
Assert.assertTrue(isValidCard18);
|
||||
|
||||
// 台湾人在大陆身份证
|
||||
isValidCard18 = IdcardUtil.isValidCard18("830000200209060065");
|
||||
Assert.assertTrue(isValidCard18);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -81,7 +81,7 @@
|
||||
<dependency>
|
||||
<groupId>com.github.chris2018998</groupId>
|
||||
<artifactId>beecp</artifactId>
|
||||
<version>3.2.9</version>
|
||||
<version>3.3.0</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
|
@ -239,7 +239,9 @@ public abstract class AbstractDb implements Serializable {
|
||||
* @param paramsBatch 批量的参数
|
||||
* @return 每个SQL执行影响的行数
|
||||
* @throws SQLException SQL执行异常
|
||||
* @deprecated 编译器无法区分重载
|
||||
*/
|
||||
@Deprecated
|
||||
public int[] executeBatch(String sql, Object[]... paramsBatch) throws SQLException {
|
||||
Connection conn = null;
|
||||
try {
|
||||
|
@ -16,7 +16,7 @@ import java.util.Map;
|
||||
/**
|
||||
* SQL执行器,全部为静态方法,执行查询或非查询的SQL语句<br>
|
||||
* 此方法为JDBC的简单封装,与数据库类型无关
|
||||
*
|
||||
*
|
||||
* @author loolly
|
||||
*
|
||||
*/
|
||||
@ -26,7 +26,7 @@ public class SqlExecutor {
|
||||
* 执行非查询语句<br>
|
||||
* 语句包括 插入、更新、删除<br>
|
||||
* 此方法不会关闭Connection
|
||||
*
|
||||
*
|
||||
* @param conn 数据库连接对象
|
||||
* @param sql SQL,使用name做为占位符,例如:name
|
||||
* @param paramMap 参数Map
|
||||
@ -43,7 +43,7 @@ public class SqlExecutor {
|
||||
* 执行非查询语句<br>
|
||||
* 语句包括 插入、更新、删除<br>
|
||||
* 此方法不会关闭Connection
|
||||
*
|
||||
*
|
||||
* @param conn 数据库连接对象
|
||||
* @param sql SQL
|
||||
* @param params 参数
|
||||
@ -63,7 +63,7 @@ public class SqlExecutor {
|
||||
/**
|
||||
* 执行调用存储过程<br>
|
||||
* 此方法不会关闭Connection
|
||||
*
|
||||
*
|
||||
* @param conn 数据库连接对象
|
||||
* @param sql SQL
|
||||
* @param params 参数
|
||||
@ -83,7 +83,7 @@ public class SqlExecutor {
|
||||
/**
|
||||
* 执行调用存储过程<br>
|
||||
* 此方法不会关闭Connection
|
||||
*
|
||||
*
|
||||
* @param conn 数据库连接对象
|
||||
* @param sql SQL
|
||||
* @param params 参数
|
||||
@ -99,7 +99,7 @@ public class SqlExecutor {
|
||||
* 执行非查询语句,返回主键<br>
|
||||
* 发查询语句包括 插入、更新、删除<br>
|
||||
* 此方法不会关闭Connection
|
||||
*
|
||||
*
|
||||
* @param conn 数据库连接对象
|
||||
* @param sql SQL
|
||||
* @param paramMap 参数Map
|
||||
@ -116,7 +116,7 @@ public class SqlExecutor {
|
||||
* 执行非查询语句,返回主键<br>
|
||||
* 发查询语句包括 插入、更新、删除<br>
|
||||
* 此方法不会关闭Connection
|
||||
*
|
||||
*
|
||||
* @param conn 数据库连接对象
|
||||
* @param sql SQL
|
||||
* @param params 参数
|
||||
@ -148,22 +148,24 @@ public class SqlExecutor {
|
||||
* 批量执行非查询语句<br>
|
||||
* 语句包括 插入、更新、删除<br>
|
||||
* 此方法不会关闭Connection
|
||||
*
|
||||
*
|
||||
* @param conn 数据库连接对象
|
||||
* @param sql SQL
|
||||
* @param paramsBatch 批量的参数
|
||||
* @return 每个SQL执行影响的行数
|
||||
* @throws SQLException SQL执行异常
|
||||
* @deprecated 重载导致编译器无法区分
|
||||
*/
|
||||
@Deprecated
|
||||
public static int[] executeBatch(Connection conn, String sql, Object[]... paramsBatch) throws SQLException {
|
||||
return executeBatch(conn, sql, new ArrayIter<>(paramsBatch));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 批量执行非查询语句<br>
|
||||
* 语句包括 插入、更新、删除<br>
|
||||
* 此方法不会关闭Connection
|
||||
*
|
||||
*
|
||||
* @param conn 数据库连接对象
|
||||
* @param sql SQL
|
||||
* @param paramsBatch 批量的参数
|
||||
@ -179,12 +181,12 @@ public class SqlExecutor {
|
||||
DbUtil.close(ps);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 批量执行非查询语句<br>
|
||||
* 语句包括 插入、更新、删除<br>
|
||||
* 此方法不会关闭Connection
|
||||
*
|
||||
*
|
||||
* @param conn 数据库连接对象
|
||||
* @param sqls SQL列表
|
||||
* @return 每个SQL执行影响的行数
|
||||
@ -194,12 +196,12 @@ public class SqlExecutor {
|
||||
public static int[] executeBatch(Connection conn, String... sqls) throws SQLException {
|
||||
return executeBatch(conn, new ArrayIter<>(sqls));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 批量执行非查询语句<br>
|
||||
* 语句包括 插入、更新、删除<br>
|
||||
* 此方法不会关闭Connection
|
||||
*
|
||||
*
|
||||
* @param conn 数据库连接对象
|
||||
* @param sqls SQL列表
|
||||
* @return 每个SQL执行影响的行数
|
||||
@ -222,7 +224,7 @@ public class SqlExecutor {
|
||||
/**
|
||||
* 执行查询语句,例如:select * from table where field1=:name1 <br>
|
||||
* 此方法不会关闭Connection
|
||||
*
|
||||
*
|
||||
* @param <T> 处理结果类型
|
||||
* @param conn 数据库连接对象
|
||||
* @param sql 查询语句,使用参数名占位符,例如:name
|
||||
@ -240,7 +242,7 @@ public class SqlExecutor {
|
||||
/**
|
||||
* 执行查询语句<br>
|
||||
* 此方法不会关闭Connection
|
||||
*
|
||||
*
|
||||
* @param <T> 处理结果类型
|
||||
* @param conn 数据库连接对象
|
||||
* @param sql 查询语句
|
||||
@ -282,7 +284,7 @@ public class SqlExecutor {
|
||||
* executeUpdate 的返回值是一个整数(int),指示受影响的行数(即更新计数)。<br>
|
||||
* 对于 CREATE TABLE 或 DROP TABLE 等不操作行的语句,executeUpdate 的返回值总为零。<br>
|
||||
* 此方法不会关闭PreparedStatement
|
||||
*
|
||||
*
|
||||
* @param ps PreparedStatement对象
|
||||
* @param params 参数
|
||||
* @return 影响的行数
|
||||
@ -297,7 +299,7 @@ public class SqlExecutor {
|
||||
* 可用于执行任何SQL语句,返回一个boolean值,表明执行该SQL语句是否返回了ResultSet。<br>
|
||||
* 如果执行后第一个结果是ResultSet,则返回true,否则返回false。<br>
|
||||
* 此方法不会关闭PreparedStatement
|
||||
*
|
||||
*
|
||||
* @param ps PreparedStatement对象
|
||||
* @param params 参数
|
||||
* @return 如果执行后第一个结果是ResultSet,则返回true,否则返回false。
|
||||
@ -311,7 +313,7 @@ public class SqlExecutor {
|
||||
/**
|
||||
* 执行查询语句<br>
|
||||
* 此方法不会关闭PreparedStatement
|
||||
*
|
||||
*
|
||||
* @param <T> 处理结果类型
|
||||
* @param ps PreparedStatement
|
||||
* @param rsh 结果集处理对象
|
||||
@ -326,7 +328,7 @@ public class SqlExecutor {
|
||||
|
||||
/**
|
||||
* 执行查询语句并关闭PreparedStatement
|
||||
*
|
||||
*
|
||||
* @param <T> 处理结果类型
|
||||
* @param ps PreparedStatement
|
||||
* @param rsh 结果集处理对象
|
||||
@ -345,7 +347,7 @@ public class SqlExecutor {
|
||||
// -------------------------------------------------------------------------------------------------------------------------------- Private method start
|
||||
/**
|
||||
* 执行查询
|
||||
*
|
||||
*
|
||||
* @param ps {@link PreparedStatement}
|
||||
* @param rsh 结果集处理对象
|
||||
* @return 结果对象
|
||||
|
@ -241,7 +241,7 @@
|
||||
<dependency>
|
||||
<groupId>org.apache.lucene</groupId>
|
||||
<artifactId>lucene-analyzers-smartcn</artifactId>
|
||||
<version>8.10.1</version>
|
||||
<version>8.11.0</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@ -384,7 +384,7 @@
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.2.6</version>
|
||||
<version>1.2.7</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
@ -432,7 +432,7 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-expression</artifactId>
|
||||
<version>5.3.12</version>
|
||||
<version>5.3.13</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
@ -7,7 +7,6 @@ import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.io.StreamProgress;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.ReUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
@ -434,7 +433,10 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
|
||||
fileName = StrUtil.subSuf(path, path.lastIndexOf('/') + 1);
|
||||
if (StrUtil.isBlank(fileName)) {
|
||||
// 编码后的路径做为文件名
|
||||
fileName = URLUtil.encodeQuery(path, CharsetUtil.CHARSET_UTF_8);
|
||||
fileName = URLUtil.encodeQuery(path, charset);
|
||||
} else {
|
||||
// issue#I4K0FS@Gitee
|
||||
fileName = URLUtil.decode(fileName, charset);
|
||||
}
|
||||
}
|
||||
return FileUtil.file(targetFileOrDir, fileName);
|
||||
|
@ -539,6 +539,9 @@ public class HttpUtil {
|
||||
* @since 4.5.2
|
||||
*/
|
||||
public static String normalizeParams(String paramPart, Charset charset) {
|
||||
if(StrUtil.isEmpty(paramPart)){
|
||||
return paramPart;
|
||||
}
|
||||
final StrBuilder builder = StrBuilder.create(paramPart.length() + 16);
|
||||
final int len = paramPart.length();
|
||||
String name = null;
|
||||
|
@ -338,7 +338,7 @@ public class SoapClient extends HttpBase<SoapClient> {
|
||||
*/
|
||||
public SoapClient setMethod(QName name, Map<String, Object> params, boolean useMethodPrefix) {
|
||||
setMethod(name);
|
||||
final String prefix = name.getPrefix();
|
||||
final String prefix = useMethodPrefix ? name.getPrefix() : null;
|
||||
final SOAPBodyElement methodEle = this.methodEle;
|
||||
for (Entry<String, Object> entry : MapUtil.wrap(params)) {
|
||||
setParam(methodEle, entry.getKey(), entry.getValue(), prefix);
|
||||
@ -618,7 +618,7 @@ public class SoapClient extends HttpBase<SoapClient> {
|
||||
* @param ele 方法节点
|
||||
* @param name 参数名
|
||||
* @param value 参数值
|
||||
* @param prefix 命名空间前缀
|
||||
* @param prefix 命名空间前缀, {@code null}表示不使用前缀
|
||||
* @return {@link SOAPElement}子节点
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
|
@ -302,6 +302,12 @@ public class HttpUtilTest {
|
||||
Assert.assertEquals("%E5%8F%82%E6%95%B0", encodeResult);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void normalizeBlankParamsTest() {
|
||||
String encodeResult = HttpUtil.normalizeParams("", CharsetUtil.CHARSET_UTF_8);
|
||||
Assert.assertEquals("", encodeResult);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getMimeTypeTest() {
|
||||
String mimeType = HttpUtil.getMimeType("aaa.aaa");
|
||||
|
@ -4,6 +4,7 @@ import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.poi.excel.ExcelDateUtil;
|
||||
import cn.hutool.poi.excel.cell.CellValue;
|
||||
import java.util.Date;
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
import org.apache.poi.ss.usermodel.CellStyle;
|
||||
import org.apache.poi.ss.util.NumberToTextConverter;
|
||||
@ -24,7 +25,7 @@ public class NumericCellValue implements CellValue<Object> {
|
||||
*
|
||||
* @param cell {@link Cell}
|
||||
*/
|
||||
public NumericCellValue(Cell cell){
|
||||
public NumericCellValue(Cell cell) {
|
||||
this.cell = cell;
|
||||
}
|
||||
|
||||
@ -36,8 +37,13 @@ public class NumericCellValue implements CellValue<Object> {
|
||||
if (null != style) {
|
||||
// 判断是否为日期
|
||||
if (ExcelDateUtil.isDateFormat(cell)) {
|
||||
// 1899年写入会导致数据错乱,读取到1899年证明这个单元格的信息不关注年月日
|
||||
Date dateCellValue = cell.getDateCellValue();
|
||||
if ("1899".equals(DateUtil.format(dateCellValue, "yyyy"))) {
|
||||
return DateUtil.format(dateCellValue, style.getDataFormatString());
|
||||
}
|
||||
// 使用Hutool的DateTime包装
|
||||
return DateUtil.date(cell.getDateCellValue());
|
||||
return DateUtil.date(dateCellValue);
|
||||
}
|
||||
|
||||
final String format = style.getDataFormatString();
|
||||
|
@ -56,7 +56,7 @@ public class Excel07SaxReader implements ExcelSaxReader<Excel07SaxReader> {
|
||||
|
||||
@Override
|
||||
public Excel07SaxReader read(File file, String idOrRidOrSheetName) throws POIException {
|
||||
try (OPCPackage open = OPCPackage.open(file, PackageAccess.READ);){
|
||||
try (OPCPackage open = OPCPackage.open(file, PackageAccess.READ)){
|
||||
return read(open, idOrRidOrSheetName);
|
||||
} catch (InvalidFormatException | IOException e) {
|
||||
throw new POIException(e);
|
||||
@ -202,7 +202,7 @@ public class Excel07SaxReader implements ExcelSaxReader<Excel07SaxReader> {
|
||||
}
|
||||
|
||||
// sheetIndex需转换为rid
|
||||
final SheetRidReader ridReader = new SheetRidReader().read(xssfReader);
|
||||
final SheetRidReader ridReader = SheetRidReader.parse(xssfReader);
|
||||
|
||||
if (StrUtil.startWithIgnoreCase(idOrRidOrSheetName, SHEET_NAME_PREFIX)) {
|
||||
// name:开头的被认为是sheet名称直接处理
|
||||
|
@ -1,6 +1,7 @@
|
||||
package cn.hutool.poi.excel.sax;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
@ -13,6 +14,7 @@ import org.xml.sax.helpers.DefaultHandler;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@ -32,6 +34,17 @@ import java.util.Map;
|
||||
*/
|
||||
public class SheetRidReader extends DefaultHandler {
|
||||
|
||||
/**
|
||||
* 从{@link XSSFReader}中解析sheet名、sheet id等相关信息
|
||||
*
|
||||
* @param reader {@link XSSFReader}
|
||||
* @return SheetRidReader
|
||||
* @since 5.7.17
|
||||
*/
|
||||
public static SheetRidReader parse(XSSFReader reader) {
|
||||
return new SheetRidReader().read(reader);
|
||||
}
|
||||
|
||||
private final static String TAG_NAME = "sheet";
|
||||
private final static String RID_ATTR = "r:id";
|
||||
private final static String SHEET_ID_ATTR = "sheetId";
|
||||
@ -137,6 +150,16 @@ public class SheetRidReader extends DefaultHandler {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有sheet名称
|
||||
*
|
||||
* @return sheet名称
|
||||
* @since 5.7.17
|
||||
*/
|
||||
public List<String> getSheetNames() {
|
||||
return ListUtil.toList(this.NAME_RID_MAP.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startElement(String uri, String localName, String qName, Attributes attributes) {
|
||||
if (TAG_NAME.equalsIgnoreCase(localName)) {
|
||||
|
@ -0,0 +1,26 @@
|
||||
package cn.hutool.poi.excel;
|
||||
|
||||
import cn.hutool.poi.excel.cell.values.NumericCellValue;
|
||||
import java.util.Date;
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
import org.junit.Test;
|
||||
|
||||
public class NumericCellValueTest {
|
||||
|
||||
@Test
|
||||
public void writeTest() {
|
||||
final ExcelReader reader = ExcelUtil.getReader("1899bug_demo.xlsx");
|
||||
ExcelWriter writer = ExcelUtil.getWriter("1899bug_write.xlsx");
|
||||
Cell cell = reader.getCell(0, 0);
|
||||
// 直接取值
|
||||
// 和CellUtil.getCellValue(org.apache.poi.ss.usermodel.Cell)方法的结果一样
|
||||
// 1899-12-31 04:39:00
|
||||
Date cellValue = cell.getDateCellValue();
|
||||
// 将这个值写入EXCEL中自定义样式的单元格,结果会是-1
|
||||
writer.writeCellValue(0, 0, cellValue);
|
||||
// 修改后的写入,单元格内容正常
|
||||
writer.writeCellValue(1, 0, new NumericCellValue(cell).getValue());
|
||||
writer.close();
|
||||
reader.close();
|
||||
}
|
||||
}
|
BIN
hutool-poi/src/test/resources/1899bug_demo.xlsx
Normal file
BIN
hutool-poi/src/test/resources/1899bug_demo.xlsx
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user