diff --git a/CHANGELOG.md b/CHANGELOG.md index 30275df37..24ef2f625 100755 --- a/CHANGELOG.md +++ b/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) diff --git a/README-EN.md b/README-EN.md index 1e201f42e..e97a535e9 100755 --- a/README-EN.md +++ b/README-EN.md @@ -40,7 +40,7 @@

- +

@@ -150,18 +150,18 @@ We provide the T-Shirt and Sweater with Hutool Logo, please visit the shop: cn.hutool hutool-all - 5.8.33 + 5.8.34 ``` ### 🍐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. diff --git a/README.md b/README.md index 67b4a553c..4f0285353 100755 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@

- +

@@ -143,20 +143,20 @@ Hutool = Hu + tool,是原公司项目底层代码剥离后的开源库,“Hu cn.hutool hutool-all - 5.8.33 + 5.8.34 ``` ### 🍐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平台没有测试,不能保证所有工具类或工具方法可用。 diff --git a/bin/version.txt b/bin/version.txt index ac78cf1a9..8cf2ade4e 100755 --- a/bin/version.txt +++ b/bin/version.txt @@ -1 +1 @@ -5.8.33 +5.8.34 diff --git a/docs/js/version.js b/docs/js/version.js index 5af72462d..36dcf0ff4 100755 --- a/docs/js/version.js +++ b/docs/js/version.js @@ -1 +1 @@ -var version = '5.8.33' \ No newline at end of file +var version = '5.8.34' \ No newline at end of file diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index 8c23ef85e..16816432b 100755 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.33 + 5.8.34-SNAPSHOT hutool-all diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index 130b31323..3b59fff4d 100755 --- a/hutool-aop/pom.xml +++ b/hutool-aop/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.33 + 5.8.34-SNAPSHOT hutool-aop diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml index 2d146dd50..e9174e95f 100755 --- a/hutool-bloomFilter/pom.xml +++ b/hutool-bloomFilter/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.33 + 5.8.34-SNAPSHOT hutool-bloomFilter diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml index 2b047ad20..70bd043cd 100755 --- a/hutool-bom/pom.xml +++ b/hutool-bom/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.33 + 5.8.34-SNAPSHOT hutool-bom diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml index d56159136..1c6fdd3e6 100755 --- a/hutool-cache/pom.xml +++ b/hutool-cache/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.33 + 5.8.34-SNAPSHOT hutool-cache diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/StampedCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/StampedCache.java index 7727a1fbd..2e90c9c4c 100755 --- a/hutool-cache/src/main/java/cn/hutool/cache/impl/StampedCache.java +++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/StampedCache.java @@ -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; diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml index d21ab174b..55c92332e 100755 --- a/hutool-captcha/pom.xml +++ b/hutool-captcha/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.33 + 5.8.34-SNAPSHOT hutool-captcha diff --git a/hutool-captcha/src/main/java/cn/hutool/captcha/CircleCaptcha.java b/hutool-captcha/src/main/java/cn/hutool/captcha/CircleCaptcha.java index 7f6cf2ed1..f528cf359 100755 --- a/hutool-captcha/src/main/java/cn/hutool/captcha/CircleCaptcha.java +++ b/hutool-captcha/src/main/java/cn/hutool/captcha/CircleCaptcha.java @@ -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.*; diff --git a/hutool-captcha/src/main/java/cn/hutool/captcha/GifCaptcha.java b/hutool-captcha/src/main/java/cn/hutool/captcha/GifCaptcha.java index f6276967a..7da77496c 100755 --- a/hutool-captcha/src/main/java/cn/hutool/captcha/GifCaptcha.java +++ b/hutool-captcha/src/main/java/cn/hutool/captcha/GifCaptcha.java @@ -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; diff --git a/hutool-captcha/src/main/java/cn/hutool/captcha/LineCaptcha.java b/hutool-captcha/src/main/java/cn/hutool/captcha/LineCaptcha.java index 55be796cf..a4d556da4 100755 --- a/hutool-captcha/src/main/java/cn/hutool/captcha/LineCaptcha.java +++ b/hutool-captcha/src/main/java/cn/hutool/captcha/LineCaptcha.java @@ -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.*; diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index 4df697501..84c3e0d8f 100755 --- a/hutool-core/pom.xml +++ b/hutool-core/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.33 + 5.8.34-SNAPSHOT hutool-core diff --git a/hutool-core/src/main/java/cn/hutool/core/comparator/VersionComparator.java b/hutool-core/src/main/java/cn/hutool/core/comparator/VersionComparator.java index 98df7a2d1..0cb62d4cf 100644 --- a/hutool-core/src/main/java/cn/hutool/core/comparator/VersionComparator.java +++ b/hutool-core/src/main/java/cn/hutool/core/comparator/VersionComparator.java @@ -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; /** * 版本比较器
diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/NumberWordFormatter.java b/hutool-core/src/main/java/cn/hutool/core/convert/NumberWordFormatter.java index d86f21eec..cc9e6a4bb 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/NumberWordFormatter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/NumberWordFormatter.java @@ -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; diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/MapConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/MapConverter.java index 72b4c32b7..f01a46c04 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/MapConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/MapConverter.java @@ -74,6 +74,11 @@ public class MapConverter extends AbstractConverter> { } 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); diff --git a/hutool-core/src/main/java/cn/hutool/core/date/DateRange.java b/hutool-core/src/main/java/cn/hutool/core/date/DateRange.java index 0e91132a4..69183f3a9 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/DateRange.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/DateRange.java @@ -48,6 +48,10 @@ public class DateRange extends Range { */ 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; diff --git a/hutool-core/src/main/java/cn/hutool/core/io/unit/DataSizeUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/unit/DataSizeUtil.java index 2835a8046..3962c5617 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/unit/DataSizeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/unit/DataSizeUtil.java @@ -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]; + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/unit/DataUnit.java b/hutool-core/src/main/java/cn/hutool/core/io/unit/DataUnit.java index 2163fc94e..f26ec8e0b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/unit/DataUnit.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/unit/DataUnit.java @@ -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 + "'"); } - } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Snowflake.java b/hutool-core/src/main/java/cn/hutool/core/lang/Snowflake.java index 7e7f76d12..f44cf83d6 100755 --- a/hutool-core/src/main/java/cn/hutool/core/lang/Snowflake.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/Snowflake.java @@ -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; diff --git a/hutool-core/src/main/java/cn/hutool/core/math/Calculator.java b/hutool-core/src/main/java/cn/hutool/core/math/Calculator.java index dbda0cfc5..ecaf93515 100644 --- a/hutool-core/src/main/java/cn/hutool/core/math/Calculator.java +++ b/hutool-core/src/main/java/cn/hutool/core/math/Calculator.java @@ -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] == '(')) { diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvParser.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvParser.java index f58a0a276..e9344822b 100755 --- a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvParser.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvParser.java @@ -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 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 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 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); } /** @@ -90,13 +97,13 @@ public final class CsvParser extends ComputeIter implements Closeable, S * @throws IllegalStateException 如果不解析头部或者没有调用nextRow()方法 */ public List getHeader() { - if (config.headerLineNo < 0) { + if (config.headerLineNo < 0) { throw new IllegalStateException("No header available - header parsing is disabled"); } 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 implements Closeable, S /** * 读取下一行数据 * - * @return CsvRow + * @return CsvRow,{@code null}表示 * @throws IORuntimeException IO读取异常 */ public CsvRow nextRow() throws IORuntimeException { List 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 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 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); } } @@ -190,7 +197,7 @@ public final class CsvParser extends ComputeIter implements Closeable, S * 空行是size为1的List,唯一元素是"" * *

