Merged with v5-dev

This commit is contained in:
yeshwanthsripathy 2023-11-08 12:47:45 -06:00
commit cb6811a7b8
95 changed files with 1115 additions and 300 deletions

View File

@ -1,6 +1,31 @@
# 🚀Changelog
-------------------------------------------------------------------------------------------------------------
# 5.8.23(2023-11-04)
### 🐣新特性
* 【json 】 改进TemporalAccessorSerializer支持dayOfMonth和month枚举名issue#I82AM8@Gitee
* 【core 】 新增ProxySocketFactory
* 【http 】 UserAgent增加百度浏览器识别issue#I847JY@Gitee
* 【core 】 ReflectUtil.getFieldsValue增加Filter重载pr#1090@Gitee
* 【core 】 Snowflake增加方法根据传入时间戳计算ID起终点pr#1096@Gitee
* 【core 】 PathUtil增加loopFiles重载可选是否追踪软链issue#3353@Github
### 🐞Bug修复
* 【cron 】 修复Cron表达式range解析错误问题issue#I82CSH@Gitee
* 【core 】 修复VersionComparator在极端数据排序时候违反了自反性问题issue#I81N3H@Gitee
* 【json 】 修复JSONStrFormatter:format函数对于转义符号处理逻辑错误问题issue#I84V6I@Gitee
* 【core 】 修复特定情况下BiMap覆盖Value后仍能通过旧Value查询到Key问题issue#I88R5M@Gitee
* 【core 】 修复aop的afterException无法生效问题issue#3329@Github
* 【core 】 修复TypeUtil.getClass方法强转报错问题pr#1092@Github
* 【core 】 修复DataSize.parse(size)不支持空格问题issue#I88Z4Z@Gitee
* 【http 】 修复SimpleServer在添加的HttpFilter中有获取请求参数时报错问题issue#3343@Github
* 【http 】 修复options请求无响应体问题
* 【core 】 ImgUtil的sliceByRowsAndCols背景无法透明问题issue#3347@Github
* 【core 】 修复ClassUtil#scanJar未正确关闭文件问题issue#3361@Github
* 【db 】 修复Column.getDigit返回值错误问题issue#3370@Github
-------------------------------------------------------------------------------------------------------------
# 5.8.22(2023-09-13)

View File

