mirror of
https://gitee.com/dromara/hutool.git
synced 2026-02-09 09:16:26 +08:00
Compare commits
11 Commits
29ac254f06
...
a6b1bebeac
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6b1bebeac | ||
|
|
c2abe4c9d3 | ||
|
|
84a6132c26 | ||
|
|
b3ec45a0ac | ||
|
|
dca189f700 | ||
|
|
9c35fa9e3f | ||
|
|
c0bb9675b2 | ||
|
|
7e0199c1a9 | ||
|
|
8d2c5b0449 | ||
|
|
190bdf2585 | ||
|
|
47479e9262 |
@@ -1,17 +1,24 @@
|
||||
|
||||
# 🚀Changelog
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
# 5.8.44(2026-01-27)
|
||||
# 5.8.44(2026-02-09)
|
||||
### 🐣新特性
|
||||
* 【core 】 `NumberUtil.parseNumber`增加支持科学计数法(pr#4211@Github)
|
||||
* 【captcha】 `AbstractCaptcha`增加`setStroke`方法支持线条粗细(issue#IDJQ15@Gitee)
|
||||
* 【core 】 `BooleanUtil`新增 exactlyOneTrue 方法用于互斥条件校验(issue#IDJQ15@Gitee)
|
||||
* 【core 】 `DateUtil.normalize`方法中正则预编译提升效率(pr#4221@Gitee)
|
||||
* 【core 】 `AppendableWriter`增加checkNotClosed(issue#IDMZ5K@Gitee)
|
||||
|
||||
### 🐞Bug修复
|
||||
* 【json 】 修复`JSONUtil.wrap`忽略错误问题(issue#4210@Github)
|
||||
* 【http 】 修复`HttpUtil.normalizeParams `在极端输入下抛 StringIndexOutOfBoundsException(pr#4216@Github)
|
||||
* 【extra 】 修复`MailAccount.setAuth`参数与field不一致问题(issue#4217@Github)
|
||||
* 【core 】 修复`TransMap.computeIfAbsent`mappingFunction处理不一致问题(issue#IDM6UR@Gitee)
|
||||
* 【core 】 修复`MultiResource`游标歧义问题(issue#IDNAOY@Gitee)
|
||||
* 【core 】 修复`BufferUtil`copy歧义问题(issue#IDN097@Gitee)
|
||||
* 【core 】 修复`JschSessionPool`回收导致的session未关闭问题(issue#4223@Github)
|
||||
* 【core 】 修复`XmlUtil.xmlToBean`option参数无效问题(issue#4226@Github)
|
||||
* 【core 】 修复`ReUtil.replaceAll`空指针问题(issue#IDPHVW@Gitee)
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
# 5.8.43(2026-01-04)
|
||||
|
||||
@@ -25,6 +25,7 @@ import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -44,6 +45,16 @@ public class DateUtil extends CalendarUtil {
|
||||
"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec", // 月份
|
||||
"gmt", "ut", "utc", "est", "edt", "cst", "cdt", "mst", "mdt", "pst", "pdt"// 时间标准
|
||||
};
|
||||
|
||||
/**
|
||||
* 匹配日期分隔符(正斜杠、点、年、月),用于统一替换为横杠
|
||||
*/
|
||||
private static final Pattern DATE_SEPARATOR_PATTERN = Pattern.compile("[/.年月]");
|
||||
|
||||
/**
|
||||
* 匹配时间单位(时、分、秒),用于统一替换为冒号 匹配时间单位(时、分、秒),用于统一替换为冒号
|
||||
*/
|
||||
private static final Pattern TIME_UNIT_PATTERN = Pattern.compile("[时分秒]");
|
||||
|
||||
/**
|
||||
* 当前时间,转换为{@link DateTime}对象
|
||||
@@ -2408,14 +2419,14 @@ public class DateUtil extends CalendarUtil {
|
||||
final StringBuilder builder = StrUtil.builder();
|
||||
|
||||
// 日期部分("\"、"/"、"."、"年"、"月"都替换为"-")
|
||||
String datePart = dateAndTime.get(0).replaceAll("[/.年月]", "-");
|
||||
String datePart = DATE_SEPARATOR_PATTERN.matcher(dateAndTime.get(0)).replaceAll("-");
|
||||
datePart = StrUtil.removeSuffix(datePart, "日");
|
||||
builder.append(datePart);
|
||||
|
||||
// 时间部分
|
||||
if (size == 2) {
|
||||
builder.append(' ');
|
||||
String timePart = dateAndTime.get(1).replaceAll("[时分秒]", ":");
|
||||
String timePart = TIME_UNIT_PATTERN.matcher(dateAndTime.get(1)).replaceAll(":");
|
||||
timePart = StrUtil.removeSuffix(timePart, ":");
|
||||
//将ISO8601中的逗号替换为.
|
||||
timePart = timePart.replace(',', '.');
|
||||
|
||||
@@ -66,11 +66,13 @@ public class AppendableWriter extends Writer implements Appendable {
|
||||
|
||||
@Override
|
||||
public void write(final String str) throws IOException {
|
||||
checkNotClosed();
|
||||
appendable.append(str);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(final char[] cbuf) throws IOException {
|
||||
checkNotClosed();
|
||||
appendable.append(CharBuffer.wrap(cbuf));
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import cn.hutool.core.util.StrUtil;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* {@link ByteBuffer} 工具类<br>
|
||||
@@ -26,7 +27,7 @@ public class BufferUtil {
|
||||
* @return 新的ByteBuffer
|
||||
*/
|
||||
public static ByteBuffer copy(ByteBuffer src, int start, int end) {
|
||||
return copy(src, ByteBuffer.allocate(end - start));
|
||||
return ByteBuffer.wrap(Arrays.copyOfRange(src.array(), start, end));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,7 +24,10 @@ public class MultiResource implements Resource, Iterable<Resource>, Iterator<Res
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final List<Resource> resources;
|
||||
private int cursor;
|
||||
/**
|
||||
* 游标
|
||||
*/
|
||||
private int cursor = -1;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
@@ -50,42 +53,42 @@ public class MultiResource implements Resource, Iterable<Resource>, Iterator<Res
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return resources.get(cursor).getName();
|
||||
return resources.get(getValidCursor()).getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getUrl() {
|
||||
return resources.get(cursor).getUrl();
|
||||
return resources.get(getValidCursor()).getUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getStream() {
|
||||
return resources.get(cursor).getStream();
|
||||
return resources.get(getValidCursor()).getStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isModified() {
|
||||
return resources.get(cursor).isModified();
|
||||
return resources.get(getValidCursor()).isModified();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedReader getReader(Charset charset) {
|
||||
return resources.get(cursor).getReader(charset);
|
||||
return resources.get(getValidCursor()).getReader(charset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readStr(Charset charset) throws IORuntimeException {
|
||||
return resources.get(cursor).readStr(charset);
|
||||
return resources.get(getValidCursor()).readStr(charset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readUtf8Str() throws IORuntimeException {
|
||||
return resources.get(cursor).readUtf8Str();
|
||||
return resources.get(getValidCursor()).readUtf8Str();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] readBytes() throws IORuntimeException {
|
||||
return resources.get(cursor).readBytes();
|
||||
return resources.get(getValidCursor()).readBytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -95,12 +98,12 @@ public class MultiResource implements Resource, Iterable<Resource>, Iterator<Res
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return cursor < resources.size();
|
||||
return getValidCursor() < resources.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Resource next() {
|
||||
if (cursor >= resources.size()) {
|
||||
if (!hasNext()) {
|
||||
throw new ConcurrentModificationException();
|
||||
}
|
||||
this.cursor++;
|
||||
@@ -109,14 +112,14 @@ public class MultiResource implements Resource, Iterable<Resource>, Iterator<Res
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
this.resources.remove(this.cursor);
|
||||
this.resources.remove(getValidCursor());
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置游标
|
||||
*/
|
||||
public synchronized void reset() {
|
||||
this.cursor = 0;
|
||||
this.cursor = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,4 +132,12 @@ public class MultiResource implements Resource, Iterable<Resource>, Iterator<Res
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前有效游标位置的资源
|
||||
*
|
||||
* @return 资源
|
||||
*/
|
||||
private int getValidCursor() {
|
||||
return Math.max(cursor, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,17 +87,17 @@ public abstract class TransMap<K, V> extends MapWrapper<K, V> {
|
||||
|
||||
@Override
|
||||
public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
|
||||
return super.computeIfPresent(customKey(key), (k, v) -> remappingFunction.apply(customKey(k), customValue(v)));
|
||||
return super.computeIfPresent(customKey(key), (k, v) -> remappingFunction.apply(k, customValue(v)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
|
||||
return super.compute(customKey(key), (k, v) -> remappingFunction.apply(customKey(k), customValue(v)));
|
||||
return super.compute(customKey(key), (k, v) -> remappingFunction.apply(k, customValue(v)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
|
||||
return super.merge(customKey(key), customValue(value), (v1, v2) -> remappingFunction.apply(customValue(v1), customValue(v2)));
|
||||
return super.merge(customKey(key), customValue(value), remappingFunction);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -4,10 +4,7 @@ import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.comparator.LengthComparator;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.exceptions.UtilException;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.lang.PatternPool;
|
||||
import cn.hutool.core.lang.RegexPool;
|
||||
import cn.hutool.core.lang.Validator;
|
||||
import cn.hutool.core.lang.*;
|
||||
import cn.hutool.core.lang.func.Func1;
|
||||
import cn.hutool.core.lang.mutable.Mutable;
|
||||
import cn.hutool.core.lang.mutable.MutableObj;
|
||||
@@ -216,7 +213,7 @@ public class ReUtil {
|
||||
* @param pattern 编译后的正则模式
|
||||
* @param content 被匹配的内容
|
||||
* @param withGroup0 是否包括分组0,此分组表示全匹配的信息
|
||||
* @param findAll 是否查找所有匹配到的内容,{@code false}表示只读取第一个匹配到的内容
|
||||
* @param findAll 是否查找所有匹配到的内容,{@code false}表示只读取第一个匹配到的内容
|
||||
* @return 匹配后得到的字符串数组,按照分组顺序依次列出,未匹配到返回空列表,任何一个参数为null返回null
|
||||
* @since 4.0.13
|
||||
*/
|
||||
@@ -234,7 +231,7 @@ public class ReUtil {
|
||||
result.add(matcher.group(i));
|
||||
}
|
||||
|
||||
if(false == findAll){
|
||||
if (false == findAll) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -885,7 +882,8 @@ public class ReUtil {
|
||||
String replacement = replacementTemplate;
|
||||
for (final String var : varNums) {
|
||||
final int group = Integer.parseInt(var);
|
||||
replacement = replacement.replace("$" + var, matcher.group(group));
|
||||
//replacement = replacement.replace("$" + var, matcher.group(group));
|
||||
replacement = StrUtil.replace(replacement, "$" + var, matcher.group(group));
|
||||
}
|
||||
matcher.appendReplacement(sb, escape(replacement));
|
||||
result = matcher.find();
|
||||
|
||||
@@ -1022,7 +1022,7 @@ public class XmlUtil {
|
||||
final String nodeName = CollUtil.getFirst(map.keySet());
|
||||
if (simpleName.equalsIgnoreCase(nodeName)) {
|
||||
// 只有key和bean的名称匹配时才做单一对象转换
|
||||
return BeanUtil.toBean(map.get(nodeName), bean);
|
||||
return BeanUtil.toBean(map.get(nodeName), bean, copyOptions);
|
||||
}
|
||||
}
|
||||
return BeanUtil.toBean(map, bean, copyOptions);
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package cn.hutool.core.io;
|
||||
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
|
||||
/**
|
||||
* BufferUtil单元测试
|
||||
@@ -81,4 +82,84 @@ public class BufferUtilTest {
|
||||
// 再次调用工具类转换,输出结果应该不变
|
||||
assertEquals(originalText, StrUtil.str(buffer, StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试正常范围内的拷贝功能
|
||||
*/
|
||||
@Test
|
||||
public void copyNormalRangeTest() {
|
||||
// 准备测试数据
|
||||
final byte[] originalData = {65, 66, 67, 68, 69, 70}; // 对应 "ABCDEF"
|
||||
final ByteBuffer srcBuffer = ByteBuffer.wrap(originalData);
|
||||
|
||||
// 执行拷贝操作,从索引1到4(不包含4),即拷贝BCD
|
||||
final ByteBuffer resultBuffer = BufferUtil.copy(srcBuffer, 1, 4);
|
||||
|
||||
// 验证结果
|
||||
final byte[] resultArray = new byte[3];
|
||||
resultBuffer.get(resultArray);
|
||||
assertArrayEquals(new byte[]{66, 67, 68}, resultArray); // BCD
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试从开头开始拷贝
|
||||
*/
|
||||
@Test
|
||||
public void copyFromStartTest() {
|
||||
final byte[] originalData = {65, 66, 67, 68, 69, 70}; // 对应 "ABCDEF"
|
||||
final ByteBuffer srcBuffer = ByteBuffer.wrap(originalData);
|
||||
|
||||
// 从索引0拷贝到3,即拷贝ABC
|
||||
final ByteBuffer resultBuffer = BufferUtil.copy(srcBuffer, 0, 3);
|
||||
|
||||
final byte[] resultArray = new byte[3];
|
||||
resultBuffer.get(resultArray);
|
||||
assertArrayEquals(new byte[]{65, 66, 67}, resultArray); // ABC
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试拷贝到末尾
|
||||
*/
|
||||
@Test
|
||||
public void copyToEndTest() {
|
||||
final byte[] originalData = {65, 66, 67, 68, 69, 70}; // 对应 "ABCDEF"
|
||||
final ByteBuffer srcBuffer = ByteBuffer.wrap(originalData);
|
||||
|
||||
// 从索引3拷贝到末尾,即拷贝DEF
|
||||
final ByteBuffer resultBuffer = BufferUtil.copy(srcBuffer, 3, 6);
|
||||
|
||||
final byte[] resultArray = new byte[3];
|
||||
resultBuffer.get(resultArray);
|
||||
assertArrayEquals(new byte[]{68, 69, 70}, resultArray); // DEF
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试空拷贝(start等于end)
|
||||
*/
|
||||
@Test
|
||||
public void copyEmptyRangeTest() {
|
||||
final byte[] originalData = {65, 66, 67, 68, 69, 70}; // 对应 "ABCDEF"
|
||||
final ByteBuffer srcBuffer = ByteBuffer.wrap(originalData);
|
||||
|
||||
// 拷贝相同起始和结束位置,应该得到空数组
|
||||
final ByteBuffer resultBuffer = BufferUtil.copy(srcBuffer, 2, 2);
|
||||
|
||||
assertEquals(0, resultBuffer.remaining()); // 应该为空
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试整个数组的拷贝
|
||||
*/
|
||||
@Test
|
||||
public void copyFullRangeTest() {
|
||||
final byte[] originalData = {65, 66, 67, 68, 69, 70}; // 对应 "ABCDEF"
|
||||
final ByteBuffer srcBuffer = ByteBuffer.wrap(originalData);
|
||||
|
||||
// 拷贝整个数组
|
||||
final ByteBuffer resultBuffer = BufferUtil.copy(srcBuffer, 0, 6);
|
||||
|
||||
final byte[] resultArray = new byte[6];
|
||||
resultBuffer.get(resultArray);
|
||||
assertArrayEquals(originalData, resultArray);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,4 +244,10 @@ public class ReUtilTest {
|
||||
s = ReUtil.get(PatternPool.EMAIL, mail, 0);
|
||||
assertEquals("a.b@Hutool.cn", s);
|
||||
}
|
||||
|
||||
@Test
|
||||
void issueIDPHVWTest(){
|
||||
final String s = ReUtil.replaceAll("2 倾斜摄影成果", "(^\\d+(\\.\\d+)*)(\\s)(((.*?)(DEM|DOM)?)([(|\\(](.*?)[)|\\)])?$)", "$1$3$5($9)");
|
||||
assertEquals("2 倾斜摄影成果()", s);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package cn.hutool.extra.ssh;
|
||||
|
||||
import cn.hutool.core.lang.SimpleCache;
|
||||
import cn.hutool.core.map.SafeConcurrentHashMap;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.jcraft.jsch.Session;
|
||||
|
||||
@@ -22,7 +23,7 @@ public enum JschSessionPool {
|
||||
/**
|
||||
* SSH会话池,key:host,value:Session对象
|
||||
*/
|
||||
private final SimpleCache<String, Session> cache = new SimpleCache<>();
|
||||
private final SimpleCache<String, Session> cache = new SimpleCache<>(new SafeConcurrentHashMap<>());
|
||||
|
||||
/**
|
||||
* 获取Session,不存在返回null
|
||||
|
||||
Reference in New Issue
Block a user