mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-05-09 23:51:34 +08:00
🤤release 5.8.33
This commit is contained in:
parent
d57de39963
commit
226d4bd205
19
CHANGELOG.md
19
CHANGELOG.md
@ -1,6 +1,25 @@
|
||||
|
||||
# 🚀Changelog
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
# 5.8.34(2024-11-25)
|
||||
|
||||
### 🐣新特性
|
||||
* 【http 】 增加Windows微信浏览器识别(issue#IB3SJF@Gitee)
|
||||
* 【core 】 ZipUtil.unzip增加编码容错(issue#I3UZ28@Gitee)
|
||||
* 【core 】 Calculator兼容`x`字符作为乘号(issue#3787@Github)
|
||||
* 【poi 】 Excel07SaxReader中,对于小数类型,增加精度判断(issue#IB0EJ9@Gitee)
|
||||
* 【extra 】 SpringUtil增加getBean重载(issue#3779@Github)
|
||||
* 【core 】 DataSizeUtil 新增format方法(issue#IB6UUX@Gitee)
|
||||
|
||||
### 🐞Bug修复
|
||||
* 【core 】 修复DateUtil.rangeToList中step小于等于0时无限循环问题(issue#3783@Github)
|
||||
* 【cron 】 修复cron模块依赖log模块问题
|
||||
* 【extra 】 修复MailUtil发送html格式邮件无法正常展示图片问题(pr#1279@Gitee)
|
||||
* 【core 】 【可能的向下兼容问题】修复双引号转义符转义错误问题,修改规则后,对非闭合双引号字段的策略变更,如"aa,则被识别为aa(issue#IB5UQ8@Gitee)
|
||||
* 【extra 】 修复Sftp中传入Session重连时逻辑错误问题(issue#IB69U8@Gitee)
|
||||
* 【json 】 修复JSONUtil.toBean()中将JSON数组字符串转Map对象返回错误问题(issue#3795@Github)
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
# 5.8.33(2024-11-05)
|
||||
|
||||
|
@ -40,7 +40,7 @@
|
||||
|
||||
<br/>
|
||||
<p align="center">
|
||||
<a href="https://qm.qq.com/cgi-bin/qm/qr?k=QtsqXLkHpLjE99tkre19j6pjPMhSay1a&jump_from=webapi">
|
||||
<a href="https://qm.qq.com/q/I7pPlTzCa4">
|
||||
<img src="https://img.shields.io/badge/QQ%E7%BE%A4%E2%91%A6-715292493-orange"/></a>
|
||||
</p>
|
||||
|
||||
@ -150,18 +150,18 @@ We provide the T-Shirt and Sweater with Hutool Logo, please visit the shop:
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.8.33</version>
|
||||
<version>5.8.34</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### 🍐Gradle
|
||||
```
|
||||
implementation 'cn.hutool:hutool-all:5.8.33'
|
||||
implementation 'cn.hutool:hutool-all:5.8.34'
|
||||
```
|
||||
|
||||
## 📥Download
|
||||
|
||||
- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.33/)
|
||||
- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.34/)
|
||||
|
||||
> 🔔️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.
|
||||
|
@ -40,7 +40,7 @@
|
||||
|
||||
<br/>
|
||||
<p align="center">
|
||||
<a href="https://qm.qq.com/cgi-bin/qm/qr?k=QtsqXLkHpLjE99tkre19j6pjPMhSay1a&jump_from=webapi">
|
||||
<a href="https://qm.qq.com/q/I7pPlTzCa4">
|
||||
<img src="https://img.shields.io/badge/QQ%E7%BE%A4%E2%91%A6-715292493-orange"/></a>
|
||||
</p>
|
||||
|
||||
@ -143,20 +143,20 @@ Hutool = Hu + tool,是原公司项目底层代码剥离后的开源库,“Hu
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.8.33</version>
|
||||
<version>5.8.34</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### 🍐Gradle
|
||||
```
|
||||
implementation 'cn.hutool:hutool-all:5.8.33'
|
||||
implementation 'cn.hutool:hutool-all:5.8.34'
|
||||
```
|
||||
|
||||
### 📥下载jar
|
||||
|
||||
点击以下链接,下载`hutool-all-X.X.X.jar`即可:
|
||||
|
||||
- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.33/)
|
||||
- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.34/)
|
||||
|
||||
> 🔔️注意
|
||||
> Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。
|
||||
|
@ -1 +1 @@
|
||||
5.8.33
|
||||
5.8.34
|
||||
|
@ -1 +1 @@
|
||||
var version = '5.8.33'
|
||||
var version = '5.8.34'
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.33</version>
|
||||
<version>5.8.34-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-all</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.33</version>
|
||||
<version>5.8.34-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-aop</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.33</version>
|
||||
<version>5.8.34-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-bloomFilter</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.33</version>
|
||||
<version>5.8.34-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-bom</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.33</version>
|
||||
<version>5.8.34-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-cache</artifactId>
|
||||
|
@ -1,7 +1,6 @@
|
||||
package cn.hutool.cache.impl;
|
||||
|
||||
import cn.hutool.core.collection.CopiedIter;
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.locks.StampedLock;
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.33</version>
|
||||
<version>5.8.34-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-captcha</artifactId>
|
||||
|
@ -4,7 +4,6 @@ import cn.hutool.captcha.generator.CodeGenerator;
|
||||
import cn.hutool.captcha.generator.RandomGenerator;
|
||||
import cn.hutool.core.img.GraphicsUtil;
|
||||
import cn.hutool.core.img.ImgUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
|
||||
import java.awt.*;
|
||||
|
@ -5,7 +5,6 @@ import cn.hutool.captcha.generator.CodeGenerator;
|
||||
import cn.hutool.captcha.generator.RandomGenerator;
|
||||
import cn.hutool.core.img.ImgUtil;
|
||||
import cn.hutool.core.img.gif.AnimatedGifEncoder;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
|
||||
import java.awt.AlphaComposite;
|
||||
|
@ -4,7 +4,6 @@ import cn.hutool.captcha.generator.CodeGenerator;
|
||||
import cn.hutool.captcha.generator.RandomGenerator;
|
||||
import cn.hutool.core.img.GraphicsUtil;
|
||||
import cn.hutool.core.img.ImgUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
|
||||
import java.awt.*;
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.33</version>
|
||||
<version>5.8.34-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-core</artifactId>
|
||||
|
@ -1,13 +1,10 @@
|
||||
package cn.hutool.core.comparator;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.lang.Version;
|
||||
import cn.hutool.core.util.*;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 版本比较器<br>
|
||||
|
@ -1,6 +1,5 @@
|
||||
package cn.hutool.core.convert;
|
||||
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
|
@ -74,6 +74,11 @@ public class MapConverter extends AbstractConverter<Map<?, ?>> {
|
||||
}
|
||||
convertMapToMap((Map) value, map);
|
||||
} else if (BeanUtil.isBean(value.getClass())) {
|
||||
if(value.getClass().getName().equals("cn.hutool.json.JSONArray")){
|
||||
// issue#3795 增加JSONArray转Map错误检查
|
||||
throw new UnsupportedOperationException(StrUtil.format("Unsupported {} to Map.", value.getClass().getName()));
|
||||
}
|
||||
|
||||
map = BeanUtil.beanToMap(value);
|
||||
// 二次转换,转换键值类型
|
||||
map = convertInternal(map);
|
||||
|
@ -48,6 +48,10 @@ public class DateRange extends Range<DateTime> {
|
||||
*/
|
||||
public DateRange(Date start, Date end, DateField unit, int step, boolean isIncludeStart, boolean isIncludeEnd) {
|
||||
super(DateUtil.date(start), DateUtil.date(end), (current, end1, index) -> {
|
||||
if(step <= 0){
|
||||
// issue#3783
|
||||
return null;
|
||||
}
|
||||
final DateTime dt = DateUtil.date(start).offsetNew(unit, (index + 1) * step);
|
||||
if (dt.isAfter(end1)) {
|
||||
return null;
|
||||
|
@ -1,5 +1,7 @@
|
||||
package cn.hutool.core.io.unit;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
/**
|
||||
@ -35,4 +37,20 @@ public class DataSizeUtil {
|
||||
return new DecimalFormat("#,##0.##")
|
||||
.format(size / Math.pow(1024, digitGroups)) + " " + DataUnit.UNIT_NAMES[digitGroups];
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据单位,将文件大小转换为对应单位的大小
|
||||
*
|
||||
* @param size 文件大小
|
||||
* @param fileDataUnit 单位
|
||||
* @return 大小
|
||||
* @since 5.8.34
|
||||
*/
|
||||
public static String format(Long size, DataUnit fileDataUnit){
|
||||
if (size <= 0) {
|
||||
return "0";
|
||||
}
|
||||
int digitGroups = ArrayUtil.indexOf(DataUnit.UNIT_NAMES,fileDataUnit.getSuffix());
|
||||
return new DecimalFormat("##0.##").format(size / Math.pow(1024, digitGroups)) + " " + DataUnit.UNIT_NAMES[digitGroups];
|
||||
}
|
||||
}
|
||||
|
@ -44,10 +44,12 @@ public enum DataUnit {
|
||||
*/
|
||||
TERABYTES("TB", DataSize.ofTerabytes(1));
|
||||
|
||||
/**
|
||||
* 单位后缀
|
||||
*/
|
||||
public static final String[] UNIT_NAMES = new String[]{"B", "KB", "MB", "GB", "TB", "PB", "EB"};
|
||||
|
||||
private final String suffix;
|
||||
|
||||
private final DataSize size;
|
||||
|
||||
|
||||
@ -56,6 +58,16 @@ public enum DataUnit {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 单位后缀
|
||||
*
|
||||
* @return 单位后缀
|
||||
* @since 5.8.34
|
||||
*/
|
||||
public String getSuffix() {
|
||||
return this.suffix;
|
||||
}
|
||||
|
||||
DataSize size() {
|
||||
return this.size;
|
||||
}
|
||||
@ -76,5 +88,4 @@ public enum DataUnit {
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown data unit suffix '" + suffix + "'");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package cn.hutool.core.lang;
|
||||
|
||||
import cn.hutool.core.date.SystemClock;
|
||||
import cn.hutool.core.lang.id.IdConstants;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package cn.hutool.core.math;
|
||||
|
||||
import cn.hutool.core.util.CharUtil;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
@ -192,6 +193,9 @@ public class Calculator {
|
||||
arr[i] = '~';
|
||||
}
|
||||
}
|
||||
} else if(CharUtil.equals(arr[i], 'x', true)){
|
||||
// issue#3787 x转换为*
|
||||
arr[i] = '*';
|
||||
}
|
||||
}
|
||||
if (arr[0] == '~' && (arr.length > 1 && arr[1] == '(')) {
|
||||
|
@ -1,24 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2024 Hutool Team and hutool.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cn.hutool.core.text.csv;
|
||||
|
||||
import cn.hutool.core.collection.ComputeIter;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.text.StrBuilder;
|
||||
import cn.hutool.core.util.CharUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* CSV行解析器,参考:FastCSV
|
||||
@ -30,10 +39,8 @@ public final class CsvParser extends ComputeIter<CsvRow> implements Closeable, S
|
||||
|
||||
private static final int DEFAULT_ROW_CAPACITY = 10;
|
||||
|
||||
private final Reader reader;
|
||||
private final CsvReadConfig config;
|
||||
|
||||
private final Buffer buf = new Buffer(IoUtil.DEFAULT_LARGE_BUFFER_SIZE);
|
||||
private final CsvTokener tokener;
|
||||
/**
|
||||
* 前一个特殊分界字符
|
||||
*/
|
||||
@ -45,7 +52,7 @@ public final class CsvParser extends ComputeIter<CsvRow> implements Closeable, S
|
||||
/**
|
||||
* 当前读取字段
|
||||
*/
|
||||
private final StrBuilder currentField = new StrBuilder(512);
|
||||
private final StringBuilder currentField = new StringBuilder(512);
|
||||
|
||||
/**
|
||||
* 标题行
|
||||
@ -78,9 +85,9 @@ public final class CsvParser extends ComputeIter<CsvRow> implements Closeable, S
|
||||
* @param reader Reader
|
||||
* @param config 配置,null则为默认配置
|
||||
*/
|
||||
public CsvParser(final Reader reader, CsvReadConfig config) {
|
||||
this.reader = Objects.requireNonNull(reader, "reader must not be null");
|
||||
this.config = ObjectUtil.defaultIfNull(config, CsvReadConfig::defaultConfig);
|
||||
public CsvParser(final Reader reader, final CsvReadConfig config) {
|
||||
this.config = ObjUtil.defaultIfNull(config, CsvReadConfig::defaultConfig);
|
||||
this.tokener = new CsvTokener(reader);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -96,7 +103,7 @@ public final class CsvParser extends ComputeIter<CsvRow> implements Closeable, S
|
||||
if (lineNo < config.beginLineNo) {
|
||||
throw new IllegalStateException("No header available - call nextRow() first");
|
||||
}
|
||||
return header.fields;
|
||||
return header.getRawList();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -107,13 +114,13 @@ public final class CsvParser extends ComputeIter<CsvRow> implements Closeable, S
|
||||
/**
|
||||
* 读取下一行数据
|
||||
*
|
||||
* @return CsvRow
|
||||
* @return CsvRow,{@code null}表示
|
||||
* @throws IORuntimeException IO读取异常
|
||||
*/
|
||||
public CsvRow nextRow() throws IORuntimeException {
|
||||
List<String> currentFields;
|
||||
int fieldCount;
|
||||
while (false == finished) {
|
||||
while (!finished) {
|
||||
currentFields = readLine();
|
||||
fieldCount = currentFields.size();
|
||||
if (fieldCount < 1) {
|
||||
@ -122,11 +129,11 @@ public final class CsvParser extends ComputeIter<CsvRow> implements Closeable, S
|
||||
}
|
||||
|
||||
// 读取范围校验
|
||||
if(lineNo < config.beginLineNo){
|
||||
if (lineNo < config.beginLineNo) {
|
||||
// 未达到读取起始行,继续
|
||||
continue;
|
||||
}
|
||||
if(lineNo > config.endLineNo){
|
||||
if (lineNo > config.endLineNo) {
|
||||
// 超出结束行,读取结束
|
||||
break;
|
||||
}
|
||||
@ -175,9 +182,9 @@ public final class CsvParser extends ComputeIter<CsvRow> implements Closeable, S
|
||||
String field = currentFields.get(i);
|
||||
if (MapUtil.isNotEmpty(this.config.headerAlias)) {
|
||||
// 自定义别名
|
||||
field = ObjectUtil.defaultIfNull(this.config.headerAlias.get(field), field);
|
||||
field = ObjUtil.defaultIfNull(this.config.headerAlias.get(field), field);
|
||||
}
|
||||
if (StrUtil.isNotEmpty(field) && false == localHeaderMap.containsKey(field)) {
|
||||
if (StrUtil.isNotEmpty(field) && !localHeaderMap.containsKey(field)) {
|
||||
localHeaderMap.put(field, i);
|
||||
}
|
||||
}
|
||||
@ -199,70 +206,67 @@ public final class CsvParser extends ComputeIter<CsvRow> implements Closeable, S
|
||||
private List<String> readLine() throws IORuntimeException {
|
||||
// 矫正行号
|
||||
// 当一行内容包含多行数据时,记录首行行号,但是读取下一行时,需要把多行内容的行数加上
|
||||
if(inQuotesLineCount > 0){
|
||||
if (inQuotesLineCount > 0) {
|
||||
this.lineNo += this.inQuotesLineCount;
|
||||
this.inQuotesLineCount = 0;
|
||||
}
|
||||
|
||||
final List<String> currentFields = new ArrayList<>(maxFieldCount > 0 ? maxFieldCount : DEFAULT_ROW_CAPACITY);
|
||||
|
||||
final StrBuilder currentField = this.currentField;
|
||||
final Buffer buf = this.buf;
|
||||
final StringBuilder currentField = this.currentField;
|
||||
int preChar = this.preChar;//前一个特殊分界字符
|
||||
int copyLen = 0; //拷贝长度
|
||||
boolean inComment = false;
|
||||
|
||||
int c;
|
||||
while (true) {
|
||||
if (false == buf.hasRemaining()) {
|
||||
// 此Buffer读取结束,开始读取下一段
|
||||
if (copyLen > 0) {
|
||||
buf.appendTo(currentField, copyLen);
|
||||
// 此处无需mark,read方法会重置mark
|
||||
c = tokener.next();
|
||||
if(c < 0){
|
||||
if (currentField.length() > 0 || preChar == config.fieldSeparator) {
|
||||
if(this.inQuotes){
|
||||
// 未闭合的文本包装,在末尾补充包装符
|
||||
currentField.append(config.textDelimiter);
|
||||
}
|
||||
if (buf.read(this.reader) < 0) {
|
||||
// CSV读取结束
|
||||
finished = true;
|
||||
|
||||
if (currentField.hasContent() || preChar == config.fieldSeparator) {
|
||||
//剩余部分作为一个字段
|
||||
addField(currentFields, currentField.toStringAndReset());
|
||||
addField(currentFields, currentField.toString());
|
||||
currentField.setLength(0);
|
||||
}
|
||||
// 读取结束
|
||||
this.finished = true;
|
||||
break;
|
||||
}
|
||||
|
||||
//重置
|
||||
copyLen = 0;
|
||||
}
|
||||
|
||||
final char c = buf.get();
|
||||
|
||||
// 注释行标记
|
||||
if(preChar < 0 || preChar == CharUtil.CR || preChar == CharUtil.LF){
|
||||
if (preChar < 0 || preChar == CharUtil.CR || preChar == CharUtil.LF) {
|
||||
// 判断行首字符为指定注释字符的注释开始,直到遇到换行符
|
||||
// 行首分两种,1是preChar < 0表示文本开始,2是换行符后紧跟就是下一行的开始
|
||||
// issue#IA8WE0 如果注释符出现在包装符内,被认为是普通字符
|
||||
if((false == inQuotes) && null != this.config.commentCharacter && c == this.config.commentCharacter){
|
||||
if (!inQuotes && null != this.config.commentCharacter && c == this.config.commentCharacter) {
|
||||
inComment = true;
|
||||
}
|
||||
}
|
||||
// 注释行处理
|
||||
if(inComment){
|
||||
if (inComment) {
|
||||
if (c == CharUtil.CR || c == CharUtil.LF) {
|
||||
// 注释行以换行符为结尾
|
||||
lineNo++;
|
||||
inComment = false;
|
||||
}
|
||||
// 跳过注释行中的任何字符
|
||||
buf.mark();
|
||||
preChar = c;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inQuotes) {
|
||||
//引号内,作为内容,直到引号结束
|
||||
if (c == config.textDelimiter) {
|
||||
// End of quoted text
|
||||
// issue#IB5UQ8 文本包装符转义
|
||||
final int next = tokener.next();
|
||||
if(next != config.textDelimiter){
|
||||
// 包装结束
|
||||
inQuotes = false;
|
||||
tokener.back();
|
||||
}
|
||||
// https://datatracker.ietf.org/doc/html/rfc4180#section-2 跳过转义符,只保留被转义的包装符
|
||||
} else {
|
||||
// 字段内容中新行
|
||||
if (isLineEnd(c, preChar)) {
|
||||
@ -270,46 +274,34 @@ public final class CsvParser extends ComputeIter<CsvRow> implements Closeable, S
|
||||
}
|
||||
}
|
||||
// 普通字段字符
|
||||
copyLen++;
|
||||
currentField.append((char)c);
|
||||
} else {
|
||||
// 非引号内
|
||||
if (c == config.fieldSeparator) {
|
||||
//一个字段结束
|
||||
if (copyLen > 0) {
|
||||
buf.appendTo(currentField, copyLen);
|
||||
copyLen = 0;
|
||||
}
|
||||
buf.mark();
|
||||
addField(currentFields, currentField.toStringAndReset());
|
||||
addField(currentFields, currentField.toString());
|
||||
currentField.setLength(0);
|
||||
} else if (c == config.textDelimiter && isFieldBegin(preChar)) {
|
||||
// 引号开始且出现在字段开头
|
||||
inQuotes = true;
|
||||
copyLen++;
|
||||
currentField.append((char)c);
|
||||
} else if (c == CharUtil.CR) {
|
||||
// \r,直接结束
|
||||
if (copyLen > 0) {
|
||||
buf.appendTo(currentField, copyLen);
|
||||
}
|
||||
buf.mark();
|
||||
addField(currentFields, currentField.toStringAndReset());
|
||||
// \r
|
||||
addField(currentFields, currentField.toString());
|
||||
currentField.setLength(0);
|
||||
preChar = c;
|
||||
break;
|
||||
} else if (c == CharUtil.LF) {
|
||||
// \n
|
||||
if (preChar != CharUtil.CR) {
|
||||
if (copyLen > 0) {
|
||||
buf.appendTo(currentField, copyLen);
|
||||
}
|
||||
buf.mark();
|
||||
addField(currentFields, currentField.toStringAndReset());
|
||||
addField(currentFields, currentField.toString());
|
||||
currentField.setLength(0);
|
||||
preChar = c;
|
||||
break;
|
||||
}
|
||||
// 前一个字符是\r,已经处理过这个字段了,此处直接跳过
|
||||
buf.mark();
|
||||
} else {
|
||||
// 普通字符
|
||||
copyLen++;
|
||||
currentField.append((char)c);
|
||||
}
|
||||
}
|
||||
|
||||
@ -325,7 +317,7 @@ public final class CsvParser extends ComputeIter<CsvRow> implements Closeable, S
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
reader.close();
|
||||
tokener.close();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -334,7 +326,7 @@ public final class CsvParser extends ComputeIter<CsvRow> implements Closeable, S
|
||||
* @param currentFields 当前的字段列表(即为行)
|
||||
* @param field 字段
|
||||
*/
|
||||
private void addField(List<String> currentFields, String field) {
|
||||
private void addField(final List<String> currentFields, String field) {
|
||||
final char textDelimiter = this.config.textDelimiter;
|
||||
|
||||
// 忽略多余引号后的换行符
|
||||
@ -342,12 +334,9 @@ public final class CsvParser extends ComputeIter<CsvRow> implements Closeable, S
|
||||
|
||||
if(StrUtil.isWrap(field, textDelimiter)){
|
||||
field = StrUtil.sub(field, 1, field.length() - 1);
|
||||
// https://datatracker.ietf.org/doc/html/rfc4180#section-2
|
||||
// 第七条规则,只有包装内的包装符需要转义
|
||||
field = StrUtil.replace(field, String.valueOf(textDelimiter) + textDelimiter, String.valueOf(textDelimiter));
|
||||
}
|
||||
|
||||
if(this.config.trimField){
|
||||
if (this.config.trimField) {
|
||||
// issue#I49M0C@Gitee
|
||||
field = StrUtil.trim(field);
|
||||
}
|
||||
@ -362,7 +351,7 @@ public final class CsvParser extends ComputeIter<CsvRow> implements Closeable, S
|
||||
* @return 是否结束
|
||||
* @since 5.7.4
|
||||
*/
|
||||
private boolean isLineEnd(char c, int preChar) {
|
||||
private boolean isLineEnd(final int c, final int preChar) {
|
||||
return (c == CharUtil.CR || c == CharUtil.LF) && preChar != CharUtil.CR;
|
||||
}
|
||||
|
||||
@ -383,89 +372,4 @@ public final class CsvParser extends ComputeIter<CsvRow> implements Closeable, S
|
||||
|| preChar == CharUtil.LF
|
||||
|| preChar == CharUtil.CR;
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部Buffer
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
private static class Buffer implements Serializable{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
final char[] buf;
|
||||
|
||||
/**
|
||||
* 标记位置,用于读数据
|
||||
*/
|
||||
private int mark;
|
||||
/**
|
||||
* 当前位置
|
||||
*/
|
||||
private int position;
|
||||
/**
|
||||
* 读取的数据长度,一般小于buf.length,-1表示无数据
|
||||
*/
|
||||
private int limit;
|
||||
|
||||
Buffer(int capacity) {
|
||||
buf = new char[capacity];
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否还有未读数据
|
||||
*
|
||||
* @return 是否还有未读数据
|
||||
*/
|
||||
public final boolean hasRemaining() {
|
||||
return position < limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取到缓存<br>
|
||||
* 全量读取,会重置Buffer中所有数据
|
||||
*
|
||||
* @param reader {@link Reader}
|
||||
*/
|
||||
int read(Reader reader) {
|
||||
int length;
|
||||
try {
|
||||
length = reader.read(this.buf);
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
this.mark = 0;
|
||||
this.position = 0;
|
||||
this.limit = length;
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 先获取当前字符,再将当前位置后移一位<br>
|
||||
* 此方法不检查是否到了数组末尾,请自行使用{@link #hasRemaining()}判断。
|
||||
*
|
||||
* @return 当前位置字符
|
||||
* @see #hasRemaining()
|
||||
*/
|
||||
char get() {
|
||||
return this.buf[this.position++];
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记位置记为下次读取位置
|
||||
*/
|
||||
void mark() {
|
||||
this.mark = this.position;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数据追加到{@link StrBuilder},追加结束后需手动调用{@link #mark()} 重置读取位置
|
||||
*
|
||||
* @param builder {@link StrBuilder}
|
||||
* @param length 追加的长度
|
||||
* @see #mark()
|
||||
*/
|
||||
void appendTo(StrBuilder builder, int length) {
|
||||
builder.append(this.buf, this.mark, length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright (c) 2024 Hutool Team and hutool.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cn.hutool.core.text.csv;
|
||||
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
|
||||
/**
|
||||
* CSV解析器,用于解析CSV文件
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.8.0
|
||||
*/
|
||||
public class CsvTokener implements Closeable {
|
||||
|
||||
private final Reader raw;
|
||||
/**
|
||||
* 在Reader的位置(解析到第几个字符)
|
||||
*/
|
||||
private long index;
|
||||
/**
|
||||
* 前一个字符
|
||||
*/
|
||||
private int prev;
|
||||
/**
|
||||
* 是否使用前一个字符
|
||||
*/
|
||||
private boolean usePrev;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param reader {@link Reader}
|
||||
*/
|
||||
public CsvTokener(final Reader reader) {
|
||||
this.raw = IoUtil.toBuffered(reader);
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取下一个字符,并记录位置
|
||||
*
|
||||
* @return 下一个字符
|
||||
*/
|
||||
public int next() {
|
||||
if(this.usePrev){
|
||||
this.usePrev = false;
|
||||
}else{
|
||||
try {
|
||||
this.prev = this.raw.read();
|
||||
} catch (final IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
}
|
||||
this.index++;
|
||||
return this.prev;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将标记回退到第一个字符
|
||||
*
|
||||
* @throws IllegalStateException 当多次调用back时,抛出此异常
|
||||
*/
|
||||
public void back() throws IllegalStateException {
|
||||
if (this.usePrev || this.index <= 0) {
|
||||
throw new IllegalStateException("Stepping back two steps is not supported");
|
||||
}
|
||||
this.index --;
|
||||
this.usePrev = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前位置
|
||||
*
|
||||
* @return 位置
|
||||
*/
|
||||
public long getIndex() {
|
||||
return this.index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
IoUtil.close(this.raw);
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
package cn.hutool.core.util;
|
||||
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.core.lang.ParameterizedTypeImpl;
|
||||
import cn.hutool.core.lang.reflect.ActualTypeMapperPool;
|
||||
|
||||
|
@ -34,10 +34,7 @@ import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
import java.util.zip.*;
|
||||
|
||||
/**
|
||||
* 压缩工具类
|
||||
@ -65,6 +62,17 @@ public class ZipUtil {
|
||||
try {
|
||||
return new ZipFile(file, ObjectUtil.defaultIfNull(charset, CharsetUtil.CHARSET_UTF_8));
|
||||
} catch (IOException e) {
|
||||
// issue#I3UZ28 可能编码错误提示
|
||||
if(e instanceof ZipException){
|
||||
if(e.getMessage().contains("invalid CEN header")){
|
||||
try {
|
||||
// 尝试使用不同编码
|
||||
return new ZipFile(file, CharsetUtil.CHARSET_UTF_8.equals(charset) ? CharsetUtil.CHARSET_GBK : CharsetUtil.CHARSET_UTF_8);
|
||||
} catch (final IOException ex) {
|
||||
throw new IORuntimeException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,27 @@
|
||||
package cn.hutool.core.date;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public class DateRangeTest {
|
||||
@Test
|
||||
void issue3783Test() {
|
||||
final Date start = DateUtil.parse("2024-01-01");
|
||||
final Date end = DateUtil.parse("2024-02-01");
|
||||
final List<DateTime> dateTimes = DateUtil.rangeToList(start, end, DateField.DAY_OF_MONTH, 0);
|
||||
Assertions.assertEquals(1, dateTimes.size());
|
||||
Assertions.assertEquals("2024-01-01 00:00:00", dateTimes.get(0).toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void issue3783Test2() {
|
||||
final Date start = DateUtil.parse("2024-01-01");
|
||||
final Date end = DateUtil.parse("2024-02-01");
|
||||
final List<DateTime> dateTimes = DateUtil.rangeToList(start, end, DateField.DAY_OF_MONTH, -2);
|
||||
Assertions.assertEquals(1, dateTimes.size());
|
||||
Assertions.assertEquals("2024-01-01 00:00:00", dateTimes.get(0).toString());
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package cn.hutool.core.date;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.TimeZone;
|
||||
|
||||
public class Issue3798Test {
|
||||
@Test
|
||||
void parseTest() {
|
||||
final String iso_datetime1 = "2000-01-01T12:00:00+08:00";
|
||||
final DateTime parse1 = DateUtil.parse(iso_datetime1);
|
||||
Assertions.assertEquals(TimeZone.getTimeZone("GMT+08:00"), parse1.getTimeZone());
|
||||
Assertions.assertEquals("2000-01-01 12:00:00", parse1.toString());
|
||||
|
||||
// 伦敦时间(Greenwich Mean Time, GMT)和北京时间(China Standard Time, CST)之间的时差是8小时。北京时间比伦敦时间快8小时
|
||||
final String iso_datetime2 = "2000-01-01T12:00:00+00:00";
|
||||
final DateTime parse2 = DateUtil.parse(iso_datetime2);
|
||||
Assertions.assertEquals(TimeZone.getTimeZone("GMT+00:00"), parse2.getTimeZone());
|
||||
Assertions.assertEquals("2000-01-01 20:00:00", parse2.toString(TimeZone.getTimeZone("GMT+08:00")));
|
||||
}
|
||||
}
|
@ -1,8 +1,9 @@
|
||||
package cn.hutool.core.io.unit;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class DataSizeUtilTest {
|
||||
|
||||
@Test
|
||||
@ -61,6 +62,18 @@ public class DataSizeUtilTest {
|
||||
assertEquals("1 TB", format);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void formatWithUnitTest(){
|
||||
String format = DataSizeUtil.format(Long.MAX_VALUE, DataUnit.TERABYTES);
|
||||
assertEquals("8388608 TB", format);
|
||||
|
||||
format = DataSizeUtil.format(1024L * 1024 * 1024 * 1024 * 1024, DataUnit.GIGABYTES);
|
||||
assertEquals("1048576 GB", format);
|
||||
|
||||
format = DataSizeUtil.format(1024L * 1024 * 1024 * 1024, DataUnit.GIGABYTES);
|
||||
assertEquals("1024 GB", format);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void issueI88Z4ZTest() {
|
||||
final String size = DataSizeUtil.format(10240000);
|
||||
|
@ -1,8 +1,9 @@
|
||||
package cn.hutool.core.math;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class CalculatorTest {
|
||||
|
||||
@Test
|
||||
@ -55,4 +56,14 @@ public class CalculatorTest {
|
||||
final double calcValue = Calculator.conversion("(11+2)12");
|
||||
assertEquals(156D, calcValue, 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
void issue3787Test() {
|
||||
final Calculator calculator1 = new Calculator();
|
||||
double result = calculator1.calculate("0+50/100x(1/0.5)");
|
||||
assertEquals(1D, result);
|
||||
|
||||
result = calculator1.calculate("0+50/100X(1/0.5)");
|
||||
assertEquals(1D, result);
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@ -17,6 +16,8 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class CsvUtilTest {
|
||||
|
||||
@Test
|
||||
@ -32,7 +33,8 @@ public class CsvUtilTest {
|
||||
assertEquals("关注\"对象\"", row0.get(3));
|
||||
assertEquals("年龄", row0.get(4));
|
||||
assertEquals("", row0.get(5));
|
||||
assertEquals("\"", row0.get(6));
|
||||
// 由于"""未闭合包装,因此末尾的换行符被当作包装内的内容,相当于:"""\n",转义后就是"\n
|
||||
assertEquals("\"\n", row0.get(6));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -46,7 +48,8 @@ public class CsvUtilTest {
|
||||
assertEquals("关注\"对象\"", csvRow.get(3));
|
||||
assertEquals("年龄", csvRow.get(4));
|
||||
assertEquals("", csvRow.get(5));
|
||||
assertEquals("\"", csvRow.get(6));
|
||||
// 由于"""未闭合包装,因此末尾的换行符被当作包装内的内容,相当于:"""\n",转义后就是"\n
|
||||
assertEquals("\"\n", csvRow.get(6));
|
||||
});
|
||||
}
|
||||
|
||||
@ -70,7 +73,8 @@ public class CsvUtilTest {
|
||||
assertEquals("关注\"对象\"", row0.get(3));
|
||||
assertEquals("年龄", row0.get(4));
|
||||
assertEquals("", row0.get(5));
|
||||
assertEquals("\"", row0.get(6));
|
||||
// 由于"""未闭合包装,因此末尾的换行符被当作包装内的内容,相当于:"""\n",转义后就是"\n
|
||||
assertEquals("\"\n", row0.get(6));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -84,7 +88,8 @@ public class CsvUtilTest {
|
||||
assertEquals("关注\"对象\"", csvRow.get(3));
|
||||
assertEquals("年龄", csvRow.get(4));
|
||||
assertEquals("", csvRow.get(5));
|
||||
assertEquals("\"", csvRow.get(6));
|
||||
// 由于"""未闭合包装,因此末尾的换行符被当作包装内的内容,相当于:"""\n",转义后就是"\n
|
||||
assertEquals("\"\n", csvRow.get(6));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,16 @@
|
||||
package cn.hutool.core.text.csv;
|
||||
|
||||
import cn.hutool.core.lang.Console;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.StringReader;
|
||||
|
||||
public class IssueIB5UQ8Test {
|
||||
@Test
|
||||
void parseEscapeTest() {
|
||||
String csv = "\"Consultancy, 10\"\",, food\"";
|
||||
final CsvReader reader = CsvUtil.getReader(new StringReader(csv));
|
||||
final String s = reader.read().getRow(0).get(0);
|
||||
Console.log(s);
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.33</version>
|
||||
<version>5.8.34-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-cron</artifactId>
|
||||
@ -26,6 +26,11 @@
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-log</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-setting</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.33</version>
|
||||
<version>5.8.34-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-crypto</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.33</version>
|
||||
<version>5.8.34-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-db</artifactId>
|
||||
|
@ -2,10 +2,8 @@ package cn.hutool.db;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.lang.PatternPool;
|
||||
import cn.hutool.core.lang.RegexPool;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.ReUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.db.dialect.Dialect;
|
||||
import cn.hutool.db.dialect.DialectFactory;
|
||||
|
@ -3,7 +3,6 @@ package cn.hutool.db.dialect.impl;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.db.Entity;
|
||||
import cn.hutool.db.Page;
|
||||
import cn.hutool.db.StatementUtil;
|
||||
import cn.hutool.db.dialect.DialectName;
|
||||
import cn.hutool.db.sql.SqlBuilder;
|
||||
|
@ -1,7 +1,6 @@
|
||||
package cn.hutool.db.ds.pooled;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.ClassUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.db.DbRuntimeException;
|
||||
import cn.hutool.db.DbUtil;
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.33</version>
|
||||
<version>5.8.34-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-dfa</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.33</version>
|
||||
<version>5.8.34-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-extra</artifactId>
|
||||
|
@ -1,7 +1,6 @@
|
||||
package cn.hutool.extra.compress.archiver;
|
||||
|
||||
import cn.hutool.core.lang.Filter;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
|
@ -268,6 +268,8 @@ public class JakartaMail implements Builder<MimeMessage> {
|
||||
if (StrUtil.startWith(attachment.getContentType(), "image/")) {
|
||||
// 图片附件,用于正文中引用图片
|
||||
bodyPart.setContentID(nameEncoded);
|
||||
// 图片附件设置内联,否则无法正常引用图片
|
||||
bodyPart.setDisposition(MimeBodyPart.INLINE);
|
||||
}
|
||||
this.multipart.addBodyPart(bodyPart);
|
||||
}
|
||||
|
@ -8,10 +8,7 @@ import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import javax.activation.DataHandler;
|
||||
import javax.activation.DataSource;
|
||||
import javax.activation.FileDataSource;
|
||||
import javax.activation.FileTypeMap;
|
||||
import javax.activation.*;
|
||||
import javax.mail.Address;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.Multipart;
|
||||
@ -273,6 +270,8 @@ public class Mail implements Builder<MimeMessage> {
|
||||
if (StrUtil.startWith(attachment.getContentType(), "image/")) {
|
||||
// 图片附件,用于正文中引用图片
|
||||
bodyPart.setContentID(nameEncoded);
|
||||
// 图片附件设置内联,否则无法正常引用图片
|
||||
bodyPart.setDisposition(MimeBodyPart.INLINE);
|
||||
}
|
||||
this.multipart.addBodyPart(bodyPart);
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package cn.hutool.extra.pinyin.engine.tinypinyin;
|
||||
|
||||
import cn.hutool.core.lang.Opt;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.pinyin.PinyinEngine;
|
||||
import com.github.promeg.pinyinhelper.Pinyin;
|
||||
|
@ -122,6 +122,19 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA
|
||||
return getBeanFactory().getBean(clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过class获取Bean
|
||||
*
|
||||
* @param <T> Bean类型
|
||||
* @param clazz Bean类
|
||||
* @param args 创建bean需要的参数属性
|
||||
* @return Bean对象
|
||||
* @since 5.8.34
|
||||
*/
|
||||
public static <T> T getBean(Class<T> clazz, Object... args) {
|
||||
return getBeanFactory().getBean(clazz, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过name,以及Clazz返回指定的Bean
|
||||
*
|
||||
@ -134,6 +147,18 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA
|
||||
return getBeanFactory().getBean(name, clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过name,以及Clazz返回指定的Bean
|
||||
*
|
||||
* @param name Bean名称
|
||||
* @param args 创建bean需要的参数属性
|
||||
* @return Bean对象
|
||||
* @since 5.8.34
|
||||
*/
|
||||
public static Object getBean(String name, Object... args) {
|
||||
return getBeanFactory().getBean(name, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过类型参考返回带泛型参数的Bean
|
||||
*
|
||||
|
@ -8,13 +8,9 @@ import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.ftp.AbstractFtp;
|
||||
import cn.hutool.extra.ftp.FtpConfig;
|
||||
import cn.hutool.extra.ftp.FtpException;
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
import com.jcraft.jsch.*;
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntrySelector;
|
||||
import com.jcraft.jsch.Session;
|
||||
import com.jcraft.jsch.SftpATTRS;
|
||||
import com.jcraft.jsch.SftpException;
|
||||
import com.jcraft.jsch.SftpProgressMonitor;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
@ -111,7 +107,7 @@ public class Sftp extends AbstractFtp {
|
||||
* @since 4.1.14
|
||||
*/
|
||||
public Sftp(Session session, Charset charset) {
|
||||
super(FtpConfig.create().setCharset(charset));
|
||||
super(FtpConfig.create().setCharset(charset).setHost(session.getHost()).setPort(session.getPort()));
|
||||
init(session, charset);
|
||||
}
|
||||
|
||||
@ -172,6 +168,17 @@ public class Sftp extends AbstractFtp {
|
||||
* @since 5.3.3
|
||||
*/
|
||||
public void init() {
|
||||
// issue#IB69U8 如果用户传入Session对象,则不能使用配置初始化,而是尝试重新连接
|
||||
if(StrUtil.isEmpty(this.ftpConfig.getHost()) && null != this.session){
|
||||
try {
|
||||
this.session.connect((int) this.ftpConfig.getConnectionTimeout());
|
||||
} catch (JSchException e) {
|
||||
throw new JschRuntimeException(e);
|
||||
}
|
||||
init(this.session, this.ftpConfig.getCharset());
|
||||
return;
|
||||
}
|
||||
|
||||
init(this.ftpConfig);
|
||||
}
|
||||
|
||||
|
@ -38,6 +38,15 @@ public class JakartaMailTest {
|
||||
JakartaMailUtil.sendHtml("hutool@foxmail.com", "测试", "<h1>邮件来自Hutool测试</h1><img src=\"cid:testImage\" />", map);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void sendHtmlWithImageTest() {
|
||||
Map<String, InputStream> map = new HashMap<>();
|
||||
InputStream in = getClass().getClassLoader().getResourceAsStream("image/Dromara.png");
|
||||
map.put("<image-1>", in);
|
||||
JakartaMailUtil.sendHtml("hutool@foxmail.com;li7hai26@outlook.com", "测试", "<h1>邮件来自Hutool测试</h1><img src=\"cid:image-1\" />", map);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void sendHtmlTest() {
|
||||
|
@ -38,6 +38,15 @@ public class MailTest {
|
||||
MailUtil.sendHtml("hutool@foxmail.com", "测试", "<h1>邮件来自Hutool测试</h1><img src=\"cid:testImage\" />", map);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void sendHtmlWithImageTest() {
|
||||
Map<String, InputStream> map = new HashMap<>();
|
||||
InputStream in = getClass().getClassLoader().getResourceAsStream("image/Dromara.png");
|
||||
map.put("<image-1>", in);
|
||||
MailUtil.sendHtml("hutool@foxmail.com;li7hai26@outlook.com", "测试", "<h1>邮件来自Hutool测试</h1><img src=\"cid:image-1\" />", map);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void sendHtmlTest() {
|
||||
|
BIN
hutool-extra/src/test/resources/image/Dromara.png
Normal file
BIN
hutool-extra/src/test/resources/image/Dromara.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.33</version>
|
||||
<version>5.8.34-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-http</artifactId>
|
||||
|
@ -5,7 +5,6 @@ import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.io.StreamProgress;
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.net.RFC3986;
|
||||
import cn.hutool.core.net.url.UrlQuery;
|
||||
|
@ -31,6 +31,8 @@ public class Browser extends UserAgentInfo {
|
||||
// 部分特殊浏览器是基于安卓、Iphone等的,需要优先判断
|
||||
// 企业微信 企业微信使用微信浏览器内核,会包含 MicroMessenger 所以要放在前面
|
||||
new Browser("wxwork", "wxwork", "wxwork\\/([\\d\\w\\.\\-]+)"),
|
||||
// issue#IB3SJF 微信电脑端
|
||||
new Browser("WindowsWechat", "WindowsWechat", "MicroMessenger" + Other_Version),
|
||||
// 微信
|
||||
new Browser("MicroMessenger", "MicroMessenger", Other_Version),
|
||||
// 微信小程序
|
||||
|
@ -0,0 +1,16 @@
|
||||
package cn.hutool.http.useragent;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class IssueIB3SJFTest {
|
||||
@Test
|
||||
void isMobileTest() {
|
||||
String str="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 NetType/WIFI " +
|
||||
"MicroMessenger/7.0.20.1781(0x6700143B) WindowsWechat(0x63090c11) XWEB/11275 Flue";
|
||||
UserAgent ua = UserAgentUtil.parse(str);
|
||||
|
||||
Assertions.assertFalse(ua.isMobile());
|
||||
Assertions.assertEquals("7.0.20.1781", ua.getBrowser().getVersion(str));
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.33</version>
|
||||
<version>5.8.34-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-json</artifactId>
|
||||
|
@ -2,7 +2,6 @@ package cn.hutool.json;
|
||||
|
||||
import cn.hutool.core.bean.BeanPath;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.core.lang.Filter;
|
||||
import cn.hutool.core.lang.Validator;
|
||||
import cn.hutool.core.lang.mutable.Mutable;
|
||||
|
25
hutool-json/src/test/java/cn/hutool/json/Issue3790Test.java
Normal file
25
hutool-json/src/test/java/cn/hutool/json/Issue3790Test.java
Normal file
@ -0,0 +1,25 @@
|
||||
package cn.hutool.json;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
public class Issue3790Test {
|
||||
@Test
|
||||
void bigDecimalToStringTest() {
|
||||
BigDecimal bigDecimal = new BigDecimal("0.01");
|
||||
bigDecimal = bigDecimal.setScale(4, RoundingMode.HALF_UP);
|
||||
|
||||
Dto dto = new Dto();
|
||||
dto.remain = bigDecimal;
|
||||
|
||||
final String jsonStr = JSONUtil.toJsonStr(dto, JSONConfig.create().setStripTrailingZeros(false));
|
||||
Assertions.assertEquals("{\"remain\":0.0100}", jsonStr);
|
||||
}
|
||||
|
||||
static class Dto {
|
||||
public BigDecimal remain;
|
||||
}
|
||||
}
|
17
hutool-json/src/test/java/cn/hutool/json/Issue3795Test.java
Normal file
17
hutool-json/src/test/java/cn/hutool/json/Issue3795Test.java
Normal file
@ -0,0 +1,17 @@
|
||||
package cn.hutool.json;
|
||||
|
||||
import cn.hutool.core.lang.TypeReference;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class Issue3795Test {
|
||||
@Test
|
||||
void toBeanTest() {
|
||||
String fieldMapping = "[{\"lable\":\"id\",\"value\":\"id\"},{\"lable\":\"name\",\"value\":\"name\"},{\"lable\":\"age\",\"value\":\"age\"}]";
|
||||
Assertions.assertThrows(UnsupportedOperationException.class, ()->{
|
||||
JSONUtil.toBean(fieldMapping, new TypeReference<Map<String, String>>() {}, false);
|
||||
});
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.33</version>
|
||||
<version>5.8.34-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-jwt</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.33</version>
|
||||
<version>5.8.34-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-log</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.33</version>
|
||||
<version>5.8.34-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-poi</artifactId>
|
||||
|
@ -5,6 +5,7 @@ import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.exceptions.DependencyException;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.util.CharUtil;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.poi.excel.ExcelDateUtil;
|
||||
import cn.hutool.poi.excel.sax.handler.RowHandler;
|
||||
@ -264,7 +265,15 @@ public class ExcelSaxUtil {
|
||||
if (StrUtil.isBlank(value)) {
|
||||
return null;
|
||||
}
|
||||
return getNumberValue(Double.parseDouble(value), numFmtString);
|
||||
|
||||
// issue#IB0EJ9 可能精度丢失,对含有小数的value判断并转为BigDecimal
|
||||
final double number = Double.parseDouble(value);
|
||||
if(StrUtil.contains(value, CharUtil.DOT) && !value.equals(Double.toString(number))){
|
||||
// 精度丢失
|
||||
return NumberUtil.toBigDecimal(value);
|
||||
}
|
||||
|
||||
return getNumberValue(number, numFmtString);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -18,9 +18,10 @@ import java.util.List;
|
||||
/**
|
||||
* sheetData标签内容读取处理器
|
||||
*
|
||||
* <pre>
|
||||
* <sheetData></sheetData>
|
||||
* </pre>
|
||||
* <pre>{@code
|
||||
* <sheetData></sheetData>
|
||||
* }</pre>
|
||||
*
|
||||
* @since 5.5.3
|
||||
*/
|
||||
public class SheetDataSaxHandler extends DefaultHandler {
|
||||
@ -62,7 +63,12 @@ public class SheetDataSaxHandler extends DefaultHandler {
|
||||
// 存储每行的列元素
|
||||
private List<Object> rowCellList = new ArrayList<>();
|
||||
|
||||
public SheetDataSaxHandler(RowHandler rowHandler){
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param rowHandler 行处理器
|
||||
*/
|
||||
public SheetDataSaxHandler(RowHandler rowHandler) {
|
||||
this.rowHandler = rowHandler;
|
||||
}
|
||||
|
||||
@ -156,7 +162,7 @@ public class SheetDataSaxHandler extends DefaultHandler {
|
||||
lastFormula.append(ch, start, length);
|
||||
break;
|
||||
}
|
||||
} else{
|
||||
} else {
|
||||
// 按理说内容应该为"<v>内容</v>",但是某些特别的XML内容不在v或f标签中,此处做一些兼容
|
||||
// issue#1303@Github
|
||||
lastContent.append(ch, start, length);
|
||||
|
@ -9,7 +9,7 @@ public class IssueIB0EJ9Test {
|
||||
@Test
|
||||
@Disabled
|
||||
void saxReadTest() {
|
||||
ExcelUtil.readBySax(FileUtil.file("d:/test/bbb.xlsx"), "Sheet1",
|
||||
ExcelUtil.readBySax(FileUtil.file("d:/test/数值型测试.xlsx"), "hcm工资表",
|
||||
(sheetIndex, rowIndex, rowlist) -> Console.log("[{}] [{}] {}", sheetIndex, rowIndex, rowlist));
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.33</version>
|
||||
<version>5.8.34-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-script</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.33</version>
|
||||
<version>5.8.34-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-setting</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.33</version>
|
||||
<version>5.8.34-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-socket</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.33</version>
|
||||
<version>5.8.34-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-system</artifactId>
|
||||
|
2
pom.xml
2
pom.xml
@ -8,7 +8,7 @@
|
||||
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.33</version>
|
||||
<version>5.8.34-SNAPSHOT</version>
|
||||
<name>hutool</name>
|
||||
<description>Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。</description>
|
||||
<url>https://github.com/dromara/hutool</url>
|
||||
|
Loading…
x
Reference in New Issue
Block a user