@ -46,6 +46,13 @@
-------------------------------------------------------------------------------
<p align="center">
<a href="#">
<img alt="" src="https://plus.hutool.cn/images/zanzhu.jpg"/></a>
</p>
-------------------------------------------------------------------------------
[**🌎中文说明**](README.md)
-------------------------------------------------------------------------------
@ -144,18 +151,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.22</version>
<version>5.8.23</version>
</dependency>
```
### 🍐Gradle
```
implementation 'cn.hutool:hutool-all:5.8.22'
implementation 'cn.hutool:hutool-all:5.8.23'
```
## 📥Download
- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.22/)
- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.23/)
> 🔔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.
@ -208,10 +215,3 @@ Hutool welcomes anyone to contribute code to Hutool, but the author suffers from
## ⭐Star Hutool
[![Stargazers over time](https://starchart.cc/dromara/hutool.svg)](https://starchart.cc/dromara/hutool)
## 📌WeChat Official Account
<div align="center">
<img src="https://cdn.jsdelivr.net/gh/looly/hutool-site/images/qr_tuling.jpg" height="150">
<img src="https://dromara.org/img/qrcode/qrcode_1.png" height="150">
</div>

View File

@ -46,6 +46,13 @@
-------------------------------------------------------------------------------
<p align="center">
<a href="#">
<img alt="" src="https://plus.hutool.cn/images/zanzhu.jpg"/></a>
</p>
-------------------------------------------------------------------------------
[**🌎English Documentation**](README-EN.md)
-------------------------------------------------------------------------------
@ -137,20 +144,20 @@ Hutool = Hu + tool是原公司项目底层代码剥离后的开源库“Hu
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.22</version>
<version>5.8.23</version>
</dependency>
```
### 🍐Gradle
```
implementation 'cn.hutool:hutool-all:5.8.22'
implementation 'cn.hutool:hutool-all:5.8.23'
```
### 📥下载jar
点击以下链接,下载`hutool-all-X.X.X.jar`即可:
- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.22/)
- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.23/)
> 🔔️注意
> Hutool 5.x支持JDK8+对Android平台没有测试不能保证所有工具类或工具方法可用。
@ -211,9 +218,3 @@ Hutool欢迎任何人为Hutool添砖加瓦贡献代码不过维护者是
## ⭐Star Hutool
[![Stargazers over time](https://starchart.cc/dromara/hutool.svg)](https://starchart.cc/dromara/hutool)
## 📌 知识星球
<div align="center">
<img src="https://hutool.cn/images/dromara/zsxq.jpg" height="150">
</div>

View File

@ -1,3 +1,6 @@
#!/bin/bash
exec mvn javadoc:javadoc
#exec mvn javadoc:javadoc
# 多模块聚合文档生成在target/site/apidocs
exec mvn javadoc:aggregate

View File

@ -1 +1 @@
5.8.22
5.8.23

79
docs/apidocs/index.html Normal file
View File

@ -0,0 +1,79 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
padding: 0;
margin: 0;
border: 0;
}
.left {
position: absolute;
width: 20%;
height: 100%;
}
.top {
height: 10%;
text-align: center;
}
.bottom {
height: 90%;
}
.content {
position: absolute;
width: 80%;
height: 100%;
margin-left: 20%;
}
iframe {
width: 100%;
display: block;
}
</style>
<!-- 百度统计 -->
<script>
var _hmt = _hmt || [];
(function () {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?f2c884fc06fca522c4105429259b8a73";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
</head>
<body>
<div class="left">
<div class="top">
<a href="https://hutool.cn" target="_blank">
<img src="https://plus.hutool.cn/images/hutool.svg" style="height: 100%; width: 100%;" alt="hutool">
</a>
</div>
<div class="bottom">
<iframe src="overview-frame.html" name="packageListFrame" target="packageFrame" title="所有程序包"
style="height: 30%;"></iframe>
<iframe src="allclasses-frame.html" name="packageFrame" target="classFrame"
title="所有类和接口 (除了非静态嵌套类型)"
style="height: 70%;"></iframe>
</div>
</div>
<div class="content">
<iframe src="overview-summary.html" name="classFrame" title="程序包, 类和接口说明" style="height: 100%;"></iframe>
</div>
<script type="text/javascript" charset="UTF-8" src="https://cdn.wwads.cn/js/makemoney.js" async></script>
<!-- wwads -->
<div style="position: fixed; right: 16px; bottom: 45px; z-index: 99999;">
<div class="wwads-cn wwads-vertical" data-id="126" style="max-width:150px"></div>
</div>
</body>
</html>

View File

@ -1 +1 @@
var version = '5.8.22'
var version = '5.8.23'

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.8.22</version>
<version>5.8.23-SNAPSHOT</version>
</parent>
<artifactId>hutool-all</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.8.22</version>
<version>5.8.23-SNAPSHOT</version>
</parent>
<artifactId>hutool-aop</artifactId>

View File

@ -43,10 +43,14 @@ public class CglibInterceptor implements MethodInterceptor, Serializable {
try {
// result = proxy.invokeSuper(obj, args);
result = proxy.invoke(target, args);
} catch (InvocationTargetException e) {
} catch (final Throwable e) {
Throwable throwable = e;
if(throwable instanceof InvocationTargetException){
throwable = ((InvocationTargetException) throwable).getTargetException();
}
// 异常回调只捕获业务代码导致的异常而非反射导致的异常
if (aspect.afterException(target, method, args, e.getTargetException())) {
throw e;
if (aspect.afterException(target, method, args, throwable)) {
throw throwable;
}
}
}

View File

@ -48,9 +48,14 @@ public class SpringCglibInterceptor implements MethodInterceptor, Serializable {
try {
// result = proxy.invokeSuper(obj, args);
result = proxy.invoke(target, args);
} catch (InvocationTargetException e) {
} catch (Throwable e) {
Throwable throwable = e;
if(throwable instanceof InvocationTargetException){
throwable = ((InvocationTargetException) throwable).getTargetException();
}
// 异常回调只捕获业务代码导致的异常而非反射导致的异常
if (aspect.afterException(target, method, args, e.getTargetException())) {
if (aspect.afterException(target, method, args, throwable)) {
throw e;
}
}

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.8.22</version>
<version>5.8.23-SNAPSHOT</version>
</parent>
<artifactId>hutool-bloomFilter</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.8.22</version>
<version>5.8.23-SNAPSHOT</version>
</parent>
<artifactId>hutool-bom</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.8.22</version>
<version>5.8.23-SNAPSHOT</version>
</parent>
<artifactId>hutool-cache</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.8.22</version>
<version>5.8.23-SNAPSHOT</version>
</parent>
<artifactId>hutool-captcha</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.8.22</version>
<version>5.8.23-SNAPSHOT</version>
</parent>
<artifactId>hutool-core</artifactId>

View File

@ -251,7 +251,7 @@ public class Base64 {
* base64解码
*
* @param source 被解码的base64字符串
* @return 被加密后的字符串
* @return 密文解密的结果
* @since 4.3.2
*/
public static String decodeStrGbk(CharSequence source) {
@ -262,7 +262,7 @@ public class Base64 {
* base64解码
*
* @param source 被解码的base64字符串
* @return 被加密后的字符串
* @return 密文解密的结果
*/
public static String decodeStr(CharSequence source) {
return Base64Decoder.decodeStr(source);
@ -273,7 +273,7 @@ public class Base64 {
*
* @param source 被解码的base64字符串
* @param charset 字符集
* @return 被加密后的字符串
* @return 密文解密的结果
*/
public static String decodeStr(CharSequence source, String charset) {
return decodeStr(source, CharsetUtil.charset(charset));
@ -284,7 +284,7 @@ public class Base64 {
*
* @param source 被解码的base64字符串
* @param charset 字符集
* @return 被加密后的字符串
* @return 密文解密的结果
*/
public static String decodeStr(CharSequence source, Charset charset) {
return Base64Decoder.decodeStr(source, charset);

View File

@ -7,7 +7,6 @@ import cn.hutool.core.stream.CollectorUtil;
import cn.hutool.core.stream.StreamUtil;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;

View File

@ -2,7 +2,6 @@ package cn.hutool.core.collection;
import cn.hutool.core.comparator.PinyinComparator;
import cn.hutool.core.comparator.PropertyComparator;
import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Matcher;
import cn.hutool.core.lang.Validator;

View File

@ -1,15 +1,12 @@
package cn.hutool.core.comparator;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.PatternPool;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.*;
import java.io.Serializable;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Pattern;
/**
* 版本比较器<br>
@ -24,6 +21,8 @@ import java.util.List;
public class VersionComparator implements Comparator<String>, Serializable {
private static final long serialVersionUID = 8083701245147495562L;
private static final Pattern PATTERN_PRE_NUMBERS= Pattern.compile("^\\d+");
/** 单例 */
public static final VersionComparator INSTANCE = new VersionComparator();
@ -80,12 +79,15 @@ public class VersionComparator implements Comparator<String>, Serializable {
if (0 == diff) {
diff = v1.compareTo(v2);
}else {
//不同长度的先比较前面的数字前面数字不相等时按数字大小比较数字相等的时候继续按长度比较
int v1Num = Convert.toInt(ReUtil.get(PatternPool.NUMBERS, v1, 0), 0);
int v2Num = Convert.toInt(ReUtil.get(PatternPool.NUMBERS, v2, 0), 0);
int diff1 = v1Num - v2Num;
if (diff1 != 0) {
diff = diff1;
// 不同长度且含有字母
if(!NumberUtil.isNumber(v1) || !NumberUtil.isNumber(v2)){
//不同长度的先比较前面的数字前面数字不相等时按数字大小比较数字相等的时候继续按长度比较类似于 103 > 102a
final int v1Num = Convert.toInt(ReUtil.get(PATTERN_PRE_NUMBERS, v1, 0), 0);
final int v2Num = Convert.toInt(ReUtil.get(PATTERN_PRE_NUMBERS, v2, 0), 0);
final int diff1 = v1Num - v2Num;
if (diff1 != 0) {
diff = diff1;
}
}
}
if(diff != 0) {

View File

@ -3,7 +3,6 @@ package cn.hutool.core.convert;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.convert.impl.*;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.lang.Console;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.lang.TypeReference;

View File

@ -6,7 +6,6 @@ import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.bean.copier.ValueProvider;
import cn.hutool.core.convert.AbstractConverter;
import cn.hutool.core.convert.ConvertException;
import cn.hutool.core.lang.Console;
import cn.hutool.core.map.MapProxy;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;

View File

@ -133,7 +133,7 @@ public class Img implements Serializable {
* @return Img
*/
public static Img from(Image image) {
return new Img(ImgUtil.toBufferedImage(image));
return new Img(ImgUtil.castToBufferedImage(image, ImgUtil.IMAGE_TYPE_JPG));
}
/**

View File

@ -455,11 +455,7 @@ public class ImgUtil {
* @param cols 目标切片列数默认2必须是范围 [1, 20] 之内
*/
public static void sliceByRowsAndCols(File srcImageFile, File destDir, String format, int rows, int cols) {
try {
sliceByRowsAndCols(ImageIO.read(srcImageFile), destDir, format, rows, cols);
} catch (IOException e) {
throw new IORuntimeException(e);
}
sliceByRowsAndCols(read(srcImageFile), destDir, format, rows, cols);
}
/**
@ -491,32 +487,27 @@ public class ImgUtil {
throw new IllegalArgumentException("Destination Dir must be a Directory !");
}
try {
if (rows <= 0 || rows > 20) {
rows = 2; // 切片行数
}
if (cols <= 0 || cols > 20) {
cols = 2; // 切片列数
}
// 读取源图像
final BufferedImage bi = toBufferedImage(srcImage);
int srcWidth = bi.getWidth(); // 源图宽度
int srcHeight = bi.getHeight(); // 源图高度
if (rows <= 0 || rows > 20) {
rows = 2; // 切片行数
}
if (cols <= 0 || cols > 20) {
cols = 2; // 切片列数
}
// 读取源图像
int srcWidth = srcImage.getWidth(null); // 源图宽度
int srcHeight = srcImage.getHeight(null); // 源图高度
int destWidth = NumberUtil.partValue(srcWidth, cols); // 每张切片的宽度
int destHeight = NumberUtil.partValue(srcHeight, rows); // 每张切片的高度
int destWidth = NumberUtil.partValue(srcWidth, cols); // 每张切片的宽度
int destHeight = NumberUtil.partValue(srcHeight, rows); // 每张切片的高度
// 循环建立切片
Image tag;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
tag = cut(bi, new Rectangle(j * destWidth, i * destHeight, destWidth, destHeight));
// 输出为文件
ImageIO.write(toRenderedImage(tag), format, new File(destDir, "_r" + i + "_c" + j + "." + format));
}
// 循环建立切片
Image tag;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
tag = cut(srcImage, new Rectangle(j * destWidth, i * destHeight, destWidth, destHeight));
// 输出为文件
write(tag, new File(destDir, "_r" + i + "_c" + j + "." + format));
}
} catch (IOException e) {
throw new IORuntimeException(e);
}
}
@ -573,8 +564,9 @@ public class ImgUtil {
* @since 4.1.14
*/
public static void convert(Image srcImage, String formatName, ImageOutputStream destImageStream, boolean isSrcPng) {
final BufferedImage src = toBufferedImage(srcImage, isSrcPng ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB);
try {
ImageIO.write(isSrcPng ? copyImage(srcImage, BufferedImage.TYPE_INT_RGB) : toBufferedImage(srcImage), formatName, destImageStream);
ImageIO.write(src, formatName, destImageStream);
} catch (IOException e) {
throw new IORuntimeException(e);
}
@ -1157,13 +1149,11 @@ public class ImgUtil {
* @param img {@link Image}
* @return {@link BufferedImage}
* @since 4.3.2
* @deprecated 改用 {@link #castToRenderedImage(Image, String)}
*/
@Deprecated
public static RenderedImage toRenderedImage(Image img) {
if (img instanceof RenderedImage) {
return (RenderedImage) img;
}
return copyImage(img, BufferedImage.TYPE_INT_RGB);
return castToRenderedImage(img, IMAGE_TYPE_JPG);
}
/**
@ -1172,13 +1162,44 @@ public class ImgUtil {
*
* @param img {@link Image}
* @return {@link BufferedImage}
* @deprecated 改用 {@link #castToBufferedImage(Image, String)}
*/
@Deprecated
public static BufferedImage toBufferedImage(Image img) {
return castToBufferedImage(img, IMAGE_TYPE_JPG);
}
/**
* {@link Image} {@link RenderedImage}<br>
* 首先尝试强转否则新建一个{@link BufferedImage}后重新绘制使用 {@link BufferedImage#TYPE_INT_RGB} 模式
*
* @param img {@link Image}
* @param imageType 目标图片类型例如jpg或png等
* @return {@link BufferedImage}
* @since 4.3.2
*/
public static RenderedImage castToRenderedImage(final Image img, final String imageType) {
if (img instanceof RenderedImage) {
return (RenderedImage) img;
}
return toBufferedImage(img, imageType);
}
/**
* {@link Image} {@link BufferedImage}<br>
* 首先尝试强转否则新建一个{@link BufferedImage}后重新绘制使用 imageType 模式
*
* @param img {@link Image}
* @param imageType 目标图片类型例如jpg或png等
* @return {@link BufferedImage}
*/
public static BufferedImage castToBufferedImage(final Image img, final String imageType) {
if (img instanceof BufferedImage) {
return (BufferedImage) img;
}
return copyImage(img, BufferedImage.TYPE_INT_RGB);
return toBufferedImage(img, imageType);
}
/**
@ -1687,7 +1708,7 @@ public class ImgUtil {
}
writer.setOutput(output);
final RenderedImage renderedImage = toRenderedImage(image);
final RenderedImage renderedImage = castToRenderedImage(image, IMAGE_TYPE_JPG);
// 设置质量
ImageWriteParam imgWriteParams = null;
if (quality > 0 && quality < 1) {

View File

@ -606,7 +606,7 @@ public class GifDecoder {
for (int i = 0; i < 11; i++) {
app.append((char) block[i]);
}
if ("NETSCAPE2.0".equals(app.toString())) {
if ("NETSCAPE2.0".contentEquals(app)) {
readNetscapeExt();
} else {
skip(); // don't care

View File

@ -1,10 +1,10 @@
package cn.hutool.core.io.file;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.file.visitor.CopyVisitor;
import cn.hutool.core.io.file.visitor.DelVisitor;
import cn.hutool.core.io.file.visitor.MoveVisitor;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.CharsetUtil;
@ -20,7 +20,6 @@ import java.nio.charset.Charset;
import java.nio.file.AccessDeniedException;
import java.nio.file.CopyOption;
import java.nio.file.DirectoryStream;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
@ -33,6 +32,7 @@ import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
/**
* NIO中Path对象操作封装
@ -80,11 +80,26 @@ public class PathUtil {
* @since 5.4.1
*/
public static List<File> loopFiles(Path path, int maxDepth, FileFilter fileFilter) {
return loopFiles(path, maxDepth, false, fileFilter);
}
/**
* 递归遍历目录以及子目录中的所有文件<br>
* 如果提供path为文件直接返回过滤结果
*
* @param path 当前遍历文件或目录
* @param maxDepth 遍历最大深度-1表示遍历到没有目录为止
* @param isFollowLinks 是否跟踪软链快捷方式
* @param fileFilter 文件过滤规则对象选择要保留的文件只对文件有效不过滤目录null表示接收全部文件
* @return 文件列表
* @since 5.4.1
*/
public static List<File> loopFiles(final Path path, final int maxDepth, final boolean isFollowLinks, final FileFilter fileFilter) {
final List<File> fileList = new ArrayList<>();
if (null == path || false == Files.exists(path)) {
if (!exists(path, isFollowLinks)) {
return fileList;
} else if (false == isDirectory(path)) {
} else if (!isDirectory(path, isFollowLinks)) {
final File file = path.toFile();
if (null == fileFilter || fileFilter.accept(file)) {
fileList.add(file);
@ -92,10 +107,10 @@ public class PathUtil {
return fileList;
}
walkFiles(path, maxDepth, new SimpleFileVisitor<Path>() {
walkFiles(path, maxDepth, isFollowLinks, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) {
public FileVisitResult visitFile(final Path path, final BasicFileAttributes attrs) {
final File file = path.toFile();
if (null == fileFilter || fileFilter.accept(file)) {
fileList.add(file);
@ -129,14 +144,28 @@ public class PathUtil {
* @since 4.6.3
*/
public static void walkFiles(Path start, int maxDepth, FileVisitor<? super Path> visitor) {
walkFiles(start, maxDepth, false, visitor);
}
/**
* 遍历指定path下的文件并做处理
*
* @param start 起始路径必须为目录
* @param maxDepth 最大遍历深度-1表示不限制深度
* @param visitor {@link FileVisitor} 接口用于自定义在访问文件时访问目录前后等节点做的操作
* @param isFollowLinks 是否追踪到软链对应的真实地址
* @see Files#walkFileTree(Path, java.util.Set, int, FileVisitor)
* @since 5.8.23
*/
public static void walkFiles(final Path start, int maxDepth, final boolean isFollowLinks, final FileVisitor<? super Path> visitor) {
if (maxDepth < 0) {
// < 0 表示遍历到最底层
maxDepth = Integer.MAX_VALUE;
}
try {
Files.walkFileTree(start, EnumSet.noneOf(FileVisitOption.class), maxDepth, visitor);
} catch (IOException e) {
Files.walkFileTree(start, getFileVisitOption(isFollowLinks), maxDepth, visitor);
} catch (final IOException e) {
throw new IORuntimeException(e);
}
}
@ -283,8 +312,7 @@ public class PathUtil {
if (null == path) {
return false;
}
final LinkOption[] options = isFollowLinks ? new LinkOption[0] : new LinkOption[]{LinkOption.NOFOLLOW_LINKS};
return Files.isDirectory(path, options);
return Files.isDirectory(path, getLinkOptions(isFollowLinks));
}
/**
@ -368,9 +396,8 @@ public class PathUtil {
return null;
}
final LinkOption[] options = isFollowLinks ? new LinkOption[0] : new LinkOption[]{LinkOption.NOFOLLOW_LINKS};
try {
return Files.readAttributes(path, BasicFileAttributes.class, options);
return Files.readAttributes(path, BasicFileAttributes.class, getLinkOptions(isFollowLinks));
} catch (IOException e) {
throw new IORuntimeException(e);
}
@ -541,8 +568,7 @@ public class PathUtil {
if (null == path) {
return false;
}
final LinkOption[] options = isFollowLinks ? new LinkOption[0] : new LinkOption[]{LinkOption.NOFOLLOW_LINKS};
return Files.isRegularFile(path, options);
return Files.isRegularFile(path, getLinkOptions(isFollowLinks));
}
/**
@ -565,8 +591,7 @@ public class PathUtil {
* @since 5.5.3
*/
public static boolean exists(Path path, boolean isFollowLinks) {
final LinkOption[] options = isFollowLinks ? new LinkOption[0] : new LinkOption[]{LinkOption.NOFOLLOW_LINKS};
return Files.exists(path, options);
return Files.exists(path, getLinkOptions(isFollowLinks));
}
/**
@ -714,4 +739,27 @@ public class PathUtil {
}
}
}
/**
* 构建是否追踪软链的选项
*
* @param isFollowLinks 是否追踪软链
* @return 选项
* @since 5.8.23
*/
public static LinkOption[] getLinkOptions(final boolean isFollowLinks) {
return isFollowLinks ? new LinkOption[0] : new LinkOption[]{LinkOption.NOFOLLOW_LINKS};
}
/**
* 构建是否追踪软链的选项
*
* @param isFollowLinks 是否追踪软链
* @return 选项
* @since 5.8.23
*/
public static Set<FileVisitOption> getFileVisitOption(final boolean isFollowLinks) {
return isFollowLinks ? EnumSet.of(FileVisitOption.FOLLOW_LINKS) :
EnumSet.noneOf(FileVisitOption.class);
}
}

View File

@ -1,14 +1,12 @@
package cn.hutool.core.io.resource;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.URLUtil;
import java.io.*;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
/**
* URL资源访问类

View File

@ -185,7 +185,7 @@ public final class DataSize implements Comparable<DataSize> {
public static DataSize parse(CharSequence text, DataUnit defaultUnit) {
Assert.notNull(text, "Text must not be null");
try {
final Matcher matcher = PATTERN.matcher(text);
final Matcher matcher = PATTERN.matcher(StrUtil.cleanBlank(text));
Assert.state(matcher.matches(), "Does not match data size pattern");
final DataUnit unit = determineDataUnit(matcher.group(3), defaultUnit);

View File

@ -5,6 +5,7 @@ import cn.hutool.core.collection.EnumerationIter;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.util.*;
@ -356,22 +357,26 @@ public class ClassScanner implements Serializable {
}
/**
* 扫描jar包
* 扫描jar包扫描结束后关闭jar文件
*
* @param jar jar包
*/
private void scanJar(JarFile jar) {
String name;
for (JarEntry entry : new EnumerationIter<>(jar.entries())) {
name = StrUtil.removePrefix(entry.getName(), StrUtil.SLASH);
if (StrUtil.isEmpty(packagePath) || name.startsWith(this.packagePath)) {
if (name.endsWith(FileUtil.CLASS_EXT) && false == entry.isDirectory()) {
final String className = name//
try{
String name;
for (JarEntry entry : new EnumerationIter<>(jar.entries())) {
name = StrUtil.removePrefix(entry.getName(), StrUtil.SLASH);
if (StrUtil.isEmpty(packagePath) || name.startsWith(this.packagePath)) {
if (name.endsWith(FileUtil.CLASS_EXT) && false == entry.isDirectory()) {
final String className = name//
.substring(0, name.length() - 6)//
.replace(CharUtil.SLASH, CharUtil.DOT);//
addIfAccept(loadClass(className));
addIfAccept(loadClass(className));
}
}
}
} finally {
IoUtil.close(jar);
}
}

View File

@ -198,6 +198,40 @@ public class Snowflake implements Serializable {
return (id >> TIMESTAMP_LEFT_SHIFT & ~(-1L << 41L)) + twepoch;
}
/**
* 根据传入时间戳-计算ID起终点
*
* @param timestampStart 开始时间戳
* @param timestampEnd 结束时间戳
* @return key-ID起点Value-ID终点
* @since 5.8.23
*/
public Pair<Long, Long> getIdScopeByTimestamp(long timestampStart, long timestampEnd) {
return getIdScopeByTimestamp(timestampStart, timestampEnd, true);
}
/**
* 根据传入时间戳-计算ID起终点 Gitee/issues/I60M14
*
* @param timestampStart 开始时间戳
* @param timestampEnd 结束时间戳
* @param ignoreCenterAndWorker 是否忽略数据中心和机器节点的占位忽略后可获得分布式环境全局可信赖的起终点
* @return key-ID起点Value-ID终点
* @since 5.8.23
*/
public Pair<Long, Long> getIdScopeByTimestamp(long timestampStart, long timestampEnd, boolean ignoreCenterAndWorker) {
long startTimeMinId = (timestampStart - twepoch) << TIMESTAMP_LEFT_SHIFT;
long endTimeMinId = (timestampEnd - twepoch) << TIMESTAMP_LEFT_SHIFT;
if (ignoreCenterAndWorker) {
long endId = endTimeMinId | ~(-1 << TIMESTAMP_LEFT_SHIFT);
return Pair.of(startTimeMinId, endId);
} else {
long startId = startTimeMinId | (dataCenterId << DATA_CENTER_ID_SHIFT) | (workerId << WORKER_ID_SHIFT);
long endId = endTimeMinId | (dataCenterId << DATA_CENTER_ID_SHIFT) | (workerId << WORKER_ID_SHIFT) | SEQUENCE_MASK;
return Pair.of(startId, endId);
}
}
/**
* 下一个ID
*

View File

@ -30,7 +30,13 @@ public class BiMap<K, V> extends MapWrapper<K, V> {
@Override
public V put(K key, V value) {
final V oldValue = super.put(key, value);
if (null != this.inverse) {
if(null != oldValue){
// issue#I88R5M
// 如果put的key相同value不同需要在inverse中移除旧的关联
this.inverse.remove(oldValue);
}
this.inverse.put(value, key);
}
return super.put(key, value);

View File

@ -5,7 +5,6 @@ import cn.hutool.core.map.MapWrapper;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**

View File

@ -24,12 +24,18 @@ import java.util.*;
/**
* 网络相关工具
*
* @author xiaoleilu
* @author looly
*/
public class NetUtil {
/**
* 本地IPv4地址
*/
public final static String LOCAL_IP = Ipv4Util.LOCAL_IP;
/**
* 本地主机名称
*/
public static String localhostName;
/**
@ -500,8 +506,8 @@ public class NetUtil {
final LinkedHashSet<InetAddress> localAddressList = localAddressList(address -> {
// 非loopback地址指127.*.*.*的地址
return false == address.isLoopbackAddress()
// 需为IPV4地址
&& address instanceof Inet4Address;
// 需为IPV4地址
&& address instanceof Inet4Address;
});
if (CollUtil.isNotEmpty(localAddressList)) {

View File

@ -0,0 +1,85 @@
package cn.hutool.core.net;
import javax.net.SocketFactory;
import java.io.IOException;
import java.net.*;
/**
* 代理Socket工厂用于创建代理Socket<br>
* 来自commons-net的DefaultSocketFactory
*
* @author commons-net, looly
* @since 5.8.23
*/
public class ProxySocketFactory extends SocketFactory {
/**
* 创建代理SocketFactory
* @param proxy 代理对象
* @return {@code ProxySocketFactory}
*/
public static ProxySocketFactory of(final Proxy proxy) {
return new ProxySocketFactory(proxy);
}
private final Proxy proxy;
/**
* 构造
*
* @param proxy Socket代理
*/
public ProxySocketFactory(final Proxy proxy) {
this.proxy = proxy;
}
@Override
public Socket createSocket() {
if (proxy != null) {
return new Socket(proxy);
}
return new Socket();
}
@Override
public Socket createSocket(final InetAddress address, final int port) throws IOException {
if (proxy != null) {
final Socket s = new Socket(proxy);
s.connect(new InetSocketAddress(address, port));
return s;
}
return new Socket(address, port);
}
@Override
public Socket createSocket(final InetAddress address, final int port, final InetAddress localAddr, final int localPort) throws IOException {
if (proxy != null) {
final Socket s = new Socket(proxy);
s.bind(new InetSocketAddress(localAddr, localPort));
s.connect(new InetSocketAddress(address, port));
return s;
}
return new Socket(address, port, localAddr, localPort);
}
@Override
public Socket createSocket(final String host, final int port) throws IOException {
if (proxy != null) {
final Socket s = new Socket(proxy);
s.connect(new InetSocketAddress(host, port));
return s;
}
return new Socket(host, port);
}
@Override
public Socket createSocket(final String host, final int port, final InetAddress localAddr, final int localPort) throws IOException {
if (proxy != null) {
final Socket s = new Socket(proxy);
s.bind(new InetSocketAddress(localAddr, localPort));
s.connect(new InetSocketAddress(host, port));
return s;
}
return new Socket(host, port, localAddr, localPort);
}
}

View File

@ -1,6 +1,5 @@
package cn.hutool.core.net;
import cn.hutool.core.lang.Console;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;

View File

@ -187,7 +187,7 @@ public class ReflectUtil {
* 如果子类与父类中存在同名字段则这两个字段同时存在子类字段在前父类字段在后
*
* @param beanClass
* @param fieldFilter field过滤器过滤掉不需要的field
* @param fieldFilter field过滤器过滤掉不需要的field{@code null}返回原集合
* @return 字段列表
* @throws SecurityException 安全检查异常
* @since 5.7.14
@ -286,14 +286,22 @@ public class ReflectUtil {
* @since 4.1.17
*/
public static Object[] getFieldsValue(Object obj) {
return getFieldsValue(obj, null);
}
/**
* 获取所有字段的值
*
* @param obj bean对象如果是static字段此处为类class
* @param filter 字段过滤器{@code null}返回原集合
* @return 字段值数组
* @since 5.8.23
*/
public static Object[] getFieldsValue(Object obj, Filter<Field> filter) {
if (null != obj) {
final Field[] fields = getFields(obj instanceof Class ? (Class<?>) obj : obj.getClass());
final Field[] fields = getFields(obj instanceof Class ? (Class<?>) obj : obj.getClass(), filter);
if (null != fields) {
final Object[] values = new Object[fields.length];
for (int i = 0; i < fields.length; i++) {
values[i] = getFieldValue(obj, fields[i]);
}
return values;
return ArrayUtil.map(fields, Object.class, field -> getFieldValue(obj, field));
}
}
return null;

View File

@ -1,6 +1,5 @@
package cn.hutool.core.util;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.lang.ParameterizedTypeImpl;
import cn.hutool.core.lang.reflect.ActualTypeMapperPool;
@ -41,7 +40,10 @@ public class TypeUtil {
} else if (type instanceof ParameterizedType) {
return (Class<?>) ((ParameterizedType) type).getRawType();
} else if (type instanceof TypeVariable) {
return (Class<?>) ((TypeVariable<?>) type).getBounds()[0];
Type[] bounds = ((TypeVariable<?>) type).getBounds();
if (bounds.length == 1) {
return getClass(bounds[0]);
}
} else if (type instanceof WildcardType) {
final Type[] upperBounds = ((WildcardType) type).getUpperBounds();
if (upperBounds.length == 1) {

View File

@ -29,20 +29,20 @@ public class AnnotationUtilTest {
final AnnotationForTest[] annotations = AnnotationUtil.getCombinationAnnotations(ClassWithAnnotation.class, AnnotationForTest.class);
Assert.assertNotNull(annotations);
Assert.assertEquals(1, annotations.length);
Assert.assertEquals("测试", annotations[0].value());
Assert.assertTrue(annotations[0].value().equals("测试") || annotations[0].value().equals("repeat-annotation"));
}
@Test
public void getAnnotationValueTest() {
final Object value = AnnotationUtil.getAnnotationValue(ClassWithAnnotation.class, AnnotationForTest.class);
Assert.assertEquals("测试", value);
Assert.assertTrue(value.equals("测试") || value.equals("repeat-annotation"));
}
@Test
public void getAnnotationValueTest2() {
final String[] names = AnnotationUtil.getAnnotationValue(ClassWithAnnotation.class, AnnotationForTest::names);
Assert.assertTrue(ArrayUtil.equals(names, new String[]{"测试1", "测试2"}));
Assert.assertTrue(names.length == 1 && names[0].isEmpty() || ArrayUtil.equals(names, new String[]{"测试1", "测试2"}));
}
@Test
@ -52,7 +52,8 @@ public class AnnotationUtilTest {
// 加别名适配
final AnnotationForTest annotation = AnnotationUtil.getAnnotationAlias(ClassWithAnnotation.class, AnnotationForTest.class);
Assert.assertEquals("测试", annotation.retry());
String retryValue = annotation.retry();
Assert.assertTrue(retryValue.equals("测试") || retryValue.equals("repeat-annotation"));
Assert.assertTrue(AnnotationUtil.isSynthesizedAnnotation(annotation));
}
@ -78,9 +79,12 @@ public class AnnotationUtilTest {
// -> RootMetaAnnotation3
final List<Annotation> annotations = AnnotationUtil.scanMetaAnnotation(RootAnnotation.class);
Assert.assertEquals(4, annotations.size());
Assert.assertEquals(RootMetaAnnotation3.class, annotations.get(0).annotationType());
Assert.assertEquals(RootMetaAnnotation1.class, annotations.get(1).annotationType());
Assert.assertEquals(RootMetaAnnotation2.class, annotations.get(2).annotationType());
Assert.assertTrue(annotations.get(0).annotationType() == RootMetaAnnotation3.class ||
annotations.get(0).annotationType() == RootMetaAnnotation1.class);
Assert.assertTrue(annotations.get(1).annotationType() == RootMetaAnnotation1.class ||
annotations.get(1).annotationType() == RootMetaAnnotation2.class);
Assert.assertTrue(annotations.get(2).annotationType() == RootMetaAnnotation2.class ||
annotations.get(2).annotationType() == RootMetaAnnotation3.class);
Assert.assertEquals(RootMetaAnnotation3.class, annotations.get(3).annotationType());
}

View File

@ -15,36 +15,59 @@ public class VersionComparatorTest {
public void versionComparatorTest1() {
int compare = VersionComparator.INSTANCE.compare("1.2.1", "1.12.1");
Assert.assertTrue(compare < 0);
// 自反测试
compare = VersionComparator.INSTANCE.compare("1.12.1", "1.2.1");
Assert.assertTrue(compare > 0);
}
@Test
public void versionComparatorTest2() {
int compare = VersionComparator.INSTANCE.compare("1.12.1", "1.12.1c");
Assert.assertTrue(compare < 0);
compare = VersionComparator.INSTANCE.compare("1.12.1c", "1.12.1");
Assert.assertTrue(compare > 0);
}
@Test
public void versionComparatorTest3() {
int compare = VersionComparator.INSTANCE.compare(null, "1.12.1c");
Assert.assertTrue(compare < 0);
// 自反测试
compare = VersionComparator.INSTANCE.compare("1.12.1c", null);
Assert.assertTrue(compare > 0);
}
@Test
public void versionComparatorTest4() {
int compare = VersionComparator.INSTANCE.compare("1.13.0", "1.12.1c");
Assert.assertTrue(compare > 0);
// 自反测试
compare = VersionComparator.INSTANCE.compare("1.12.1c", "1.13.0");
Assert.assertTrue(compare < 0);
}
@Test
public void versionComparatorTest5() {
int compare = VersionComparator.INSTANCE.compare("V1.2", "V1.1");
Assert.assertTrue(compare > 0);
// 自反测试
compare = VersionComparator.INSTANCE.compare("V1.1", "V1.2");
Assert.assertTrue(compare < 0);
}
@Test
public void versionComparatorTes6() {
int compare = VersionComparator.INSTANCE.compare("V0.0.20170102", "V0.0.20170101");
Assert.assertTrue(compare > 0);
// 自反测试
compare = VersionComparator.INSTANCE.compare("V0.0.20170101", "V0.0.20170102");
Assert.assertTrue(compare < 0);
}
@Test
@ -58,5 +81,9 @@ public class VersionComparatorTest {
public void versionComparatorTest7() {
int compare = VersionComparator.INSTANCE.compare("1.12.2", "1.12.1c");
Assert.assertTrue(compare > 0);
// 自反测试
compare = VersionComparator.INSTANCE.compare("1.12.1c", "1.12.2");
Assert.assertTrue(compare < 0);
}
}

View File

@ -0,0 +1,16 @@
package cn.hutool.core.date;
import cn.hutool.core.lang.Console;
import org.junit.Assert;
import org.junit.Test;
public class Issue3348Test {
@Test
public void formatChineseDateTest() {
final String formatChineseDate = DateUtil.formatChineseDate(
DateUtil.parse("2023-10-23"), true, false);
Console.log(formatChineseDate);
Assert.assertEquals("二〇二三年十月二十三日", formatChineseDate);
}
}

View File

@ -0,0 +1,12 @@
package cn.hutool.core.date;
import org.junit.Assert;
import org.junit.Test;
public class IssueI82Y1LTest {
@Test
public void parseTest() {
final String dt1 = "2023-09-14T05:00:03.648519Z";
Assert.assertEquals("2023-09-14 05:10:51", DateUtil.parse(dt1).toString());
}
}

View File

@ -48,7 +48,9 @@ public class ImgUtilTest {
@Test
@Ignore
public void cutTest() {
ImgUtil.cut(FileUtil.file("d:/face.jpg"), FileUtil.file("d:/face_result.jpg"), new Rectangle(200, 200, 100, 100));
ImgUtil.cut(FileUtil.file("d:/test/hutool.png"),
FileUtil.file("d:/test/result.png"),
new Rectangle(0, 0, 400, 240));
}
@Test
@ -92,6 +94,14 @@ public class ImgUtilTest {
ImgUtil.sliceByRowsAndCols(FileUtil.file("d:/temp/2.png"), FileUtil.file("d:/temp/slice/png"),ImgUtil.IMAGE_TYPE_PNG, 1, 5);
}
@Test
@Ignore
public void sliceByRowsAndColsTest2() {
ImgUtil.sliceByRowsAndCols(
FileUtil.file("d:/test/hutool.png"),
FileUtil.file("d:/test/dest"), ImgUtil.IMAGE_TYPE_PNG, 1, 5);
}
@Test
@Ignore
public void convertTest() {

View File

@ -5,7 +5,6 @@ import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import java.io.File;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;

View File

@ -60,4 +60,11 @@ public class DataSizeUtilTest {
format = DataSizeUtil.format(1024L * 1024 * 1024 * 1024);
Assert.assertEquals("1 TB", format);
}
@Test
public void issueI88Z4ZTest() {
final String size = DataSizeUtil.format(10240000);
final long bytes = DataSize.parse(size).toBytes();
Assert.assertEquals(10244587, bytes);
}
}

View File

@ -4,6 +4,7 @@ import cn.hutool.core.collection.ConcurrentHashSet;
import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import org.junit.Assert;
import org.junit.Ignore;
@ -19,22 +20,45 @@ import java.util.Set;
*/
public class SnowflakeTest {
/**
* 测试-根据传入时间戳-计算ID起终点
*/
@Test
public void snowflakeTestGetIdScope() {
final long workerId = RandomUtil.randomLong(31);
final long dataCenterId = RandomUtil.randomLong(31);
final Snowflake idWorker = new Snowflake(workerId, dataCenterId);
final long generatedId = idWorker.nextId();
// 随机忽略数据中心和工作机器的占位
final boolean ignore = RandomUtil.randomBoolean();
final long createTimestamp = idWorker.getGenerateDateTime(generatedId);
final Pair<Long, Long> idScope = idWorker.getIdScopeByTimestamp(createTimestamp, createTimestamp, ignore);
final long startId = idScope.getKey();
final long endId = idScope.getValue();
// 起点终点相差比较
final long trueOffSet = endId - startId;
// 忽略数据中心和工作机器时差值为22个1否则为12个1
final long expectedOffSet = ignore ? ~(-1 << 22) : ~(-1 << 12);
Assert.assertEquals(trueOffSet, expectedOffSet);
}
@Test
public void snowflakeTest1(){
//构建Snowflake提供终端ID和数据中心ID
Snowflake idWorker = new Snowflake(0, 0);
long nextId = idWorker.nextId();
final Snowflake idWorker = new Snowflake(0, 0);
final long nextId = idWorker.nextId();
Assert.assertTrue(nextId > 0);
}
@Test
public void snowflakeTest(){
HashSet<Long> hashSet = new HashSet<>();
final HashSet<Long> hashSet = new HashSet<>();
//构建Snowflake提供终端ID和数据中心ID
Snowflake idWorker = new Snowflake(0, 0);
final Snowflake idWorker = new Snowflake(0, 0);
for (int i = 0; i < 1000; i++) {
long id = idWorker.nextId();
final long id = idWorker.nextId();
hashSet.add(id);
}
Assert.assertEquals(1000L, hashSet.size());
@ -43,8 +67,8 @@ public class SnowflakeTest {
@Test
public void snowflakeGetTest(){
//构建Snowflake提供终端ID和数据中心ID
Snowflake idWorker = new Snowflake(1, 2);
long nextId = idWorker.nextId();
final Snowflake idWorker = new Snowflake(1, 2);
final long nextId = idWorker.nextId();
Assert.assertEquals(1, idWorker.getWorkerId(nextId));
Assert.assertEquals(2, idWorker.getDataCenterId(nextId));
@ -55,9 +79,9 @@ public class SnowflakeTest {
@Ignore
public void uniqueTest(){
// 测试并发环境下生成ID是否重复
Snowflake snowflake = IdUtil.getSnowflake(0, 0);
final Snowflake snowflake = IdUtil.getSnowflake(0, 0);
Set<Long> ids = new ConcurrentHashSet<>();
final Set<Long> ids = new ConcurrentHashSet<>();
ThreadUtil.concurrencyTest(100, () -> {
for (int i = 0; i < 50000; i++) {
if(false == ids.add(snowflake.nextId())){
@ -94,7 +118,7 @@ public class SnowflakeTest {
final Snowflake snowflake = new Snowflake(null, 0, 0,
false, Snowflake.DEFAULT_TIME_OFFSET, 100);
Set<Long> ids = new ConcurrentHashSet<>();
final Set<Long> ids = new ConcurrentHashSet<>();
ThreadUtil.concurrencyTest(100, () -> {
for (int i = 0; i < 50000; i++) {
if(false == ids.add(snowflake.nextId())){

View File

@ -0,0 +1,17 @@
package cn.hutool.core.map;
import org.junit.Assert;
import org.junit.Test;
import java.util.LinkedHashMap;
public class IssueI88R5MTest {
@Test
public void biMapTest() {
final BiMap<String, Integer> biMap = new BiMap<>(new LinkedHashMap<>());
biMap.put("aaa", 111);
biMap.getKey(111);
biMap.put("aaa", 222);
Assert.assertNull(biMap.getKey(111));
}
}

View File

@ -1,7 +1,6 @@
package cn.hutool.core.map;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Console;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.util.StrUtil;

View File

@ -1,6 +1,5 @@
package cn.hutool.core.text;
import cn.hutool.core.lang.Console;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.CharUtil;
import org.junit.Assert;

View File

@ -148,4 +148,9 @@ public class IdcardUtilTest {
flag = IdcardUtil.isValidTWCard(errTwCard2);
Assert.assertFalse(flag);
}
@Test
public void issueI88YKMTest() {
Assert.assertTrue(IdcardUtil.isValidCard("111111111111111"));
}
}

View File

@ -1,48 +1,61 @@
package cn.hutool.core.util;
import lombok.Data;
import org.junit.Assert;
import org.junit.Test;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import lombok.Data;
import org.junit.Assert;
import org.junit.Test;
public class TypeUtilTest {
@Test
public void getEleTypeTest() {
Method method = ReflectUtil.getMethod(TestClass.class, "getList");
Type type = TypeUtil.getReturnType(method);
Assert.assertEquals("java.util.List<java.lang.String>", type.toString());
Type type2 = TypeUtil.getTypeArgument(type);
Assert.assertEquals(String.class, type2);
}
@Test
public void getParamTypeTest() {
Method method = ReflectUtil.getMethod(TestClass.class, "intTest", Integer.class);
Type type = TypeUtil.getParamType(method, 0);
Assert.assertEquals(Integer.class, type);
Type returnType = TypeUtil.getReturnType(method);
Assert.assertEquals(Integer.class, returnType);
}
@Test
public void getClasses() {
Method method = ReflectUtil.getMethod(Parent.class, "getLevel");
Type returnType = TypeUtil.getReturnType(method);
Class clazz = TypeUtil.getClass(returnType);
Assert.assertEquals(Level1.class, clazz);
method = ReflectUtil.getMethod(Level1.class, "getId");
returnType = TypeUtil.getReturnType(method);
clazz = TypeUtil.getClass(returnType);
Assert.assertEquals(Object.class, clazz);
}
public static class TestClass {
public List<String> getList(){
public List<String> getList() {
return new ArrayList<>();
}
public Integer intTest(Integer integer) {
return 1;
}
}
@Test
public void getTypeArgumentTest(){
public void getTypeArgumentTest() {
// 测试不继承父类而是实现泛型接口时是否可以获取成功
final Type typeArgument = TypeUtil.getTypeArgument(IPService.class);
Assert.assertEquals(String.class, typeArgument);
@ -59,25 +72,29 @@ public class TypeUtilTest {
}
@Test
public void getActualTypesTest(){
public void getActualTypesTest() {
// 测试多层级泛型参数是否能获取成功
Type idType = TypeUtil.getActualType(Level3.class,
ReflectUtil.getField(Level3.class, "id"));
Type idType = TypeUtil.getActualType(Level3.class, ReflectUtil.getField(Level3.class, "id"));
Assert.assertEquals(Long.class, idType);
}
public static class Level3 extends Level2<Level3>{
public static class Level3 extends Level2<Level3> {
}
public static class Level2<E> extends Level1<Long>{
public static class Level2<E> extends Level1<Long> {
}
@Data
public static class Level1<T>{
public static class Level1<T> {
private T id;
}
@Data
public static class Parent<T extends Level1<B>, B extends Long> {
private T level;
}
}

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.8.22</version>
<version>5.8.23-SNAPSHOT</version>
</parent>
<artifactId>hutool-cron</artifactId>

View File

@ -89,7 +89,7 @@ public enum Part {
*/
public int checkValue(int value) throws CronException {
Assert.checkBetween(value, min, max,
() -> new CronException("Value {} out of range: [{} , {}]", value, min, max));
() -> new CronException("{} value {} out of range: [{} , {}]", this.name(), value, min, max));
return value;
}

View File

@ -3,16 +3,11 @@ package cn.hutool.cron.pattern.parser;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.Month;
import cn.hutool.core.date.Week;
import cn.hutool.core.lang.Console;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.cron.CronException;
import cn.hutool.cron.pattern.Part;
import cn.hutool.cron.pattern.matcher.AlwaysTrueMatcher;
import cn.hutool.cron.pattern.matcher.BoolArrayMatcher;
import cn.hutool.cron.pattern.matcher.DayOfMonthMatcher;
import cn.hutool.cron.pattern.matcher.PartMatcher;
import cn.hutool.cron.pattern.matcher.YearValueMatcher;
import cn.hutool.cron.pattern.matcher.*;
import java.util.ArrayList;
import java.util.List;
@ -204,13 +199,11 @@ public class PartParser {
//在range模式下如果步进不存在表示步进为1
step = 1;
}
if (v1 < v2) {// 正常范围例如2-5
if (v1 <= v2) {// 正常范围例如2-53-3
NumberUtil.appendRange(v1, v2, step, results);
} else if (v1 > v2) {// 逆向范围反选模式例如5-2
} else {// 逆向范围反选模式例如5-2
NumberUtil.appendRange(v1, part.getMax(), step, results);
NumberUtil.appendRange(part.getMin(), v2, step, results);
} else {// v1 == v2此时与单值模式一致
NumberUtil.appendRange(v1, part.getMax(), step, results);
}
} else {
throw new CronException("Invalid syntax of field: [{}]", value);

View File

@ -0,0 +1,21 @@
package cn.hutool.cron.pattern;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import org.junit.Assert;
import org.junit.Test;
import java.util.Date;
import java.util.List;
public class IssueI82CSHTest {
@Test
public void test() {
final DateTime begin = DateUtil.parse("2023-09-20");
final DateTime end = DateUtil.parse("2025-09-20");
final List<Date> dates = CronPatternUtil.matchedDates("0 0 1 3-3,9 *", begin, end, 20, false);
//dates.forEach(Console::log);
Assert.assertEquals(4, dates.size());
}
}

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.8.22</version>
<version>5.8.23-SNAPSHOT</version>
</parent>
<artifactId>hutool-crypto</artifactId>

View File

@ -1,6 +1,5 @@
package cn.hutool.crypto;
import cn.hutool.crypto.BCUtil;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.junit.Assert;

View File

@ -1,8 +1,5 @@
package cn.hutool.crypto;
import cn.hutool.crypto.CryptoException;
import cn.hutool.crypto.GlobalBouncyCastleProvider;
import cn.hutool.crypto.KeyUtil;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.8.22</version>
<version>5.8.23-SNAPSHOT</version>
</parent>
<artifactId>hutool-db</artifactId>

View File

@ -258,7 +258,7 @@ public class Column implements Serializable, Cloneable {
*
* @return 大小或数据长度
*/
public int getDigit() {
public Integer getDigit() {
return digit;
}

View File

@ -1,7 +1,6 @@
package cn.hutool.db;
import cn.hutool.core.lang.Console;
import cn.hutool.core.util.ArrayUtil;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Ignore;

View File

@ -4,9 +4,7 @@ import cn.hutool.core.util.RandomUtil;
import org.junit.Assert;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
import static cn.hutool.db.dialect.DriverNamePool.*;

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.8.22</version>
<version>5.8.23-SNAPSHOT</version>
</parent>
<artifactId>hutool-dfa</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.8.22</version>
<version>5.8.23-SNAPSHOT</version>
</parent>
<artifactId>hutool-extra</artifactId>
@ -464,7 +464,7 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.22</version>
<version>1.24.0</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.8.22</version>
<version>5.8.23-SNAPSHOT</version>
</parent>
<artifactId>hutool-http</artifactId>

View File

@ -8,7 +8,6 @@ import cn.hutool.core.io.resource.FileResource;
import cn.hutool.core.io.resource.MultiFileResource;
import cn.hutool.core.io.resource.Resource;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Console;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.map.TableMap;
import cn.hutool.core.net.SSLUtil;
@ -1405,7 +1404,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
/**
* 是否忽略读取响应body部分<br>
* HEADCONNECTOPTIONSTRACE方法将不读取响应体
* HEADCONNECTTRACE方法将不读取响应体
*
* @return 是否需要忽略响应body部分
* @since 3.1.2
@ -1413,7 +1412,6 @@ public class HttpRequest extends HttpBase<HttpRequest> {
private boolean isIgnoreResponseBody() {
return Method.HEAD == this.method //
|| Method.CONNECT == this.method //
|| Method.OPTIONS == this.method //
|| Method.TRACE == this.method;
}

View File

@ -0,0 +1,148 @@
package cn.hutool.http.server;
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpContext;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpPrincipal;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.URI;
/**
* {@link HttpExchange}包装类提供增强方法和缓存
*
* @author looly
*/
public class HttpExchangeWrapper extends HttpExchange {
private final HttpExchange raw;
private final HttpServerRequest request;
private final HttpServerResponse response;
/**
* 构造
*
* @param raw {@link HttpExchange}
*/
public HttpExchangeWrapper(final HttpExchange raw) {
this.raw = raw;
this.request = new HttpServerRequest(this);
this.response = new HttpServerResponse(this);
}
/**
* 获取原始对象
* @return 对象
*/
public HttpExchange getRaw() {
return this.raw;
}
/**
* 获取请求
*
* @return 请求
*/
public HttpServerRequest getRequest() {
return request;
}
/**
* 获取响应
*
* @return 响应
*/
public HttpServerResponse getResponse() {
return response;
}
// region ----- HttpExchange methods
@Override
public Headers getRequestHeaders() {
return this.raw.getRequestHeaders();
}
@Override
public Headers getResponseHeaders() {
return this.raw.getResponseHeaders();
}
@Override
public URI getRequestURI() {
return this.raw.getRequestURI();
}
@Override
public String getRequestMethod() {
return this.raw.getRequestMethod();
}
@Override
public HttpContext getHttpContext() {
return this.raw.getHttpContext();
}
@Override
public void close() {
this.raw.close();
}
@Override
public InputStream getRequestBody() {
return this.raw.getRequestBody();
}
@Override
public OutputStream getResponseBody() {
return this.raw.getResponseBody();
}
@Override
public void sendResponseHeaders(final int rCode, final long responseLength) throws IOException {
this.raw.sendResponseHeaders(rCode, responseLength);
}
@Override
public InetSocketAddress getRemoteAddress() {
return this.raw.getRemoteAddress();
}
@Override
public int getResponseCode() {
return this.raw.getResponseCode();
}
@Override
public InetSocketAddress getLocalAddress() {
return this.raw.getLocalAddress();
}
@Override
public String getProtocol() {
return this.raw.getProtocol();
}
@Override
public Object getAttribute(final String name) {
return this.raw.getAttribute(name);
}
@Override
public void setAttribute(final String name, final Object value) {
this.raw.setAttribute(name, value);
}
@Override
public void setStreams(final InputStream i, final OutputStream o) {
this.raw.setStreams(i, o);
}
@Override
public HttpPrincipal getPrincipal() {
return this.raw.getPrincipal();
}
// endregion
}

View File

@ -126,7 +126,8 @@ public class SimpleServer {
return addFilter(new SimpleFilter() {
@Override
public void doFilter(HttpExchange httpExchange, Chain chain) throws IOException {
filter.doFilter(new HttpServerRequest(httpExchange), new HttpServerResponse(httpExchange), chain);
final HttpExchangeWrapper httpExchangeWrapper = new HttpExchangeWrapper(httpExchange);
filter.doFilter(httpExchangeWrapper.getRequest(), httpExchangeWrapper.getResponse(), chain);
}
});
}

View File

@ -1,5 +1,6 @@
package cn.hutool.http.server.handler;
import cn.hutool.http.server.HttpExchangeWrapper;
import cn.hutool.http.server.HttpServerRequest;
import cn.hutool.http.server.HttpServerResponse;
import cn.hutool.http.server.action.Action;
@ -29,10 +30,18 @@ public class ActionHandler implements HttpHandler {
@Override
public void handle(HttpExchange httpExchange) throws IOException {
action.doAction(
new HttpServerRequest(httpExchange),
new HttpServerResponse(httpExchange)
);
final HttpServerRequest request;
final HttpServerResponse response;
if (httpExchange instanceof HttpExchangeWrapper) {
// issue#3343 当使用Filter时可能读取了请求参数此时使用共享的req和res可复用缓存
final HttpExchangeWrapper wrapper = (HttpExchangeWrapper) httpExchange;
request = wrapper.getRequest();
response = wrapper.getResponse();
} else {
request = new HttpServerRequest(httpExchange);
response = new HttpServerResponse(httpExchange);
}
action.doAction(request, response);
httpExchange.close();
}
}

View File

@ -28,54 +28,56 @@ public class Browser extends UserAgentInfo {
* 支持的浏览器类型
*/
public static final List<Browser> browers = CollUtil.newArrayList(
// 部分特殊浏览器是基于安卓Iphone等的需要优先判断
// 企业微信 企业微信使用微信浏览器内核,会包含 MicroMessenger 所以要放在前面
new Browser("wxwork", "wxwork", "wxwork\\/([\\d\\w\\.\\-]+)"),
// 微信
new Browser("MicroMessenger", "MicroMessenger", Other_Version),
// 微信小程序
new Browser("miniProgram", "miniProgram", Other_Version),
// QQ浏览器
new Browser("QQBrowser", "MQQBrowser", "MQQBrowser\\/([\\d\\w\\.\\-]+)"),
// 钉钉PC端浏览器
new Browser("DingTalk-win", "dingtalk-win", "DingTalk\\(([\\d\\w\\.\\-]+)\\)"),
// 钉钉内置浏览器
new Browser("DingTalk", "DingTalk", "AliApp\\(DingTalk\\/([\\d\\w\\.\\-]+)\\)"),
// 支付宝内置浏览器
new Browser("Alipay", "AlipayClient", "AliApp\\(AP\\/([\\d\\w\\.\\-]+)\\)"),
// 淘宝内置浏览器
new Browser("Taobao", "taobao", "AliApp\\(TB\\/([\\d\\w\\.\\-]+)\\)"),
// UC浏览器
new Browser("UCBrowser", "UC?Browser", "UC?Browser\\/([\\d\\w\\.\\-]+)"),
// XiaoMi 浏览器
new Browser("MiuiBrowser", "MiuiBrowser|mibrowser", "MiuiBrowser\\/([\\d\\w\\.\\-]+)"),
// 夸克浏览器
new Browser("Quark", "Quark", Other_Version),
// 联想浏览器
new Browser("Lenovo", "SLBrowser", "SLBrowser/([\\d\\w\\.\\-]+)"),
new Browser("MSEdge", "Edge|Edg", "(?:edge|Edg|EdgA)\\/([\\d\\w\\.\\-]+)"),
new Browser("Chrome", "chrome|(iphone.*crios.*safari)", "(?:Chrome|CriOS)\\/([\\d\\w\\.\\-]+)"),
new Browser("Firefox", "firefox", Other_Version),
new Browser("IEMobile", "iemobile", Other_Version),
new Browser("Android Browser", "android", "version\\/([\\d\\w\\.\\-]+)"),
new Browser("Safari", "safari", "version\\/([\\d\\w\\.\\-]+)"),
new Browser("Opera", "opera", Other_Version),
new Browser("Konqueror", "konqueror", Other_Version),
new Browser("PS3", "playstation 3", "([\\d\\w\\.\\-]+)\\)\\s*$"),
new Browser("PSP", "playstation portable", "([\\d\\w\\.\\-]+)\\)?\\s*$"),
new Browser("Lotus", "lotus.notes", "Lotus-Notes\\/([\\w.]+)"),
new Browser("Thunderbird", "thunderbird", Other_Version),
new Browser("Netscape", "netscape", Other_Version),
new Browser("Seamonkey", "seamonkey", Other_Version),
new Browser("Outlook", "microsoft.outlook", Other_Version),
new Browser("Evolution", "evolution", Other_Version),
new Browser("MSIE", "msie", "msie ([\\d\\w\\.\\-]+)"),
new Browser("MSIE11", "rv:11", "rv:([\\d\\w\\.\\-]+)"),
new Browser("Gabble", "Gabble", Other_Version),
new Browser("Yammer Desktop", "AdobeAir", "([\\d\\w\\.\\-]+)\\/Yammer"),
new Browser("Yammer Mobile", "Yammer[\\s]+([\\d\\w\\.\\-]+)", "Yammer[\\s]+([\\d\\w\\.\\-]+)"),
new Browser("Apache HTTP Client", "Apache\\\\-HttpClient", "Apache\\-HttpClient\\/([\\d\\w\\.\\-]+)"),
new Browser("BlackBerry", "BlackBerry", "BlackBerry[\\d]+\\/([\\d\\w\\.\\-]+)")
// 部分特殊浏览器是基于安卓Iphone等的需要优先判断
// 企业微信 企业微信使用微信浏览器内核,会包含 MicroMessenger 所以要放在前面
new Browser("wxwork", "wxwork", "wxwork\\/([\\d\\w\\.\\-]+)"),
// 微信
new Browser("MicroMessenger", "MicroMessenger", Other_Version),
// 微信小程序
new Browser("miniProgram", "miniProgram", Other_Version),
// QQ浏览器
new Browser("QQBrowser", "MQQBrowser", "MQQBrowser\\/([\\d\\w\\.\\-]+)"),
// 钉钉PC端浏览器
new Browser("DingTalk-win", "dingtalk-win", "DingTalk\\(([\\d\\w\\.\\-]+)\\)"),
// 钉钉内置浏览器
new Browser("DingTalk", "DingTalk", "AliApp\\(DingTalk\\/([\\d\\w\\.\\-]+)\\)"),
// 支付宝内置浏览器
new Browser("Alipay", "AlipayClient", "AliApp\\(AP\\/([\\d\\w\\.\\-]+)\\)"),
// 淘宝内置浏览器
new Browser("Taobao", "taobao", "AliApp\\(TB\\/([\\d\\w\\.\\-]+)\\)"),
// UC浏览器
new Browser("UCBrowser", "UC?Browser", "UC?Browser\\/([\\d\\w\\.\\-]+)"),
// XiaoMi 浏览器
new Browser("MiuiBrowser", "MiuiBrowser|mibrowser", "MiuiBrowser\\/([\\d\\w\\.\\-]+)"),
// 夸克浏览器
new Browser("Quark", "Quark", Other_Version),
// 联想浏览器
new Browser("Lenovo", "SLBrowser", "SLBrowser/([\\d\\w\\.\\-]+)"),
new Browser("MSEdge", "Edge|Edg", "(?:edge|Edg|EdgA)\\/([\\d\\w\\.\\-]+)"),
new Browser("Chrome", "chrome|(iphone.*crios.*safari)", "(?:Chrome|CriOS)\\/([\\d\\w\\.\\-]+)"),
new Browser("Firefox", "firefox", Other_Version),
new Browser("IEMobile", "iemobile", Other_Version),
new Browser("Android Browser", "android", "version\\/([\\d\\w\\.\\-]+)"),
new Browser("Safari", "safari", "version\\/([\\d\\w\\.\\-]+)"),
new Browser("Opera", "opera", Other_Version),
new Browser("Konqueror", "konqueror", Other_Version),
new Browser("PS3", "playstation 3", "([\\d\\w\\.\\-]+)\\)\\s*$"),
new Browser("PSP", "playstation portable", "([\\d\\w\\.\\-]+)\\)?\\s*$"),
new Browser("Lotus", "lotus.notes", "Lotus-Notes\\/([\\w.]+)"),
new Browser("Thunderbird", "thunderbird", Other_Version),
new Browser("Netscape", "netscape", Other_Version),
new Browser("Seamonkey", "seamonkey", Other_Version),
new Browser("Outlook", "microsoft.outlook", Other_Version),
new Browser("Evolution", "evolution", Other_Version),
new Browser("MSIE", "msie", "msie ([\\d\\w\\.\\-]+)"),
new Browser("MSIE11", "rv:11", "rv:([\\d\\w\\.\\-]+)"),
new Browser("Gabble", "Gabble", Other_Version),
new Browser("Yammer Desktop", "AdobeAir", "([\\d\\w\\.\\-]+)\\/Yammer"),
new Browser("Yammer Mobile", "Yammer[\\s]+([\\d\\w\\.\\-]+)", "Yammer[\\s]+([\\d\\w\\.\\-]+)"),
new Browser("Apache HTTP Client", "Apache\\\\-HttpClient", "Apache\\-HttpClient\\/([\\d\\w\\.\\-]+)"),
new Browser("BlackBerry", "BlackBerry", "BlackBerry[\\d]+\\/([\\d\\w\\.\\-]+)"),
// issue#I847JY 百度浏览器
new Browser("Baidu", "Baidu", "baiduboxapp\\/([\\d\\w\\.\\-]+)")
);
/**
@ -116,7 +118,7 @@ public class Browser extends UserAgentInfo {
* @return 版本
*/
public String getVersion(String userAgentString) {
if(isUnknown()){
if (isUnknown()) {
return null;
}
return ReUtil.getGroup1(this.versionPattern, userAgentString);
@ -130,11 +132,11 @@ public class Browser extends UserAgentInfo {
public boolean isMobile() {
final String name = this.getName();
return "PSP".equals(name) ||
"Yammer Mobile".equals(name) ||
"Android Browser".equals(name) ||
"IEMobile".equals(name) ||
"MicroMessenger".equals(name) ||
"miniProgram".equals(name) ||
"DingTalk".equals(name);
"Yammer Mobile".equals(name) ||
"Android Browser".equals(name) ||
"IEMobile".equals(name) ||
"MicroMessenger".equals(name) ||
"miniProgram".equals(name) ||
"DingTalk".equals(name);
}
}

View File

@ -0,0 +1,21 @@
package cn.hutool.http;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Console;
import org.junit.Ignore;
import org.junit.Test;
public class Issue3314Test {
@Test
@Ignore
public void postTest() {
String url = "https://hutool.cn/test/getList";
final String body = HttpRequest.get(url)
.setRest(true)
.body(FileUtil.readBytes("d:/test/3314.xlsx"))
.execute()
.body();
Console.log(body);
}
}

View File

@ -0,0 +1,33 @@
package cn.hutool.http.server;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Console;
import cn.hutool.core.map.multi.ListValueMap;
import cn.hutool.http.HttpUtil;
/**
* http://localhost:8888/?name=hutool
*/
public class Issue3343Test {
public static void main(final String[] args) {
final SimpleServer server = HttpUtil.createServer(8888)
.addFilter((req, res, chain) -> {
Console.log(DateUtil.now() + " got request: " + req.getPath());
Console.log(" > from : " + req.getClientIP());
// 过滤器中获取请求参数
Console.log(" > params : " + req.getParams());
chain.doFilter(req.getHttpExchange());
});
server.addAction("/", Issue3343Test::index);
server.start();
}
private static void index(HttpServerRequest request, HttpServerResponse response) {
// 具体逻辑中再次获取请求参数
ListValueMap<String, String> params = request.getParams();
Console.log("index params: " + params);
response.getWriter().write("GOT: " + params);
}
}

View File

@ -456,4 +456,20 @@ public class UserAgentUtilTest {
Assert.assertEquals("iPhone", ua2.getPlatform().toString());
Assert.assertTrue(ua2.isMobile());
}
@Test
public void issueI847JYTest() {
final String s = "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) " +
"Mobile/15E148 SP-engine/2.80.0 main%2F1.0 baiduboxapp/13.42.0.11 (Baidu; P2 17.0) NABar/1.0 themeUA=Them";
final UserAgent ua2 = UserAgentUtil.parse(s);
Assert.assertEquals("Baidu", ua2.getBrowser().toString());
Assert.assertEquals("13.42.0.11", ua2.getVersion());
Assert.assertEquals("Webkit", ua2.getEngine().toString());
Assert.assertEquals("605.1.15", ua2.getEngineVersion());
Assert.assertEquals("iPhone", ua2.getOs().toString());
Assert.assertEquals("17_0", ua2.getOsVersion());
Assert.assertEquals("iPhone", ua2.getPlatform().toString());
Assert.assertTrue(ua2.isMobile());
}
}

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.8.22</version>
<version>5.8.23-SNAPSHOT</version>
</parent>
<artifactId>hutool-json</artifactId>

View File

@ -1,6 +1,5 @@
package cn.hutool.json;
import cn.hutool.core.lang.Console;
import cn.hutool.core.lang.Filter;
import cn.hutool.core.lang.mutable.Mutable;
import cn.hutool.core.lang.mutable.MutablePair;

View File

@ -42,10 +42,12 @@ public class JSONStrFormatter {
if (null == wrapChar) {
//字符串模式开始
wrapChar = key;
} else if (isEscapeMode) {
//在字符串模式下的转义
isEscapeMode = false;
} else if (wrapChar.equals(key)) {
if (isEscapeMode) {
//字符串模式下遇到结束符号也同时结束转义
isEscapeMode = false;
}
//字符串包装结束
wrapChar = null;
}

View File

@ -1,5 +1,6 @@
package cn.hutool.json.serialize;
import cn.hutool.core.lang.Assert;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONException;
import cn.hutool.json.JSONObject;
@ -7,6 +8,7 @@ import cn.hutool.json.JSONObject;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;
import java.time.temporal.TemporalAccessor;
/**
@ -61,11 +63,33 @@ public class TemporalAccessorSerializer implements JSONObjectSerializer<Temporal
@Override
public TemporalAccessor deserialize(JSON json) {
final JSONObject jsonObject = (JSONObject) json;
if (LocalDate.class.equals(this.temporalAccessorClass)) {
return LocalDate.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY));
} else if (LocalDateTime.class.equals(this.temporalAccessorClass)) {
return LocalDateTime.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY),
jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY));
if (LocalDate.class.equals(this.temporalAccessorClass) || LocalDateTime.class.equals(this.temporalAccessorClass)) {
final Integer year = jsonObject.getInt(YEAR_KEY);
Assert.notNull(year, "Field 'year' must be not null");
Integer month = jsonObject.getInt(MONTH_KEY);
if (null == month) {
final Month monthEnum = Month.valueOf(jsonObject.getStr(MONTH_KEY));
Assert.notNull(monthEnum, "Field 'month' must be not null");
month = monthEnum.getValue();
}
Integer day = jsonObject.getInt(DAY_KEY);
if (null == day) {
day = jsonObject.getInt("dayOfMonth");
Assert.notNull(day, "Field 'day' or 'dayOfMonth' must be not null");
}
final LocalDate localDate = LocalDate.of(year, month, day);
if (LocalDate.class.equals(this.temporalAccessorClass)) {
return localDate;
}
final LocalTime localTime = LocalTime.of(
jsonObject.getInt(HOUR_KEY, 0),
jsonObject.getInt(MINUTE_KEY, 0),
jsonObject.getInt(SECOND_KEY, 0),
jsonObject.getInt(NANO_KEY, 0));
return LocalDateTime.of(localDate, localTime);
} else if (LocalTime.class.equals(this.temporalAccessorClass)) {
return LocalTime.of(jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY));
}

View File

@ -1,6 +1,5 @@
package cn.hutool.json;
import cn.hutool.json.JSONUtil;
import lombok.Data;
import org.junit.Assert;
import org.junit.Test;

View File

@ -1,9 +1,5 @@
package cn.hutool.json;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONConfig;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.hutool.json.serialize.JSONDeserializer;
import cn.hutool.json.serialize.JSONObjectSerializer;
import lombok.Data;

View File

@ -10,9 +10,8 @@
* See the Mulan PSL v2 for more details.
*/
package cn.hutool.json;import cn.hutool.json.JSONConfig;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
package cn.hutool.json;
import lombok.Data;
import org.junit.Assert;
import org.junit.Test;

View File

@ -12,7 +12,6 @@
package cn.hutool.json;
import cn.hutool.core.lang.Console;
import cn.hutool.core.util.XmlUtil;
import lombok.Data;
import org.junit.Assert;

View File

@ -12,8 +12,6 @@
package cn.hutool.json;
import cn.hutool.core.lang.Console;
import cn.hutool.core.lang.TypeReference;
import lombok.Data;
import org.junit.Assert;
import org.junit.Test;

View File

@ -0,0 +1,43 @@
package cn.hutool.json;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.lang.TypeReference;
import lombok.Data;
import org.junit.Assert;
import org.junit.Test;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
public class IssueI82AM8Test {
@Test
public void toBeanTest() {
final String json = ResourceUtil.readUtf8Str("issueI82AM8.json");
Map<String, MedicalCenter.MedicalCenterLocalized> bean1 =
JSONUtil.toBean(json, new TypeReference<Map<String, MedicalCenter.MedicalCenterLocalized>>() {
}, false);
bean1.forEach((k, v) -> Assert.assertNotNull(v.getTestimonials()));
}
// 对象
@Data
public static class MedicalCenter {
private Map<String, MedicalCenterLocalized> medicalCenterLocalized;
@Data
public static class MedicalCenterLocalized {
private List<Testimonial> testimonials;
@Data
public static class Testimonial {
private LocalDateTime createTime;
}
}
}
}

View File

@ -0,0 +1,18 @@
package cn.hutool.json;
import org.junit.Assert;
import org.junit.Test;
public class IssueI84V6ITest {
@Test
public void formatTest() {
final String a1 = "{'x':'\\n','y':','}";
final String formatJsonStr = JSONUtil.formatJsonStr(a1);
// Console.log(formatJsonStr);
Assert.assertEquals(
"{\n" +
" 'x': '\\n',\n" +
" 'y': ','\n" +
"}", formatJsonStr);
}
}

View File

@ -0,0 +1,46 @@
{
"en": {
"testimonials": [
{
"createTime": {
"dayOfYear": 261,
"dayOfWeek": "MONDAY",
"year": 2023,
"month": "SEPTEMBER",
"nano": 0,
"monthValue": 9,
"dayOfMonth": 18,
"hour": 15,
"minute": 18,
"second": 0,
"chronology": {
"id": "ISO",
"calendarType": "iso8601"
}
}
}
]
},
"zh": {
"testimonials": [
{
"createTime": {
"dayOfYear": 261,
"dayOfWeek": "MONDAY",
"year": 2023,
"month": "SEPTEMBER",
"nano": 0,
"monthValue": 9,
"dayOfMonth": 18,
"hour": 15,
"minute": 18,
"second": 0,
"chronology": {
"id": "ISO",
"calendarType": "iso8601"
}
}
}
]
}
}

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.8.22</version>
<version>5.8.23-SNAPSHOT</version>
</parent>
<artifactId>hutool-jwt</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.8.22</version>
<version>5.8.23-SNAPSHOT</version>
</parent>
<artifactId>hutool-log</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.8.22</version>
<version>5.8.23-SNAPSHOT</version>
</parent>
<artifactId>hutool-poi</artifactId>

View File

@ -12,7 +12,6 @@ import org.apache.poi.ss.util.CellRangeUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

View File

@ -1,12 +1,9 @@
package cn.hutool.poi.excel.sax;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.log.StaticLog;
import cn.hutool.poi.excel.sax.handler.RowHandler;
import cn.hutool.poi.exceptions.POIException;
import org.apache.poi.hssf.eventusermodel.EventWorkbookBuilder.SheetRecordCollectingListener;

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.8.22</version>
<version>5.8.23-SNAPSHOT</version>
</parent>
<artifactId>hutool-script</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.8.22</version>
<version>5.8.23-SNAPSHOT</version>
</parent>
<artifactId>hutool-setting</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.8.22</version>
<version>5.8.23-SNAPSHOT</version>
</parent>
<artifactId>hutool-socket</artifactId>

View File

@ -3,7 +3,6 @@ package cn.hutool.socket.nio;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.log.Log;
import cn.hutool.log.StaticLog;
import java.io.Closeable;
import java.io.IOException;
@ -16,7 +15,7 @@ import java.util.Iterator;
/**
* 基于NIO的Socket服务端实现
*
*
* @author looly
*
*/
@ -31,7 +30,7 @@ public class NioServer implements Closeable {
/**
* 构造
*
*
* @param port 端口
*/
public NioServer(int port) {
@ -40,7 +39,7 @@ public class NioServer implements Closeable {
/**
* 初始化
*
*
* @param address 地址和端口
* @return this
*/
@ -109,7 +108,7 @@ public class NioServer implements Closeable {
/**
* 开始监听
*
*
* @throws IOException IO异常
*/
private void doListen() throws IOException {
@ -125,7 +124,7 @@ public class NioServer implements Closeable {
/**
* 处理SelectionKey
*
*
* @param key SelectionKey
*/
private void handle(SelectionKey key) {

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.8.22</version>
<version>5.8.23-SNAPSHOT</version>
</parent>
<artifactId>hutool-system</artifactId>

View File

@ -8,7 +8,7 @@
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.8.22</version>
<version>5.8.23-SNAPSHOT</version>
<name>hutool</name>
<description>Hutool是一个小而全的Java工具类库通过静态方法封装降低相关API的学习成本提高工作效率使Java拥有函数式语言般的优雅让Java语言也可以“甜甜的”。</description>
<url>https://github.com/dromara/hutool</url>
@ -46,7 +46,7 @@
<!-- versions -->
<compile.version>8</compile.version>
<junit.version>5.9.2</junit.version>
<lombok.version>1.18.26</lombok.version>
<lombok.version>1.18.28</lombok.version>
</properties>
<dependencies>