- * 行号要考虑注释行和引号包装的内容中的换行 + * 行号要考虑注释行和引号包装的内容中的换行 *

* * @return 一行数据 @@ -199,70 +206,67 @@ public final class CsvParser extends ComputeIter implements Closeable, S private List readLine() throws IORuntimeException { // 矫正行号 // 当一行内容包含多行数据时,记录首行行号,但是读取下一行时,需要把多行内容的行数加上 - if(inQuotesLineCount > 0){ + if (inQuotesLineCount > 0) { this.lineNo += this.inQuotesLineCount; this.inQuotesLineCount = 0; } final List 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 - } - if (buf.read(this.reader) < 0) { - // CSV读取结束 - finished = true; - - if (currentField.hasContent() || preChar == config.fieldSeparator) { - //剩余部分作为一个字段 - addField(currentFields, currentField.toStringAndReset()); + c = tokener.next(); + if(c < 0){ + if (currentField.length() > 0 || preChar == config.fieldSeparator) { + if(this.inQuotes){ + // 未闭合的文本包装,在末尾补充包装符 + currentField.append(config.textDelimiter); } - break; - } - //重置 - copyLen = 0; + //剩余部分作为一个字段 + addField(currentFields, currentField.toString()); + currentField.setLength(0); + } + // 读取结束 + this.finished = true; + break; } - 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 - inQuotes = false; + // 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 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 implements Closeable, S @Override public void close() throws IOException { - reader.close(); + tokener.close(); } /** @@ -334,7 +326,7 @@ public final class CsvParser extends ComputeIter implements Closeable, S * @param currentFields 当前的字段列表(即为行) * @param field 字段 */ - private void addField(List currentFields, String field) { + private void addField(final List currentFields, String field) { final char textDelimiter = this.config.textDelimiter; // 忽略多余引号后的换行符 @@ -342,12 +334,9 @@ public final class CsvParser extends ComputeIter 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 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 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; - } - - /** - * 读取到缓存
- * 全量读取,会重置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; - } - - /** - * 先获取当前字符,再将当前位置后移一位
- * 此方法不检查是否到了数组末尾,请自行使用{@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); - } - } } diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvTokener.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvTokener.java new file mode 100644 index 000000000..8e957e022 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvTokener.java @@ -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); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/util/TypeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/TypeUtil.java index 8d5317d52..4ca0f7885 100755 --- a/hutool-core/src/main/java/cn/hutool/core/util/TypeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/TypeUtil.java @@ -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; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java index d44059f60..2e0dad053 100755 --- a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java @@ -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); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateRangeTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateRangeTest.java new file mode 100644 index 000000000..0a6404d26 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateRangeTest.java @@ -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 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 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()); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/date/Issue3798Test.java b/hutool-core/src/test/java/cn/hutool/core/date/Issue3798Test.java new file mode 100644 index 000000000..b041fc3da --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/date/Issue3798Test.java @@ -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"))); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/io/unit/DataSizeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/unit/DataSizeUtilTest.java index fdf557539..388878eb9 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/unit/DataSizeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/unit/DataSizeUtilTest.java @@ -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); diff --git a/hutool-core/src/test/java/cn/hutool/core/math/CalculatorTest.java b/hutool-core/src/test/java/cn/hutool/core/math/CalculatorTest.java index f1e1a8387..0e561e301 100644 --- a/hutool-core/src/test/java/cn/hutool/core/math/CalculatorTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/math/CalculatorTest.java @@ -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); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvUtilTest.java index 9f75cc208..e44e49250 100755 --- a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvUtilTest.java @@ -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)); }); } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/IssueIB5UQ8Test.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/IssueIB5UQ8Test.java new file mode 100644 index 000000000..daaf7ab38 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/IssueIB5UQ8Test.java @@ -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); + } +} diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index a31c34165..c945f9637 100755 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.33 + 5.8.34-SNAPSHOT hutool-cron @@ -26,6 +26,11 @@ hutool-core ${project.parent.version} + + cn.hutool + hutool-log + ${project.parent.version} + cn.hutool hutool-setting diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 6b1e2c6ed..33def367a 100755 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.33 + 5.8.34-SNAPSHOT hutool-crypto diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 2bda60742..d885d9db0 100755 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.33 + 5.8.34-SNAPSHOT hutool-db diff --git a/hutool-db/src/main/java/cn/hutool/db/DialectRunner.java b/hutool-db/src/main/java/cn/hutool/db/DialectRunner.java index c0c7906e6..ea9dfd952 100644 --- a/hutool-db/src/main/java/cn/hutool/db/DialectRunner.java +++ b/hutool-db/src/main/java/cn/hutool/db/DialectRunner.java @@ -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; diff --git a/hutool-db/src/main/java/cn/hutool/db/dialect/impl/DmDialect.java b/hutool-db/src/main/java/cn/hutool/db/dialect/impl/DmDialect.java index 111d0682a..06ac9e7ef 100644 --- a/hutool-db/src/main/java/cn/hutool/db/dialect/impl/DmDialect.java +++ b/hutool-db/src/main/java/cn/hutool/db/dialect/impl/DmDialect.java @@ -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; diff --git a/hutool-db/src/main/java/cn/hutool/db/ds/pooled/PooledConnection.java b/hutool-db/src/main/java/cn/hutool/db/ds/pooled/PooledConnection.java index ffca39390..d94bf27c9 100644 --- a/hutool-db/src/main/java/cn/hutool/db/ds/pooled/PooledConnection.java +++ b/hutool-db/src/main/java/cn/hutool/db/ds/pooled/PooledConnection.java @@ -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; diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 0fb5e6b44..992185a0e 100755 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.33 + 5.8.34-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 26b110a5b..208f86684 100755 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.33 + 5.8.34-SNAPSHOT hutool-extra diff --git a/hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/Archiver.java b/hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/Archiver.java index d3eec1df5..dd8fa1225 100755 --- a/hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/Archiver.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/Archiver.java @@ -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; diff --git a/hutool-extra/src/main/java/cn/hutool/extra/mail/JakartaMail.java b/hutool-extra/src/main/java/cn/hutool/extra/mail/JakartaMail.java index 8f73611ec..99a74280a 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/mail/JakartaMail.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/mail/JakartaMail.java @@ -268,6 +268,8 @@ public class JakartaMail implements Builder { if (StrUtil.startWith(attachment.getContentType(), "image/")) { // 图片附件,用于正文中引用图片 bodyPart.setContentID(nameEncoded); + // 图片附件设置内联,否则无法正常引用图片 + bodyPart.setDisposition(MimeBodyPart.INLINE); } this.multipart.addBodyPart(bodyPart); } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/mail/Mail.java b/hutool-extra/src/main/java/cn/hutool/extra/mail/Mail.java index 2d71b2dcc..535300bf9 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/mail/Mail.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/mail/Mail.java @@ -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 { if (StrUtil.startWith(attachment.getContentType(), "image/")) { // 图片附件,用于正文中引用图片 bodyPart.setContentID(nameEncoded); + // 图片附件设置内联,否则无法正常引用图片 + bodyPart.setDisposition(MimeBodyPart.INLINE); } this.multipart.addBodyPart(bodyPart); } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/tinypinyin/TinyPinyinEngine.java b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/tinypinyin/TinyPinyinEngine.java index 2ecf0dd1c..0ac8b0c52 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/tinypinyin/TinyPinyinEngine.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/tinypinyin/TinyPinyinEngine.java @@ -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; diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java index abbdbda40..6a625dec8 100755 --- a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java @@ -122,6 +122,19 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA return getBeanFactory().getBean(clazz); } + /** + * 通过class获取Bean + * + * @param Bean类型 + * @param clazz Bean类 + * @param args 创建bean需要的参数属性 + * @return Bean对象 + * @since 5.8.34 + */ + public static T getBean(Class 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 * diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java index bbca18dd0..865f216d4 100755 --- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java @@ -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); } diff --git a/hutool-extra/src/test/java/cn/hutool/extra/mail/JakartaMailTest.java b/hutool-extra/src/test/java/cn/hutool/extra/mail/JakartaMailTest.java index ec0824aac..20b419cff 100644 --- a/hutool-extra/src/test/java/cn/hutool/extra/mail/JakartaMailTest.java +++ b/hutool-extra/src/test/java/cn/hutool/extra/mail/JakartaMailTest.java @@ -38,6 +38,15 @@ public class JakartaMailTest { JakartaMailUtil.sendHtml("hutool@foxmail.com", "测试", "

邮件来自Hutool测试

", map); } + @Test + @Disabled + public void sendHtmlWithImageTest() { + Map map = new HashMap<>(); + InputStream in = getClass().getClassLoader().getResourceAsStream("image/Dromara.png"); + map.put("", in); + JakartaMailUtil.sendHtml("hutool@foxmail.com;li7hai26@outlook.com", "测试", "

邮件来自Hutool测试

", map); + } + @Test @Disabled public void sendHtmlTest() { diff --git a/hutool-extra/src/test/java/cn/hutool/extra/mail/MailTest.java b/hutool-extra/src/test/java/cn/hutool/extra/mail/MailTest.java index 9784b70af..3a29c9ec2 100644 --- a/hutool-extra/src/test/java/cn/hutool/extra/mail/MailTest.java +++ b/hutool-extra/src/test/java/cn/hutool/extra/mail/MailTest.java @@ -38,6 +38,15 @@ public class MailTest { MailUtil.sendHtml("hutool@foxmail.com", "测试", "

邮件来自Hutool测试

", map); } + @Test + @Disabled + public void sendHtmlWithImageTest() { + Map map = new HashMap<>(); + InputStream in = getClass().getClassLoader().getResourceAsStream("image/Dromara.png"); + map.put("", in); + MailUtil.sendHtml("hutool@foxmail.com;li7hai26@outlook.com", "测试", "

邮件来自Hutool测试

", map); + } + @Test @Disabled public void sendHtmlTest() { diff --git a/hutool-extra/src/test/resources/image/Dromara.png b/hutool-extra/src/test/resources/image/Dromara.png new file mode 100644 index 000000000..b5c594ff9 Binary files /dev/null and b/hutool-extra/src/test/resources/image/Dromara.png differ diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 18d5cb238..ece953121 100755 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.33 + 5.8.34-SNAPSHOT hutool-http diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java index 45609ccb8..de88d1630 100755 --- a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java @@ -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; diff --git a/hutool-http/src/main/java/cn/hutool/http/useragent/Browser.java b/hutool-http/src/main/java/cn/hutool/http/useragent/Browser.java index 3eef7862b..e6f4d7b9a 100755 --- a/hutool-http/src/main/java/cn/hutool/http/useragent/Browser.java +++ b/hutool-http/src/main/java/cn/hutool/http/useragent/Browser.java @@ -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), // 微信小程序 diff --git a/hutool-http/src/test/java/cn/hutool/http/useragent/IssueIB3SJFTest.java b/hutool-http/src/test/java/cn/hutool/http/useragent/IssueIB3SJFTest.java new file mode 100644 index 000000000..7af4c91e7 --- /dev/null +++ b/hutool-http/src/test/java/cn/hutool/http/useragent/IssueIB3SJFTest.java @@ -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)); + } +} diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 92e4f3af8..fbf76976d 100755 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.33 + 5.8.34-SNAPSHOT hutool-json diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java index 8e0a709bb..e7371f14f 100755 --- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java @@ -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; diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue3790Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue3790Test.java new file mode 100644 index 000000000..46132355c --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/Issue3790Test.java @@ -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; + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue3795Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue3795Test.java new file mode 100644 index 000000000..6e0ef606f --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/Issue3795Test.java @@ -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>() {}, false); + }); + } +} diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml index c0f0dd96c..6f5be6e0f 100755 --- a/hutool-jwt/pom.xml +++ b/hutool-jwt/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.33 + 5.8.34-SNAPSHOT hutool-jwt diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index ebda5848d..4cb957f25 100755 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.33 + 5.8.34-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 631555a30..c7c0797c1 100755 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.33 + 5.8.34-SNAPSHOT hutool-poi diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java index ff12045ef..66e5b176b 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java @@ -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); } /** diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java index a2734d819..5dbbeae8e 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java @@ -18,9 +18,10 @@ import java.util.List; /** * sheetData标签内容读取处理器 * - *
- * <sheetData></sheetData>
- * 
+ *
{@code
+ *   
+ * }
+ * * @since 5.5.3 */ public class SheetDataSaxHandler extends DefaultHandler { @@ -62,7 +63,12 @@ public class SheetDataSaxHandler extends DefaultHandler { // 存储每行的列元素 private List 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 { // 按理说内容应该为"内容",但是某些特别的XML内容不在v或f标签中,此处做一些兼容 // issue#1303@Github lastContent.append(ch, start, length); @@ -292,8 +298,8 @@ public class SheetDataSaxHandler extends DefaultHandler { // 单元格存储格式的索引,对应style.xml中的numFmts元素的子元素索引 final int numFmtIndex = xssfCellStyle.getDataFormat(); this.numFmtString = ObjectUtil.defaultIfNull( - xssfCellStyle.getDataFormatString(), - () -> BuiltinFormats.getBuiltinFormat(numFmtIndex)); + xssfCellStyle.getDataFormatString(), + () -> BuiltinFormats.getBuiltinFormat(numFmtIndex)); if (CellDataType.NUMBER == this.cellDataType && ExcelSaxUtil.isDateFormat(numFmtIndex, numFmtString)) { cellDataType = CellDataType.DATE; } diff --git a/hutool-poi/src/test/java/cn/hutool/poi/excel/IssueIB0EJ9Test.java b/hutool-poi/src/test/java/cn/hutool/poi/excel/IssueIB0EJ9Test.java index dbc32f4e2..c5f82332e 100644 --- a/hutool-poi/src/test/java/cn/hutool/poi/excel/IssueIB0EJ9Test.java +++ b/hutool-poi/src/test/java/cn/hutool/poi/excel/IssueIB0EJ9Test.java @@ -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)); } } diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index d858f51be..92b0c66b2 100755 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.33 + 5.8.34-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index 45bdc1040..570ae9686 100755 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.33 + 5.8.34-SNAPSHOT hutool-setting diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 5437b8189..36fbbb44e 100755 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.33 + 5.8.34-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index 58377d7db..a34035627 100755 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.33 + 5.8.34-SNAPSHOT hutool-system diff --git a/pom.xml b/pom.xml index 97472b007..268ebe5d9 100755 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.8.33 + 5.8.34-SNAPSHOT hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/dromara/hutool