From 065c6d61c890980e4f6669d371ac1cd098731ecf Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 6 Nov 2021 18:23:50 +0800 Subject: [PATCH 01/43] add class --- .../cn/hutool/core/codec/PercentCodec.java | 9 +- .../java/cn/hutool/core/lang/RegexPool.java | 5 + .../cn/hutool/core/net/FormUrlencoded.java | 25 +++++ .../main/java/cn/hutool/core/net/RFC3986.java | 26 +++-- .../java/cn/hutool/core/net/URLDecoder.java | 5 +- .../cn/hutool/core/net/URLEncodeUtil.java | 3 +- .../java/cn/hutool/core/net/url/UrlPath.java | 3 + .../java/cn/hutool/core/net/url/UrlQuery.java | 97 +++++++++++-------- .../java/cn/hutool/core/util/URLUtil.java | 3 +- .../java/cn/hutool/core/net/UrlQueryTest.java | 15 +++ .../main/java/cn/hutool/http/HttpUtil.java | 6 +- 11 files changed, 137 insertions(+), 60 deletions(-) create mode 100644 hutool-core/src/main/java/cn/hutool/core/net/FormUrlencoded.java diff --git a/hutool-core/src/main/java/cn/hutool/core/codec/PercentCodec.java b/hutool-core/src/main/java/cn/hutool/core/codec/PercentCodec.java index 91faa74f6..3cb1a51d5 100644 --- a/hutool-core/src/main/java/cn/hutool/core/codec/PercentCodec.java +++ b/hutool-core/src/main/java/cn/hutool/core/codec/PercentCodec.java @@ -62,8 +62,11 @@ public class PercentCodec implements Serializable { * 存放安全编码 */ private final BitSet safeCharacters; + /** - * 是否编码空格为+ + * 是否编码空格为+
+ * 如果为{@code true},则将空格编码为"+",此项只在"application/x-www-form-urlencoded"中使用
+ * 如果为{@code false},则空格编码为"%20",此项一般用于URL的Query部分(RFC3986规范) */ private boolean encodeSpaceAsPlus = false; @@ -130,7 +133,9 @@ public class PercentCodec implements Serializable { } /** - * 是否将空格编码为+ + * 是否将空格编码为+
+ * 如果为{@code true},则将空格编码为"+",此项只在"application/x-www-form-urlencoded"中使用
+ * 如果为{@code false},则空格编码为"%20",此项一般用于URL的Query部分(RFC3986规范) * * @param encodeSpaceAsPlus 是否将空格编码为+ * @return this diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/RegexPool.java b/hutool-core/src/main/java/cn/hutool/core/lang/RegexPool.java index 330775887..7d1295e0c 100755 --- a/hutool-core/src/main/java/cn/hutool/core/lang/RegexPool.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/RegexPool.java @@ -100,6 +100,11 @@ public interface RegexPool { * 生日 */ String BIRTHDAY = "^(\\d{2,4})([/\\-.年]?)(\\d{1,2})([/\\-.月]?)(\\d{1,2})日?$"; + /** + * URI
+ * 定义见:https://www.ietf.org/rfc/rfc3986.html#appendix-B + */ + String URI = "^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?"; /** * URL */ diff --git a/hutool-core/src/main/java/cn/hutool/core/net/FormUrlencoded.java b/hutool-core/src/main/java/cn/hutool/core/net/FormUrlencoded.java new file mode 100644 index 000000000..5de8513f2 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/net/FormUrlencoded.java @@ -0,0 +1,25 @@ +package cn.hutool.core.net; + +import cn.hutool.core.codec.PercentCodec; + +/** + * application/x-www-form-urlencoded,遵循W3C HTML Form content types规范,如空格须转+,+须被编码
+ * 规范见:https://url.spec.whatwg.org/#urlencoded-serializing + * + * @since 5.7.16 + */ +public class FormUrlencoded { + + /** + * query中的value
+ * value不能包含"{@code &}",可以包含 "=" + */ + public static final PercentCodec QUERY_PARAM_VALUE = PercentCodec.of(RFC3986.QUERY_PARAM_VALUE) + .setEncodeSpaceAsPlus(true).removeSafe('+'); + + /** + * query中的key
+ * key不能包含"{@code &}" 和 "=" + */ + public static final PercentCodec QUERY_PARAM_NAME = QUERY_PARAM_VALUE.removeSafe('='); +} diff --git a/hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java b/hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java index 713b082c2..a40764932 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java @@ -3,7 +3,8 @@ package cn.hutool.core.net; import cn.hutool.core.codec.PercentCodec; /** - * rfc3986 : https://www.ietf.org/rfc/rfc3986.html 编码实现 + * rfc3986 : https://www.ietf.org/rfc/rfc3986.html 编码实现
+ * 定义见:https://www.ietf.org/rfc/rfc3986.html#appendix-A * * @author looly * @since 5.7.16 @@ -21,12 +22,14 @@ public class RFC3986 { public static final PercentCodec SUB_DELIMS = PercentCodec.of("!$&'()*+,;="); /** - * reserved = gen-delims / sub-delims + * reserved = gen-delims / sub-delims
+ * see:https://www.ietf.org/rfc/rfc3986.html#section-2.2 */ public static final PercentCodec RESERVED = GEN_DELIMS.orNew(SUB_DELIMS); /** - * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
+ * see: https://www.ietf.org/rfc/rfc3986.html#section-2.3 */ public static final PercentCodec UNRESERVED = PercentCodec.of(unreservedChars()); @@ -36,7 +39,8 @@ public class RFC3986 { public static final PercentCodec PCHAR = UNRESERVED.orNew(SUB_DELIMS).or(PercentCodec.of(":@")); /** - * segment = pchar + * segment = pchar
+ * see: https://www.ietf.org/rfc/rfc3986.html#section-3.3 */ public static final PercentCodec SEGMENT = PCHAR; /** @@ -60,15 +64,17 @@ public class RFC3986 { public static final PercentCodec FRAGMENT = QUERY; /** - * query中的key - */ - public static final PercentCodec QUERY_PARAM_NAME = PercentCodec.of(QUERY).removeSafe('&').removeSafe('='); - - /** - * query中的value + * query中的value
+ * value不能包含"{@code &}",可以包含 "=" */ public static final PercentCodec QUERY_PARAM_VALUE = PercentCodec.of(QUERY).removeSafe('&'); + /** + * query中的key
+ * key不能包含"{@code &}" 和 "=" + */ + public static final PercentCodec QUERY_PARAM_NAME = QUERY_PARAM_VALUE.removeSafe('='); + /** * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" * diff --git a/hutool-core/src/main/java/cn/hutool/core/net/URLDecoder.java b/hutool-core/src/main/java/cn/hutool/core/net/URLDecoder.java index 8ae9bc59f..bd0adc849 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/URLDecoder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/URLDecoder.java @@ -41,9 +41,10 @@ public class URLDecoder implements Serializable { } /** - * 解码 + * 解码
+ * 规则见:https://url.spec.whatwg.org/#urlencoded-parsing *
-	 *   1. 将+和%20转换为空格 ;
+	 *   1. 将+和%20转换为空格(" ");
 	 *   2. 将"%xy"转换为文本形式,xy是两位16进制的数值;
 	 *   3. 跳过不符合规范的%形式,直接输出
 	 * 
diff --git a/hutool-core/src/main/java/cn/hutool/core/net/URLEncodeUtil.java b/hutool-core/src/main/java/cn/hutool/core/net/URLEncodeUtil.java index 94d462bd7..cc01cd212 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/URLEncodeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/URLEncodeUtil.java @@ -7,7 +7,8 @@ import cn.hutool.core.util.StrUtil; import java.nio.charset.Charset; /** - * URL编码工具 + * URL编码工具
+ * TODO 在6.x中移除此工具(无法很好区分URL编码和www-form编码) * * @since 5.7.13 * @author looly diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java index bc91edf9b..890c14b66 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java @@ -127,6 +127,9 @@ public class UrlPath { final StringBuilder builder = new StringBuilder(); for (String segment : segments) { + // 根据https://www.ietf.org/rfc/rfc3986.html#section-3.3定义 + // path的第一部分允许有":",其余部分不允许 + // 在此处的Path部分特指host之后的部分,即不包含第一部分 builder.append(CharUtil.SLASH).append(RFC3986.SEGMENT_NZ_NC.encode(segment, charset)); } if (withEngTag || StrUtil.isEmpty(builder)) { diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java index cd7fff640..536b08c0d 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java @@ -144,48 +144,7 @@ public class UrlQuery { } } - final int len = queryStr.length(); - String name = null; - int pos = 0; // 未处理字符开始位置 - int i; // 未处理字符结束位置 - char c; // 当前字符 - for (i = 0; i < len; i++) { - c = queryStr.charAt(i); - switch (c) { - case '='://键和值的分界符 - if (null == name) { - // name可以是"" - name = queryStr.substring(pos, i); - // 开始位置从分节符后开始 - pos = i + 1; - } - // 当=不作为分界符时,按照普通字符对待 - break; - case '&'://键值对之间的分界符 - addParam(name, queryStr.substring(pos, i), charset); - name = null; - if (i + 4 < len && "amp;".equals(queryStr.substring(i + 1, i + 5))) { - // issue#850@Github,"&"转义为"&" - i += 4; - } - // 开始位置从分节符后开始 - pos = i + 1; - break; - } - } - - if (i - pos == len) { - // 没有任何参数符号 - if (queryStr.startsWith("http") || queryStr.contains("/")) { - // 可能为url路径,忽略之 - return this; - } - } - - // 处理结尾 - addParam(name, queryStr.substring(pos, i), charset); - - return this; + return doParse(queryStr, charset); } /** @@ -250,6 +209,60 @@ public class UrlQuery { return build(null); } + /** + * 解析URL中的查询字符串
+ * 规则见:https://url.spec.whatwg.org/#urlencoded-parsing + * + * @param queryStr 查询字符串,类似于key1=v1&key2=&key3=v3 + * @param charset decode编码,null表示不做decode + * @return this + * @since 5.5.8 + */ + private UrlQuery doParse(String queryStr, Charset charset) { + final int len = queryStr.length(); + String name = null; + int pos = 0; // 未处理字符开始位置 + int i; // 未处理字符结束位置 + char c; // 当前字符 + for (i = 0; i < len; i++) { + c = queryStr.charAt(i); + switch (c) { + case '='://键和值的分界符 + if (null == name) { + // name可以是"" + name = queryStr.substring(pos, i); + // 开始位置从分节符后开始 + pos = i + 1; + } + // 当=不作为分界符时,按照普通字符对待 + break; + case '&'://键值对之间的分界符 + addParam(name, queryStr.substring(pos, i), charset); + name = null; + if (i + 4 < len && "amp;".equals(queryStr.substring(i + 1, i + 5))) { + // issue#850@Github,"&"转义为"&" + i += 4; + } + // 开始位置从分节符后开始 + pos = i + 1; + break; + } + } + + if (i - pos == len) { + // 没有任何参数符号 + if (queryStr.startsWith("http") || queryStr.contains("/")) { + // 可能为url路径,忽略之 + return this; + } + } + + // 处理结尾 + addParam(name, queryStr.substring(pos, i), charset); + + return this; + } + /** * 对象转换为字符串,用于URL的Query中 * diff --git a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java index 045fc72b8..5745bce89 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java @@ -319,7 +319,8 @@ public class URLUtil extends URLEncodeUtil { /** * 解码application/x-www-form-urlencoded字符
- * 将%开头的16进制表示的内容解码。 + * 将%开头的16进制表示的内容解码。
+ * 规则见:https://url.spec.whatwg.org/#urlencoded-parsing * * @param content 被解码内容 * @param charset 编码,null表示不解码 diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java index 85d11ec00..ca5f9581a 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java @@ -3,6 +3,7 @@ package cn.hutool.core.net; import cn.hutool.core.map.MapUtil; import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.net.url.UrlQuery; +import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.URLUtil; import org.junit.Assert; import org.junit.Test; @@ -99,4 +100,18 @@ public class UrlQueryTest { query = URLUtil.buildQuery(map, StandardCharsets.UTF_8); Assert.assertEquals("password==&username%3D=SSM", query); } + + @Test + public void plusTest(){ + // 根据RFC3986,在URL中,+是安全字符,即此符号不转义 + final String a = UrlQuery.of(MapUtil.of("a+b", "1+2")).build(CharsetUtil.CHARSET_UTF_8); + Assert.assertEquals("a+b=1+2", a); + } + + @Test + public void spaceTest(){ + // 根据RFC3986,在URL中,空格编码为"%20" + final String a = UrlQuery.of(MapUtil.of("a ", " ")).build(CharsetUtil.CHARSET_UTF_8); + Assert.assertEquals("a%20=%20", a); + } } 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 b7f485b0b..644300c47 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java @@ -6,6 +6,7 @@ import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.StreamProgress; import cn.hutool.core.map.MapUtil; +import cn.hutool.core.net.RFC3986; import cn.hutool.core.net.url.UrlQuery; import cn.hutool.core.text.StrBuilder; import cn.hutool.core.util.CharsetUtil; @@ -557,9 +558,10 @@ public class HttpUtil { if (null == name) { // 对于像&a&这类无参数值的字符串,我们将name为a的值设为"" name = paramPart.substring(pos, i); - builder.append(URLUtil.encodeQuery(name, charset)).append('='); + builder.append(RFC3986.QUERY_PARAM_NAME.encode(name, charset)).append('='); } else { - builder.append(URLUtil.encodeQuery(name, charset)).append('=').append(URLUtil.encodeQuery(paramPart.substring(pos, i), charset)).append('&'); + builder.append(RFC3986.QUERY_PARAM_NAME.encode(name, charset)).append('=') + .append(RFC3986.QUERY_PARAM_VALUE.encode(paramPart.substring(pos, i), charset)).append('&'); } name = null; } From 29d503d27475e5926047e5252033c44d5008c589 Mon Sep 17 00:00:00 2001 From: achao Date: Sat, 6 Nov 2021 19:56:40 +0800 Subject: [PATCH 02/43] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=BA=86flattedMap?= =?UTF-8?q?=EF=BC=8C=E5=A2=9E=E5=BC=BA=E4=BA=86=E5=AF=B9=E5=8E=9F=E7=94=9F?= =?UTF-8?q?Optional=E7=9A=84=E9=80=82=E9=85=8D=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E4=BA=86set=E5=87=BD=E6=95=B0=EF=BC=8C=E8=83=BD=E9=80=9A?= =?UTF-8?q?=E8=BF=87=E9=9D=9E=E9=9D=99=E6=80=81=E6=96=B9=E5=BC=8F=E4=BB=A5?= =?UTF-8?q?=E5=8F=8A=E9=93=BE=E5=BC=8F=E8=8E=B7=E5=8F=96=E6=96=B0=E7=9A=84?= =?UTF-8?q?Opt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/cn/hutool/core/lang/Opt.java | 33 +++++++++++++++++++ .../java/cn/hutool/core/lang/OptTest.java | 20 +++++++++++ 2 files changed, 53 insertions(+) diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Opt.java b/hutool-core/src/main/java/cn/hutool/core/lang/Opt.java index 03439091f..707a922dd 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/Opt.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/Opt.java @@ -29,6 +29,7 @@ import cn.hutool.core.util.StrUtil; import java.util.NoSuchElementException; import java.util.Objects; +import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; @@ -125,6 +126,16 @@ public class Opt { return value; } + /** + * 以非静态方式获取一个新的 {@code Opt} + * + * @param value 值 + * @return 新的 {@code Opt} + */ + public Opt set(T value) { + return Opt.ofNullable(value); + } + /** * 判断包裹里元素的值是否存在,存在为 {@code true},否则为{@code false} * @@ -242,6 +253,28 @@ public class Opt { } } + /** + * 如果包裹里的值存在,就执行传入的操作({@link Function#apply})并返回该操作返回值 + * 如果不存在,返回一个空的{@code Opt} + * 和 {@link Opt#map}的区别为 传入的操作返回值必须为 {@link Optional} + * + * @param mapper 值存在时执行的操作 + * @param 操作返回值的类型 + * @return 如果包裹里的值存在,就执行传入的操作({@link Function#apply})并返回该操作返回值 + * 如果不存在,返回一个空的{@code Opt} + * @throws NullPointerException 如果给定的操作为 {@code null}或者给定的操作执行结果为 {@code null},抛出 {@code NPE} + */ + public Opt flattedMap(Function> mapper) { + Objects.requireNonNull(mapper); + if (isEmpty()) { + return empty(); + } else { + @SuppressWarnings("unchecked") + Optional r = (Optional) mapper.apply(value); + return Objects.requireNonNull(ofNullable(r.orElse(null))); + } + } + /** * 如果包裹里元素的值存在,就执行对应的操作,并返回本身 * 如果不存在,返回一个空的{@code Opt} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/OptTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/OptTest.java index 647b86882..07ea9eccb 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/OptTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/OptTest.java @@ -8,6 +8,8 @@ import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; +import java.util.ArrayList; +import java.util.List; import java.util.NoSuchElementException; import java.util.function.Consumer; import java.util.stream.Stream; @@ -132,6 +134,24 @@ public class OptTest { Assert.assertNull(exceptionWithMessage); } + @Test + public void flattedMapTest() { + // 和Optional兼容的flatMap + List userList = new ArrayList<>(); + // 以前,不兼容 +// Opt.ofNullable(userList).map(List::stream).flatMap(Stream::findFirst); + // 现在,兼容 + User user = Opt.ofNullable(userList).map(List::stream).flattedMap(Stream::findFirst).orElseGet(User.builder()::build); + System.out.println(user); + } + + @Test + public void setTest() { + // 我一直在想,既然有get,为什么不能有set呢? + Opt.ofNullable(User.builder().username("ruben").build()).peek(System.out::println) + .set(User.builder().username("hutool").build()).peek(System.out::println); + } + @Data @Builder @NoArgsConstructor From a5adc9ae942d706e206b0437f55d8166f623a565 Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 7 Nov 2021 02:34:03 +0800 Subject: [PATCH 03/43] fix bug --- CHANGELOG.md | 3 +- .../cn/hutool/core/codec/PercentCodec.java | 1 + .../cn/hutool/core/net/FormUrlencoded.java | 14 +++------ .../main/java/cn/hutool/core/net/RFC3986.java | 2 +- .../java/cn/hutool/core/net/URLEncoder.java | 2 ++ .../java/cn/hutool/core/net/url/UrlQuery.java | 31 +++++++++++++++---- .../hutool/core/net/FormUrlencodedTest.java | 17 ++++++++++ .../java/cn/hutool/core/net/RFC3986Test.java | 14 +++++++++ .../cn/hutool/core/net/URLEncoderTest.java | 17 ---------- .../main/java/cn/hutool/http/HttpRequest.java | 5 ++- .../java/cn/hutool/http/HttpRequestTest.java | 1 + 11 files changed, 71 insertions(+), 36 deletions(-) create mode 100644 hutool-core/src/test/java/cn/hutool/core/net/FormUrlencodedTest.java create mode 100644 hutool-core/src/test/java/cn/hutool/core/net/RFC3986Test.java delete mode 100644 hutool-core/src/test/java/cn/hutool/core/net/URLEncoderTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 370dee44b..f1c5df6e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -# 5.7.16 (2021-11-04) +# 5.7.16 (2021-11-07) ### 🐣新特性 * 【core 】 增加DateTime.toLocalDateTime @@ -35,6 +35,7 @@ * 【core 】 修复CompilerUtil.getFileManager参数没有使用的问题(issue#I4FIO6@Gitee) * 【core 】 修复NetUtil.isInRange的cidr判断问题(pr#1917@Github) * 【core 】 修复RegexPool中对URL正则匹配问题(issue#I4GRKD@Gitee) +* 【core 】 修复UrlQuery对于application/x-www-form-urlencoded问题(issue#1931@Github) ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-core/src/main/java/cn/hutool/core/codec/PercentCodec.java b/hutool-core/src/main/java/cn/hutool/core/codec/PercentCodec.java index 3cb1a51d5..71ae0ec8b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/codec/PercentCodec.java +++ b/hutool-core/src/main/java/cn/hutool/core/codec/PercentCodec.java @@ -179,6 +179,7 @@ public class PercentCodec implements Serializable { continue; } + // 兼容双字节的Unicode符处理(如部分emoji) byte[] ba = buf.toByteArray(); for (byte toEncode : ba) { // Converting each byte in the buffer diff --git a/hutool-core/src/main/java/cn/hutool/core/net/FormUrlencoded.java b/hutool-core/src/main/java/cn/hutool/core/net/FormUrlencoded.java index 5de8513f2..1b41f023e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/FormUrlencoded.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/FormUrlencoded.java @@ -11,15 +11,9 @@ import cn.hutool.core.codec.PercentCodec; public class FormUrlencoded { /** - * query中的value
- * value不能包含"{@code &}",可以包含 "=" + * query中的value,默认除"-", "_", ".", "*"外都编码
+ * 这个类似于JDK提供的{@link java.net.URLEncoder} */ - public static final PercentCodec QUERY_PARAM_VALUE = PercentCodec.of(RFC3986.QUERY_PARAM_VALUE) - .setEncodeSpaceAsPlus(true).removeSafe('+'); - - /** - * query中的key
- * key不能包含"{@code &}" 和 "=" - */ - public static final PercentCodec QUERY_PARAM_NAME = QUERY_PARAM_VALUE.removeSafe('='); + public static final PercentCodec ALL = PercentCodec.of(RFC3986.UNRESERVED) + .removeSafe('~').addSafe('*').setEncodeSpaceAsPlus(true); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java b/hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java index a40764932..39e563ea3 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java @@ -73,7 +73,7 @@ public class RFC3986 { * query中的key
* key不能包含"{@code &}" 和 "=" */ - public static final PercentCodec QUERY_PARAM_NAME = QUERY_PARAM_VALUE.removeSafe('='); + public static final PercentCodec QUERY_PARAM_NAME = PercentCodec.of(QUERY_PARAM_VALUE).removeSafe('='); /** * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" diff --git a/hutool-core/src/main/java/cn/hutool/core/net/URLEncoder.java b/hutool-core/src/main/java/cn/hutool/core/net/URLEncoder.java index f90609e0d..cbed10e1e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/URLEncoder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/URLEncoder.java @@ -13,6 +13,7 @@ import java.util.BitSet; /** * URL编码,数据内容的类型是 application/x-www-form-urlencoded。 + * TODO 6.x移除此类,使用PercentCodec代替(无法很好区分URL编码和www-form编码) * *
  * 1.字符"a"-"z","A"-"Z","0"-"9",".","-","*",和"_" 都不会被编码;
@@ -21,6 +22,7 @@ import java.util.BitSet;
  * 
* * @author looly + * @see cn.hutool.core.codec.PercentCodec */ public class URLEncoder implements Serializable { private static final long serialVersionUID = 1L; diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java index 536b08c0d..2476f578c 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java @@ -1,5 +1,6 @@ package cn.hutool.core.net.url; +import cn.hutool.core.codec.PercentCodec; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.IterUtil; import cn.hutool.core.convert.Convert; @@ -177,10 +178,28 @@ public class UrlQuery { *
  • 如果value为{@code null},只保留key,如key1对应value为{@code null}生成类似于{@code key1&key2=v2}形式
  • * * - * @param charset encode编码,null表示不做encode编码 + * @param charset encode编码,null表示不做encode编码 * @return URL查询字符串 */ public String build(Charset charset) { + return build(RFC3986.QUERY_PARAM_NAME, RFC3986.QUERY_PARAM_VALUE, charset); + } + + /** + * 构建URL查询字符串,即将key-value键值对转换为{@code key1=v1&key2=v2&key3=v3}形式。
    + * 对于{@code null}处理规则如下: + *
      + *
    • 如果key为{@code null},则这个键值对忽略
    • + *
    • 如果value为{@code null},只保留key,如key1对应value为{@code null}生成类似于{@code key1&key2=v2}形式
    • + *
    + * + * @param keyCoder 键值对中键的编码器 + * @param valueCoder 键值对中值的编码器 + * @param charset encode编码,null表示不做encode编码 + * @return URL查询字符串 + * @since 5.7.16 + */ + public String build(PercentCodec keyCoder, PercentCodec valueCoder, Charset charset) { if (MapUtil.isEmpty(this.query)) { return StrUtil.EMPTY; } @@ -191,13 +210,13 @@ public class UrlQuery { for (Map.Entry entry : this.query) { name = entry.getKey(); if (null != name) { - if(sb.length() >0){ + if (sb.length() > 0) { sb.append("&"); } - sb.append(RFC3986.QUERY_PARAM_NAME.encode(name, charset)); + sb.append(keyCoder.encode(name, charset)); value = entry.getValue(); if (null != value) { - sb.append("=").append(RFC3986.QUERY_PARAM_VALUE.encode(value, charset)); + sb.append("=").append(valueCoder.encode(value, charset)); } } } @@ -213,8 +232,8 @@ public class UrlQuery { * 解析URL中的查询字符串
    * 规则见:https://url.spec.whatwg.org/#urlencoded-parsing * - * @param queryStr 查询字符串,类似于key1=v1&key2=&key3=v3 - * @param charset decode编码,null表示不做decode + * @param queryStr 查询字符串,类似于key1=v1&key2=&key3=v3 + * @param charset decode编码,null表示不做decode * @return this * @since 5.5.8 */ diff --git a/hutool-core/src/test/java/cn/hutool/core/net/FormUrlencodedTest.java b/hutool-core/src/test/java/cn/hutool/core/net/FormUrlencodedTest.java new file mode 100644 index 000000000..8006dbf9c --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/net/FormUrlencodedTest.java @@ -0,0 +1,17 @@ +package cn.hutool.core.net; + +import cn.hutool.core.util.CharsetUtil; +import org.junit.Assert; +import org.junit.Test; + +public class FormUrlencodedTest { + + @Test + public void encodeParamTest(){ + String encode = FormUrlencoded.ALL.encode("a+b", CharsetUtil.CHARSET_UTF_8); + Assert.assertEquals("a%2Bb", encode); + + encode = FormUrlencoded.ALL.encode("a b", CharsetUtil.CHARSET_UTF_8); + Assert.assertEquals("a+b", encode); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/net/RFC3986Test.java b/hutool-core/src/test/java/cn/hutool/core/net/RFC3986Test.java new file mode 100644 index 000000000..c76f8134d --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/net/RFC3986Test.java @@ -0,0 +1,14 @@ +package cn.hutool.core.net; + +import cn.hutool.core.util.CharsetUtil; +import org.junit.Assert; +import org.junit.Test; + +public class RFC3986Test { + + @Test + public void encodeQueryTest(){ + final String encode = RFC3986.QUERY_PARAM_VALUE.encode("a=b", CharsetUtil.CHARSET_UTF_8); + Assert.assertEquals("a=b", encode); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/net/URLEncoderTest.java b/hutool-core/src/test/java/cn/hutool/core/net/URLEncoderTest.java deleted file mode 100644 index b59e0bd80..000000000 --- a/hutool-core/src/test/java/cn/hutool/core/net/URLEncoderTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package cn.hutool.core.net; - -import cn.hutool.core.util.CharsetUtil; -import org.junit.Assert; -import org.junit.Test; - -public class URLEncoderTest { - - @Test - public void encodeTest(){ - String encode = URLEncoder.DEFAULT.encode("+", CharsetUtil.CHARSET_UTF_8); - Assert.assertEquals("+", encode); - - encode = URLEncoder.DEFAULT.encode(" ", CharsetUtil.CHARSET_UTF_8); - Assert.assertEquals("%20", encode); - } -} diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index e0135e77a..ae4e196f2 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -10,8 +10,10 @@ import cn.hutool.core.io.resource.MultiFileResource; import cn.hutool.core.io.resource.Resource; import cn.hutool.core.lang.Assert; import cn.hutool.core.map.MapUtil; +import cn.hutool.core.net.FormUrlencoded; import cn.hutool.core.net.SSLUtil; import cn.hutool.core.net.url.UrlBuilder; +import cn.hutool.core.net.url.UrlQuery; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; @@ -1227,7 +1229,8 @@ public class HttpRequest extends HttpBase { * @since 5.3.2 */ private String getFormUrlEncoded() { - return HttpUtil.toParams(this.form, this.charset); + return UrlQuery.of(this.form) + .build(FormUrlencoded.ALL, FormUrlencoded.ALL, this.charset); } /** diff --git a/hutool-http/src/test/java/cn/hutool/http/HttpRequestTest.java b/hutool-http/src/test/java/cn/hutool/http/HttpRequestTest.java index 2aabe1afe..c1cb54e9d 100644 --- a/hutool-http/src/test/java/cn/hutool/http/HttpRequestTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/HttpRequestTest.java @@ -148,4 +148,5 @@ public class HttpRequestTest { HttpResponse execute = get.execute(); Console.log(execute.body()); } + } From 941e4f39caad39e2b63eeae6c2b91aad2f216a4e Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 7 Nov 2021 03:23:44 +0800 Subject: [PATCH 04/43] fix UrlQuery --- .../java/cn/hutool/core/net/URLDecoder.java | 5 +- .../java/cn/hutool/core/net/url/UrlQuery.java | 70 ++++++++++++++++--- .../java/cn/hutool/core/util/URLUtil.java | 8 +-- .../java/cn/hutool/core/net/UrlQueryTest.java | 8 +++ .../main/java/cn/hutool/http/HttpRequest.java | 4 +- 5 files changed, 76 insertions(+), 19 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/net/URLDecoder.java b/hutool-core/src/main/java/cn/hutool/core/net/URLDecoder.java index bd0adc849..106d973e4 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/URLDecoder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/URLDecoder.java @@ -67,10 +67,13 @@ public class URLDecoder implements Serializable { * * @param str 包含URL编码后的字符串 * @param isPlusToSpace 是否+转换为空格 - * @param charset 编码 + * @param charset 编码,{@code null}表示不做编码 * @return 解码后的字符串 */ public static String decode(String str, Charset charset, boolean isPlusToSpace) { + if(null == charset){ + return str; + } return StrUtil.str(decode(StrUtil.bytes(str, charset), isPlusToSpace), charset); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java index 2476f578c..6260c727a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java @@ -6,9 +6,10 @@ import cn.hutool.core.collection.IterUtil; import cn.hutool.core.convert.Convert; import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.TableMap; +import cn.hutool.core.net.FormUrlencoded; import cn.hutool.core.net.RFC3986; +import cn.hutool.core.net.URLDecoder; import cn.hutool.core.util.StrUtil; -import cn.hutool.core.util.URLUtil; import java.nio.charset.Charset; import java.util.Iterator; @@ -26,6 +27,10 @@ import java.util.Map; public class UrlQuery { private final TableMap query; + /** + * 是否为x-www-form-urlencoded模式,此模式下空格会编码为'+' + */ + private final boolean isFormUrlEncoded; /** * 构建UrlQuery @@ -37,6 +42,17 @@ public class UrlQuery { return new UrlQuery(queryMap); } + /** + * 构建UrlQuery + * + * @param queryMap 初始化的查询键值对 + * @param isFormUrlEncoded 是否为x-www-form-urlencoded模式,此模式下空格会编码为'+' + * @return UrlQuery + */ + public static UrlQuery of(Map queryMap, boolean isFormUrlEncoded) { + return new UrlQuery(queryMap, isFormUrlEncoded); + } + /** * 构建UrlQuery * @@ -58,9 +74,21 @@ public class UrlQuery { * @since 5.5.8 */ public static UrlQuery of(String queryStr, Charset charset, boolean autoRemovePath) { - final UrlQuery urlQuery = new UrlQuery(); - urlQuery.parse(queryStr, charset, autoRemovePath); - return urlQuery; + return of(queryStr, charset, autoRemovePath, false); + } + + /** + * 构建UrlQuery + * + * @param queryStr 初始化的查询字符串 + * @param charset decode用的编码,null表示不做decode + * @param autoRemovePath 是否自动去除path部分,{@code true}则自动去除第一个?前的内容 + * @param isFormUrlEncoded 是否为x-www-form-urlencoded模式,此模式下空格会编码为'+' + * @return UrlQuery + * @since 5.7.16 + */ + public static UrlQuery of(String queryStr, Charset charset, boolean autoRemovePath, boolean isFormUrlEncoded) { + return new UrlQuery(isFormUrlEncoded).parse(queryStr, charset, autoRemovePath); } /** @@ -73,15 +101,37 @@ public class UrlQuery { /** * 构造 * - * @param queryMap 初始化的查询键值对 + * @param isFormUrlEncoded 是否为x-www-form-urlencoded模式,此模式下空格会编码为'+' + * @since 5.7.16 + */ + public UrlQuery(boolean isFormUrlEncoded) { + this(null, isFormUrlEncoded); + } + + /** + * 构造 + * + * @param queryMap 初始化的查询键值对 */ public UrlQuery(Map queryMap) { + this(queryMap, false); + } + + /** + * 构造 + * + * @param queryMap 初始化的查询键值对 + * @param isFormUrlEncoded 是否为x-www-form-urlencoded模式,此模式下空格会编码为'+' + * @since 5.7.16 + */ + public UrlQuery(Map queryMap, boolean isFormUrlEncoded) { if (MapUtil.isNotEmpty(queryMap)) { query = new TableMap<>(queryMap.size()); addAll(queryMap); } else { query = new TableMap<>(MapUtil.DEFAULT_INITIAL_CAPACITY); } + this.isFormUrlEncoded = isFormUrlEncoded; } /** @@ -182,6 +232,10 @@ public class UrlQuery { * @return URL查询字符串 */ public String build(Charset charset) { + if (isFormUrlEncoded) { + return build(FormUrlencoded.ALL, FormUrlencoded.ALL, charset); + } + return build(RFC3986.QUERY_PARAM_NAME, RFC3986.QUERY_PARAM_VALUE, charset); } @@ -315,11 +369,11 @@ public class UrlQuery { */ private void addParam(String key, String value, Charset charset) { if (null != key) { - final String actualKey = URLUtil.decode(key, charset); - this.query.put(actualKey, StrUtil.nullToEmpty(URLUtil.decode(value, charset))); + final String actualKey = URLDecoder.decode(key, charset, isFormUrlEncoded); + this.query.put(actualKey, StrUtil.nullToEmpty(URLDecoder.decode(value, charset, isFormUrlEncoded))); } else if (null != value) { // name为空,value作为name,value赋值null - this.query.put(URLUtil.decode(value, charset), null); + this.query.put(URLDecoder.decode(value, charset, isFormUrlEncoded), null); } } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java index 5745bce89..724312695 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java @@ -328,9 +328,6 @@ public class URLUtil extends URLEncodeUtil { * @since 4.4.1 */ public static String decode(String content, Charset charset) { - if (null == charset) { - return content; - } return URLDecoder.decode(content, charset); } @@ -345,9 +342,6 @@ public class URLUtil extends URLEncodeUtil { * @since 5.6.3 */ public static String decode(String content, Charset charset, boolean isPlusToSpace) { - if (null == charset) { - return content; - } return URLDecoder.decode(content, charset, isPlusToSpace); } @@ -361,7 +355,7 @@ public class URLUtil extends URLEncodeUtil { * @throws UtilException UnsupportedEncodingException */ public static String decode(String content, String charset) throws UtilException { - return decode(content, CharsetUtil.charset(charset)); + return decode(content, StrUtil.isEmpty(charset) ? null : CharsetUtil.charset(charset)); } /** diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java index ca5f9581a..4bdaabc0d 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java @@ -108,6 +108,14 @@ public class UrlQueryTest { Assert.assertEquals("a+b=1+2", a); } + @Test + public void parsePlusTest(){ + // 根据RFC3986,在URL中,+是安全字符,即此符号不转义 + final String a = UrlQuery.of("a+b=1+2", CharsetUtil.CHARSET_UTF_8) + .build(CharsetUtil.CHARSET_UTF_8); + Assert.assertEquals("a+b=1+2", a); + } + @Test public void spaceTest(){ // 根据RFC3986,在URL中,空格编码为"%20" diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index ae4e196f2..f52f570f2 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -10,7 +10,6 @@ import cn.hutool.core.io.resource.MultiFileResource; import cn.hutool.core.io.resource.Resource; import cn.hutool.core.lang.Assert; import cn.hutool.core.map.MapUtil; -import cn.hutool.core.net.FormUrlencoded; import cn.hutool.core.net.SSLUtil; import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.net.url.UrlQuery; @@ -1229,8 +1228,7 @@ public class HttpRequest extends HttpBase { * @since 5.3.2 */ private String getFormUrlEncoded() { - return UrlQuery.of(this.form) - .build(FormUrlencoded.ALL, FormUrlencoded.ALL, this.charset); + return UrlQuery.of(this.form, true).build(this.charset); } /** From fbf52763b7a6eb31bb8f254534823fb585bac0ce Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 7 Nov 2021 13:10:18 +0800 Subject: [PATCH 05/43] add method --- CHANGELOG.md | 1 + .../main/java/cn/hutool/core/lang/Opt.java | 53 +++++++++---------- .../java/cn/hutool/core/lang/OptTest.java | 14 ++--- 3 files changed, 32 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1c5df6e0..b205cb0d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ * 【core 】 TreeUtil增加walk方法(pr#1932@Gitee) * 【crypto 】 SmUtil增加sm3WithSalt(pr#454@Gitee) * 【http 】 增加HttpInterceptor(issue#I4H1ZV@Gitee) +* 【core 】 Opt增加flattedMap(issue#I4H1ZV@Gitee) ### 🐞Bug修复 * 【core 】 修复UrlBuilder.addPath歧义问题(issue#1912@Github) diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Opt.java b/hutool-core/src/main/java/cn/hutool/core/lang/Opt.java index d5f75bab9..e4bfe9e80 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/Opt.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/Opt.java @@ -50,20 +50,6 @@ public class Opt { */ private static final Opt EMPTY = new Opt<>(null); - /** - * 包裹里实际的元素 - */ - private final T value; - - /** - * {@code Opt}的构造函数 - * - * @param value 包裹里的元素 - */ - private Opt(T value) { - this.value = value; - } - /** * 返回一个空的{@code Opt} * @@ -111,6 +97,20 @@ public class Opt { return StrUtil.isBlankIfStr(value) ? empty() : new Opt<>(value); } + /** + * 包裹里实际的元素 + */ + private final T value; + + /** + * {@code Opt}的构造函数 + * + * @param value 包裹里的元素 + */ + private Opt(T value) { + this.value = value; + } + /** * 返回包裹里的元素,取不到则为{@code null},注意!!!此处和{@link java.util.Optional#get()}不同的一点是本方法并不会抛出{@code NoSuchElementException} * 如果元素为空,则返回{@code null},如果需要一个绝对不能为{@code null}的值,则使用{@link #orElseThrow()} @@ -126,16 +126,6 @@ public class Opt { return value; } - /** - * 以非静态方式获取一个新的 {@code Opt} - * - * @param value 值 - * @return 新的 {@code Opt} - */ - public Opt set(T value) { - return Opt.ofNullable(value); - } - /** * 判断包裹里元素的值是否存在,存在为 {@code true},否则为{@code false} * @@ -263,15 +253,15 @@ public class Opt { * @return 如果包裹里的值存在,就执行传入的操作({@link Function#apply})并返回该操作返回值 * 如果不存在,返回一个空的{@code Opt} * @throws NullPointerException 如果给定的操作为 {@code null}或者给定的操作执行结果为 {@code null},抛出 {@code NPE} + * @see Optional#flatMap(Function) + * @since 5.7.16 */ public Opt flattedMap(Function> mapper) { Objects.requireNonNull(mapper); if (isEmpty()) { return empty(); } else { - @SuppressWarnings("unchecked") - Optional r = (Optional) mapper.apply(value); - return Objects.requireNonNull(ofNullable(r.orElse(null))); + return ofNullable(mapper.apply(value).orElse(null)); } } @@ -428,6 +418,15 @@ public class Opt { } } + /** + * 转换为 {@link Optional}对象 + * @return {@link Optional}对象 + * @since 5.7.16 + */ + public Optional toOptional(){ + return Optional.ofNullable(this.value); + } + /** * 判断传入参数是否与 {@code Opt}相等 * 在以下情况下返回true diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/OptTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/OptTest.java index 7af6a4f04..0da48a24a 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/OptTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/OptTest.java @@ -11,6 +11,7 @@ import org.junit.Test; import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; +import java.util.stream.Stream; /** * {@link Opt}的单元测试 @@ -135,15 +136,10 @@ public class OptTest { // 以前,不兼容 // Opt.ofNullable(userList).map(List::stream).flatMap(Stream::findFirst); // 现在,兼容 - User user = Opt.ofNullable(userList).map(List::stream).flattedMap(Stream::findFirst).orElseGet(User.builder()::build); - System.out.println(user); - } - - @Test - public void setTest() { - // 我一直在想,既然有get,为什么不能有set呢? - Opt.ofNullable(User.builder().username("ruben").build()).peek(System.out::println) - .set(User.builder().username("hutool").build()).peek(System.out::println); + User user = Opt.ofNullable(userList).map(List::stream) + .flattedMap(Stream::findFirst).orElseGet(User.builder()::build); + Assert.assertNull(user.getUsername()); + Assert.assertNull(user.getNickname()); } @Data From 6721d634915735c0f698f7b074ac7de51018abb3 Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 7 Nov 2021 13:59:04 +0800 Subject: [PATCH 06/43] fix code --- .../src/main/java/cn/hutool/http/HttpUtil.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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 644300c47..0257cd464 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java @@ -453,7 +453,8 @@ public class HttpUtil { /** * 将Map形式的Form表单数据转换为Url参数形式
    * paramMap中如果key为空(null和"")会被忽略,如果value为null,会被做为空白符("")
    - * 会自动url编码键和值 + * 会自动url编码键和值
    + * 此方法用于拼接URL中的Query部分,并不适用于POST请求中的表单 * *
     	 * key1=v1&key2=&key3=v3
    @@ -462,9 +463,10 @@ public class HttpUtil {
     	 * @param paramMap 表单数据
     	 * @param charset  编码,{@code null} 表示不encode键值对
     	 * @return url参数
    +	 * @see #toParams(Map, Charset, boolean)
     	 */
     	public static String toParams(Map paramMap, Charset charset) {
    -		return UrlQuery.of(paramMap).build(charset);
    +		return toParams(paramMap, charset, false);
     	}
     
     	/**
    @@ -478,14 +480,12 @@ public class HttpUtil {
     	 *
     	 * @param paramMap 表单数据
     	 * @param charset  编码,null表示不encode键值对
    -	 * @param isEncode 是否转义键和值
    +	 * @param isFormUrlEncoded 是否为x-www-form-urlencoded模式,此模式下空格会编码为'+'
     	 * @return url参数
    -	 * @since 5.7.13
    -	 * @deprecated 请使用 {@link #toParams(Map, Charset)}, charset为null表示不编码
    +	 * @since 5.7.16
     	 */
    -	@Deprecated
    -	public static String toParams(Map paramMap, Charset charset, boolean isEncode) {
    -		return toParams(paramMap, isEncode ? charset : null);
    +	public static String toParams(Map paramMap, Charset charset, boolean isFormUrlEncoded) {
    +		return UrlQuery.of(paramMap, isFormUrlEncoded).build(charset);
     	}
     
     	/**
    
    From 29dd3ca30a7d6385c26f973ccdf857109f016bfc Mon Sep 17 00:00:00 2001
    From: Looly 
    Date: Sun, 7 Nov 2021 17:25:36 +0800
    Subject: [PATCH 07/43] update dependency
    
    ---
     hutool-aop/pom.xml    | 2 +-
     hutool-db/pom.xml     | 2 +-
     hutool-extra/pom.xml  | 2 +-
     hutool-system/pom.xml | 2 +-
     4 files changed, 4 insertions(+), 4 deletions(-)
    
    diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml
    index 5590a0b53..f9a105036 100644
    --- a/hutool-aop/pom.xml
    +++ b/hutool-aop/pom.xml
    @@ -19,7 +19,7 @@
     	
     		
     		3.3.0
    -		5.3.10
    +		5.3.12
     	
     
     	
    diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml
    index 99574b86d..96f7674f9 100644
    --- a/hutool-db/pom.xml
    +++ b/hutool-db/pom.xml
    @@ -149,7 +149,7 @@
     		
     			org.postgresql
     			postgresql
    -			42.3.0
    +			42.3.1
     			test
     		
     		
    diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml
    index fd49b4779..96a3cb7bd 100644
    --- a/hutool-extra/pom.xml
    +++ b/hutool-extra/pom.xml
    @@ -19,7 +19,7 @@
     	
     		
     		2.3
    -		3.7.0.RELEASE
    +		3.8.0.RELEASE
     		1.4.1
     		2.3.31
     		4.9.16
    diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml
    index 916d12017..64ea14404 100644
    --- a/hutool-system/pom.xml
    +++ b/hutool-system/pom.xml
    @@ -30,7 +30,7 @@
     		
     			com.github.oshi
     			oshi-core
    -			5.8.2
    +			5.8.3
     			provided
     		
     		
    
    From a70d50736e622500955dc1072b91aa8ee5fc3243 Mon Sep 17 00:00:00 2001
    From: Looly 
    Date: Sun, 7 Nov 2021 17:32:54 +0800
    Subject: [PATCH 08/43] =?UTF-8?q?=E2=98=83=EF=B8=8Frelease=205.7.16?=
    MIME-Version: 1.0
    Content-Type: text/plain; charset=UTF-8
    Content-Transfer-Encoding: 8bit
    
    ---
     hutool-all/pom.xml         | 2 +-
     hutool-aop/pom.xml         | 2 +-
     hutool-bloomFilter/pom.xml | 2 +-
     hutool-bom/pom.xml         | 2 +-
     hutool-cache/pom.xml       | 2 +-
     hutool-captcha/pom.xml     | 2 +-
     hutool-core/pom.xml        | 2 +-
     hutool-cron/pom.xml        | 2 +-
     hutool-crypto/pom.xml      | 2 +-
     hutool-db/pom.xml          | 2 +-
     hutool-dfa/pom.xml         | 2 +-
     hutool-extra/pom.xml       | 2 +-
     hutool-http/pom.xml        | 2 +-
     hutool-json/pom.xml        | 2 +-
     hutool-jwt/pom.xml         | 2 +-
     hutool-log/pom.xml         | 2 +-
     hutool-poi/pom.xml         | 2 +-
     hutool-script/pom.xml      | 2 +-
     hutool-setting/pom.xml     | 2 +-
     hutool-socket/pom.xml      | 2 +-
     hutool-system/pom.xml      | 2 +-
     pom.xml                    | 2 +-
     22 files changed, 22 insertions(+), 22 deletions(-)
    
    diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml
    index b97625af4..1c7c8e247 100644
    --- a/hutool-all/pom.xml
    +++ b/hutool-all/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16-SNAPSHOT
    +		5.7.16
     	
     
     	hutool-all
    diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml
    index f9a105036..fa39ff712 100644
    --- a/hutool-aop/pom.xml
    +++ b/hutool-aop/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16-SNAPSHOT
    +		5.7.16
     	
     
     	hutool-aop
    diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml
    index 96f02bf66..f44040002 100644
    --- a/hutool-bloomFilter/pom.xml
    +++ b/hutool-bloomFilter/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16-SNAPSHOT
    +		5.7.16
     	
     
     	hutool-bloomFilter
    diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml
    index a484343e3..54295dbd6 100644
    --- a/hutool-bom/pom.xml
    +++ b/hutool-bom/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16-SNAPSHOT
    +		5.7.16
     	
     
     	hutool-bom
    diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml
    index 7e84b9cea..e193759b1 100644
    --- a/hutool-cache/pom.xml
    +++ b/hutool-cache/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16-SNAPSHOT
    +		5.7.16
     	
     
     	hutool-cache
    diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml
    index b22ac7ab2..e44fc372b 100644
    --- a/hutool-captcha/pom.xml
    +++ b/hutool-captcha/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16-SNAPSHOT
    +		5.7.16
     	
     
     	hutool-captcha
    diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml
    index ed9ac8a21..52b1d8d34 100644
    --- a/hutool-core/pom.xml
    +++ b/hutool-core/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16-SNAPSHOT
    +		5.7.16
     	
     
     	hutool-core
    diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml
    index 4d3a77ef2..19205704c 100644
    --- a/hutool-cron/pom.xml
    +++ b/hutool-cron/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16-SNAPSHOT
    +		5.7.16
     	
     
     	hutool-cron
    diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml
    index b58ed2229..86ea6f162 100644
    --- a/hutool-crypto/pom.xml
    +++ b/hutool-crypto/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16-SNAPSHOT
    +		5.7.16
     	
     
     	hutool-crypto
    diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml
    index 96f7674f9..b4f9764ae 100644
    --- a/hutool-db/pom.xml
    +++ b/hutool-db/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16-SNAPSHOT
    +		5.7.16
     	
     
     	hutool-db
    diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml
    index 2e73f8080..1cbb8b945 100644
    --- a/hutool-dfa/pom.xml
    +++ b/hutool-dfa/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16-SNAPSHOT
    +		5.7.16
     	
     
     	hutool-dfa
    diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml
    index 96a3cb7bd..78d111451 100644
    --- a/hutool-extra/pom.xml
    +++ b/hutool-extra/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16-SNAPSHOT
    +		5.7.16
     	
     
     	hutool-extra
    diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml
    index b86a35819..4429323d2 100644
    --- a/hutool-http/pom.xml
    +++ b/hutool-http/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16-SNAPSHOT
    +		5.7.16
     	
     
     	hutool-http
    diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml
    index 7a94609b1..20e0a0ecf 100644
    --- a/hutool-json/pom.xml
    +++ b/hutool-json/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16-SNAPSHOT
    +		5.7.16
     	
     
     	hutool-json
    diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml
    index efd206d24..afb2aaaa0 100644
    --- a/hutool-jwt/pom.xml
    +++ b/hutool-jwt/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16-SNAPSHOT
    +		5.7.16
     	
     
     	hutool-jwt
    diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml
    index f0175f355..a9c38fd02 100644
    --- a/hutool-log/pom.xml
    +++ b/hutool-log/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16-SNAPSHOT
    +		5.7.16
     	
     
     	hutool-log
    diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml
    index 90a2094c1..d99254590 100644
    --- a/hutool-poi/pom.xml
    +++ b/hutool-poi/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16-SNAPSHOT
    +		5.7.16
     	
     
     	hutool-poi
    diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml
    index 9661de356..bc9ff3e9f 100644
    --- a/hutool-script/pom.xml
    +++ b/hutool-script/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16-SNAPSHOT
    +		5.7.16
     	
     
     	hutool-script
    diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml
    index f70bafd70..a2754527f 100644
    --- a/hutool-setting/pom.xml
    +++ b/hutool-setting/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16-SNAPSHOT
    +		5.7.16
     	
     
     	hutool-setting
    diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml
    index cb37a29e1..ac3a62e78 100644
    --- a/hutool-socket/pom.xml
    +++ b/hutool-socket/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16-SNAPSHOT
    +		5.7.16
     	
     
     	hutool-socket
    diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml
    index 64ea14404..02f22abaa 100644
    --- a/hutool-system/pom.xml
    +++ b/hutool-system/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16-SNAPSHOT
    +		5.7.16
     	
     
     	hutool-system
    diff --git a/pom.xml b/pom.xml
    index 3ea2cdd16..d5a60b5cf 100644
    --- a/pom.xml
    +++ b/pom.xml
    @@ -8,7 +8,7 @@
     
     	cn.hutool
     	hutool-parent
    -	5.7.16-SNAPSHOT
    +	5.7.16
     	hutool
     	Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。
     	https://github.com/dromara/hutool
    
    From 30c35234df22e3dab55913e3828f67f5bc76ba46 Mon Sep 17 00:00:00 2001
    From: Looly 
    Date: Sun, 7 Nov 2021 19:29:50 +0800
    Subject: [PATCH 09/43] prepare 5.7.17
    
    ---
     CHANGELOG.md               | 7 +++++++
     README-EN.md               | 6 +++---
     README.md                  | 6 +++---
     bin/version.txt            | 2 +-
     docs/js/version.js         | 2 +-
     hutool-all/pom.xml         | 2 +-
     hutool-aop/pom.xml         | 2 +-
     hutool-bloomFilter/pom.xml | 2 +-
     hutool-bom/pom.xml         | 2 +-
     hutool-cache/pom.xml       | 2 +-
     hutool-captcha/pom.xml     | 2 +-
     hutool-core/pom.xml        | 2 +-
     hutool-cron/pom.xml        | 2 +-
     hutool-crypto/pom.xml      | 2 +-
     hutool-db/pom.xml          | 2 +-
     hutool-dfa/pom.xml         | 2 +-
     hutool-extra/pom.xml       | 2 +-
     hutool-http/pom.xml        | 2 +-
     hutool-json/pom.xml        | 2 +-
     hutool-jwt/pom.xml         | 2 +-
     hutool-log/pom.xml         | 2 +-
     hutool-poi/pom.xml         | 2 +-
     hutool-script/pom.xml      | 2 +-
     hutool-setting/pom.xml     | 2 +-
     hutool-socket/pom.xml      | 2 +-
     hutool-system/pom.xml      | 2 +-
     pom.xml                    | 2 +-
     27 files changed, 37 insertions(+), 30 deletions(-)
    
    diff --git a/CHANGELOG.md b/CHANGELOG.md
    index b205cb0d4..a7c09f083 100644
    --- a/CHANGELOG.md
    +++ b/CHANGELOG.md
    @@ -3,6 +3,13 @@
     
     -------------------------------------------------------------------------------------------------------------
     
    +# 5.7.17 (2021-11-07)
    +
    +### 🐣新特性
    +### 🐞Bug修复
    +
    +-------------------------------------------------------------------------------------------------------------
    +
     # 5.7.16 (2021-11-07)
     
     ### 🐣新特性
    diff --git a/README-EN.md b/README-EN.md
    index f73d4c304..f7d4fd9de 100644
    --- a/README-EN.md
    +++ b/README-EN.md
    @@ -142,18 +142,18 @@ We provide the T-Shirt and Sweater with Hutool Logo, please visit the shop:
     
         cn.hutool
         hutool-all
    -    5.7.16
    +    5.7.17
     
     ```
     
     ### 🍐Gradle
     ```
    -implementation 'cn.hutool:hutool-all:5.7.16'
    +implementation 'cn.hutool:hutool-all:5.7.17'
     ```
     
     ## 📥Download
     
    -- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.7.16/)
    +- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.7.17/)
     
     > 🔔️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 ae1aea6e8..6b096cd29 100644
    --- a/README.md
    +++ b/README.md
    @@ -142,20 +142,20 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不
     
         cn.hutool
         hutool-all
    -    5.7.16
    +    5.7.17
     
     ```
     
     ### 🍐Gradle
     ```
    -implementation 'cn.hutool:hutool-all:5.7.16'
    +implementation 'cn.hutool:hutool-all:5.7.17'
     ```
     
     ### 📥下载jar
     
     点击以下链接,下载`hutool-all-X.X.X.jar`即可:
     
    -- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.7.16/)
    +- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.7.17/)
     
     > 🔔️注意
     > Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。
    diff --git a/bin/version.txt b/bin/version.txt
    index e8406ce30..f3ca4b63c 100755
    --- a/bin/version.txt
    +++ b/bin/version.txt
    @@ -1 +1 @@
    -5.7.16
    +5.7.17
    diff --git a/docs/js/version.js b/docs/js/version.js
    index 3e6f04e99..2b2bc35df 100644
    --- a/docs/js/version.js
    +++ b/docs/js/version.js
    @@ -1 +1 @@
    -var version = '5.7.16'
    \ No newline at end of file
    +var version = '5.7.17'
    \ No newline at end of file
    diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml
    index 1c7c8e247..b9f63e9df 100644
    --- a/hutool-all/pom.xml
    +++ b/hutool-all/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16
    +		5.7.17-SNAPSHOT
     	
     
     	hutool-all
    diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml
    index fa39ff712..204296f55 100644
    --- a/hutool-aop/pom.xml
    +++ b/hutool-aop/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16
    +		5.7.17-SNAPSHOT
     	
     
     	hutool-aop
    diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml
    index f44040002..5199fb9a5 100644
    --- a/hutool-bloomFilter/pom.xml
    +++ b/hutool-bloomFilter/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16
    +		5.7.17-SNAPSHOT
     	
     
     	hutool-bloomFilter
    diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml
    index 54295dbd6..1763613e3 100644
    --- a/hutool-bom/pom.xml
    +++ b/hutool-bom/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16
    +		5.7.17-SNAPSHOT
     	
     
     	hutool-bom
    diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml
    index e193759b1..c5597d752 100644
    --- a/hutool-cache/pom.xml
    +++ b/hutool-cache/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16
    +		5.7.17-SNAPSHOT
     	
     
     	hutool-cache
    diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml
    index e44fc372b..f09694d5b 100644
    --- a/hutool-captcha/pom.xml
    +++ b/hutool-captcha/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16
    +		5.7.17-SNAPSHOT
     	
     
     	hutool-captcha
    diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml
    index 52b1d8d34..810fcc936 100644
    --- a/hutool-core/pom.xml
    +++ b/hutool-core/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16
    +		5.7.17-SNAPSHOT
     	
     
     	hutool-core
    diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml
    index 19205704c..b7f6f5e64 100644
    --- a/hutool-cron/pom.xml
    +++ b/hutool-cron/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16
    +		5.7.17-SNAPSHOT
     	
     
     	hutool-cron
    diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml
    index 86ea6f162..7f112cfb5 100644
    --- a/hutool-crypto/pom.xml
    +++ b/hutool-crypto/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16
    +		5.7.17-SNAPSHOT
     	
     
     	hutool-crypto
    diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml
    index b4f9764ae..0494c0d8a 100644
    --- a/hutool-db/pom.xml
    +++ b/hutool-db/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16
    +		5.7.17-SNAPSHOT
     	
     
     	hutool-db
    diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml
    index 1cbb8b945..b6c87f3f3 100644
    --- a/hutool-dfa/pom.xml
    +++ b/hutool-dfa/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16
    +		5.7.17-SNAPSHOT
     	
     
     	hutool-dfa
    diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml
    index 78d111451..f59fd0e9a 100644
    --- a/hutool-extra/pom.xml
    +++ b/hutool-extra/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16
    +		5.7.17-SNAPSHOT
     	
     
     	hutool-extra
    diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml
    index 4429323d2..3c35d03d4 100644
    --- a/hutool-http/pom.xml
    +++ b/hutool-http/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16
    +		5.7.17-SNAPSHOT
     	
     
     	hutool-http
    diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml
    index 20e0a0ecf..70797ce5a 100644
    --- a/hutool-json/pom.xml
    +++ b/hutool-json/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16
    +		5.7.17-SNAPSHOT
     	
     
     	hutool-json
    diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml
    index afb2aaaa0..062dd810f 100644
    --- a/hutool-jwt/pom.xml
    +++ b/hutool-jwt/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16
    +		5.7.17-SNAPSHOT
     	
     
     	hutool-jwt
    diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml
    index a9c38fd02..b1cd3eea1 100644
    --- a/hutool-log/pom.xml
    +++ b/hutool-log/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16
    +		5.7.17-SNAPSHOT
     	
     
     	hutool-log
    diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml
    index d99254590..3aafbb163 100644
    --- a/hutool-poi/pom.xml
    +++ b/hutool-poi/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16
    +		5.7.17-SNAPSHOT
     	
     
     	hutool-poi
    diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml
    index bc9ff3e9f..5dd7116ea 100644
    --- a/hutool-script/pom.xml
    +++ b/hutool-script/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16
    +		5.7.17-SNAPSHOT
     	
     
     	hutool-script
    diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml
    index a2754527f..e18a4b680 100644
    --- a/hutool-setting/pom.xml
    +++ b/hutool-setting/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16
    +		5.7.17-SNAPSHOT
     	
     
     	hutool-setting
    diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml
    index ac3a62e78..5bca495ab 100644
    --- a/hutool-socket/pom.xml
    +++ b/hutool-socket/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16
    +		5.7.17-SNAPSHOT
     	
     
     	hutool-socket
    diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml
    index 02f22abaa..ec82d73d8 100644
    --- a/hutool-system/pom.xml
    +++ b/hutool-system/pom.xml
    @@ -9,7 +9,7 @@
     	
     		cn.hutool
     		hutool-parent
    -		5.7.16
    +		5.7.17-SNAPSHOT
     	
     
     	hutool-system
    diff --git a/pom.xml b/pom.xml
    index d5a60b5cf..431e1a36e 100644
    --- a/pom.xml
    +++ b/pom.xml
    @@ -8,7 +8,7 @@
     
     	cn.hutool
     	hutool-parent
    -	5.7.16
    +	5.7.17-SNAPSHOT
     	hutool
     	Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。
     	https://github.com/dromara/hutool
    
    From d53f1ff15efff04fdf0a9c92804d38aed46c14e6 Mon Sep 17 00:00:00 2001
    From: looly 
    Date: Wed, 10 Nov 2021 17:46:41 +0800
    Subject: [PATCH 10/43] fix method
    
    ---
     hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java
    index 705e09ca2..a43a053dc 100644
    --- a/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java
    +++ b/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java
    @@ -693,7 +693,7 @@ public class DateUtil extends CalendarUtil {
     	 * @since 5.7.14
     	 */
     	public static DateTime parse(CharSequence dateStr, DateParser parser, boolean lenient) {
    -		return new DateTime(dateStr, parser);
    +		return new DateTime(dateStr, parser, lenient);
     	}
     
     	/**
    
    From 4f04bd121a4f769f28728ccbc4d41e0c742a82cc Mon Sep 17 00:00:00 2001
    From: achao 
    Date: Wed, 10 Nov 2021 20:27:33 +0800
    Subject: [PATCH 11/43] =?UTF-8?q?HashMap=E5=BA=94=E6=8C=87=E5=AE=9A?=
     =?UTF-8?q?=E5=88=9D=E5=A7=8B=E9=95=BF=E5=BA=A6?=
    MIME-Version: 1.0
    Content-Type: text/plain; charset=UTF-8
    Content-Transfer-Encoding: 8bit
    
    ---
     .../hutool/core/collection/CollStreamUtil.java   |  2 +-
     .../java/cn/hutool/core/util/NumberUtil.java     | 16 ++++++++--------
     2 files changed, 9 insertions(+), 9 deletions(-)
    
    diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/CollStreamUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/CollStreamUtil.java
    index b418ddcc1..53243cb71 100644
    --- a/hutool-core/src/main/java/cn/hutool/core/collection/CollStreamUtil.java
    +++ b/hutool-core/src/main/java/cn/hutool/core/collection/CollStreamUtil.java
    @@ -179,7 +179,7 @@ public class CollStreamUtil {
     		Set key = new HashSet<>();
     		key.addAll(map1.keySet());
     		key.addAll(map2.keySet());
    -		Map map = new HashMap<>();
    +		Map map = MapUtil.newHashMap(key.size());
     		for (K t : key) {
     			X x = map1.get(t);
     			Y y = map2.get(t);
    diff --git a/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java
    index 08f30132a..bedca46f8 100644
    --- a/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java
    +++ b/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java
    @@ -7,12 +7,12 @@ import cn.hutool.core.math.Calculator;
     import java.math.BigDecimal;
     import java.math.BigInteger;
     import java.math.RoundingMode;
    +import java.security.SecureRandom;
     import java.text.DecimalFormat;
     import java.text.NumberFormat;
     import java.text.ParseException;
     import java.util.Collection;
     import java.util.HashSet;
    -import java.util.Random;
     import java.util.Set;
     
     /**
    @@ -1362,8 +1362,8 @@ public class NumberUtil {
     			throw new UtilException("Size is larger than range between begin and end!");
     		}
     
    -		Random ran = new Random();
    -		Set set = new HashSet<>();
    +		SecureRandom ran = new SecureRandom();
    +		Set set = new HashSet<>(Math.max((int) (size / .75f) + 1, 16));
     		while (set.size() < size) {
     			set.add(begin + ran.nextInt(end - begin));
     		}
    @@ -2159,14 +2159,14 @@ public class NumberUtil {
     	 * @since 4.0.9
     	 */
     	public static BigDecimal toBigDecimal(String numberStr) {
    -		if(StrUtil.isBlank(numberStr)){
    +		if (StrUtil.isBlank(numberStr)) {
     			return BigDecimal.ZERO;
     		}
     
     		try {
     			// 支持类似于 1,234.55 格式的数字
     			final Number number = parseNumber(numberStr);
    -			if(number instanceof BigDecimal){
    +			if (number instanceof BigDecimal) {
     				return (BigDecimal) number;
     			} else {
     				return new BigDecimal(number.toString());
    @@ -2512,7 +2512,7 @@ public class NumberUtil {
     	public static Number parseNumber(String numberStr) throws NumberFormatException {
     		try {
     			final NumberFormat format = NumberFormat.getInstance();
    -			if(format instanceof DecimalFormat){
    +			if (format instanceof DecimalFormat) {
     				// issue#1818@Github
     				// 当字符串数字超出double的长度时,会导致截断,此处使用BigDecimal接收
     				((DecimalFormat) format).setParseBigDecimal(true);
    @@ -2699,9 +2699,9 @@ public class NumberUtil {
     	 * @since 5.7.8
     	 */
     	public static double toDouble(Number value) {
    -		if(value instanceof Float){
    +		if (value instanceof Float) {
     			return Double.parseDouble(value.toString());
    -		}else{
    +		} else {
     			return value.doubleValue();
     		}
     	}
    
    From 1e7af4d7fc37dc43017234d84a203dbc10685271 Mon Sep 17 00:00:00 2001
    From: achao 
    Date: Wed, 10 Nov 2021 20:29:41 +0800
    Subject: [PATCH 12/43] =?UTF-8?q?Revert=20"HashMap=E5=BA=94=E6=8C=87?=
     =?UTF-8?q?=E5=AE=9A=E5=88=9D=E5=A7=8B=E9=95=BF=E5=BA=A6"?=
    MIME-Version: 1.0
    Content-Type: text/plain; charset=UTF-8
    Content-Transfer-Encoding: 8bit
    
    This reverts commit 4f04bd121a4f769f28728ccbc4d41e0c742a82cc.
    ---
     .../hutool/core/collection/CollStreamUtil.java   |  2 +-
     .../java/cn/hutool/core/util/NumberUtil.java     | 16 ++++++++--------
     2 files changed, 9 insertions(+), 9 deletions(-)
    
    diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/CollStreamUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/CollStreamUtil.java
    index 53243cb71..b418ddcc1 100644
    --- a/hutool-core/src/main/java/cn/hutool/core/collection/CollStreamUtil.java
    +++ b/hutool-core/src/main/java/cn/hutool/core/collection/CollStreamUtil.java
    @@ -179,7 +179,7 @@ public class CollStreamUtil {
     		Set key = new HashSet<>();
     		key.addAll(map1.keySet());
     		key.addAll(map2.keySet());
    -		Map map = MapUtil.newHashMap(key.size());
    +		Map map = new HashMap<>();
     		for (K t : key) {
     			X x = map1.get(t);
     			Y y = map2.get(t);
    diff --git a/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java
    index bedca46f8..08f30132a 100644
    --- a/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java
    +++ b/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java
    @@ -7,12 +7,12 @@ import cn.hutool.core.math.Calculator;
     import java.math.BigDecimal;
     import java.math.BigInteger;
     import java.math.RoundingMode;
    -import java.security.SecureRandom;
     import java.text.DecimalFormat;
     import java.text.NumberFormat;
     import java.text.ParseException;
     import java.util.Collection;
     import java.util.HashSet;
    +import java.util.Random;
     import java.util.Set;
     
     /**
    @@ -1362,8 +1362,8 @@ public class NumberUtil {
     			throw new UtilException("Size is larger than range between begin and end!");
     		}
     
    -		SecureRandom ran = new SecureRandom();
    -		Set set = new HashSet<>(Math.max((int) (size / .75f) + 1, 16));
    +		Random ran = new Random();
    +		Set set = new HashSet<>();
     		while (set.size() < size) {
     			set.add(begin + ran.nextInt(end - begin));
     		}
    @@ -2159,14 +2159,14 @@ public class NumberUtil {
     	 * @since 4.0.9
     	 */
     	public static BigDecimal toBigDecimal(String numberStr) {
    -		if (StrUtil.isBlank(numberStr)) {
    +		if(StrUtil.isBlank(numberStr)){
     			return BigDecimal.ZERO;
     		}
     
     		try {
     			// 支持类似于 1,234.55 格式的数字
     			final Number number = parseNumber(numberStr);
    -			if (number instanceof BigDecimal) {
    +			if(number instanceof BigDecimal){
     				return (BigDecimal) number;
     			} else {
     				return new BigDecimal(number.toString());
    @@ -2512,7 +2512,7 @@ public class NumberUtil {
     	public static Number parseNumber(String numberStr) throws NumberFormatException {
     		try {
     			final NumberFormat format = NumberFormat.getInstance();
    -			if (format instanceof DecimalFormat) {
    +			if(format instanceof DecimalFormat){
     				// issue#1818@Github
     				// 当字符串数字超出double的长度时,会导致截断,此处使用BigDecimal接收
     				((DecimalFormat) format).setParseBigDecimal(true);
    @@ -2699,9 +2699,9 @@ public class NumberUtil {
     	 * @since 5.7.8
     	 */
     	public static double toDouble(Number value) {
    -		if (value instanceof Float) {
    +		if(value instanceof Float){
     			return Double.parseDouble(value.toString());
    -		} else {
    +		}else{
     			return value.doubleValue();
     		}
     	}
    
    From 8e0a5e20296310acf8f83677ae6cd92e07435927 Mon Sep 17 00:00:00 2001
    From: achao 
    Date: Wed, 10 Nov 2021 20:31:32 +0800
    Subject: [PATCH 13/43] =?UTF-8?q?=E7=94=A8RandomUtil?=
    MIME-Version: 1.0
    Content-Type: text/plain; charset=UTF-8
    Content-Transfer-Encoding: 8bit
    
    ---
     .../src/main/java/cn/hutool/core/util/NumberUtil.java        | 5 ++---
     1 file changed, 2 insertions(+), 3 deletions(-)
    
    diff --git a/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java
    index 08f30132a..ec92e1a21 100644
    --- a/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java
    +++ b/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java
    @@ -1362,10 +1362,9 @@ public class NumberUtil {
     			throw new UtilException("Size is larger than range between begin and end!");
     		}
     
    -		Random ran = new Random();
    -		Set set = new HashSet<>();
    +		Set set = new HashSet<>(Math.max((int) (size / .75f) + 1, 16));
     		while (set.size() < size) {
    -			set.add(begin + ran.nextInt(end - begin));
    +			set.add(begin + RandomUtil.randomInt(end - begin));
     		}
     
     		return set.toArray(new Integer[size]);
    
    From c8743914f136952fc3b974e2542db6567750591d Mon Sep 17 00:00:00 2001
    From: achao 
    Date: Wed, 10 Nov 2021 20:33:02 +0800
    Subject: [PATCH 14/43] =?UTF-8?q?HashMap=E5=BA=94=E6=8C=87=E5=AE=9A?=
     =?UTF-8?q?=E5=88=9D=E5=A7=8B=E9=95=BF=E5=BA=A6?=
    MIME-Version: 1.0
    Content-Type: text/plain; charset=UTF-8
    Content-Transfer-Encoding: 8bit
    
    ---
     .../src/main/java/cn/hutool/core/collection/CollStreamUtil.java | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/CollStreamUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/CollStreamUtil.java
    index b418ddcc1..53243cb71 100644
    --- a/hutool-core/src/main/java/cn/hutool/core/collection/CollStreamUtil.java
    +++ b/hutool-core/src/main/java/cn/hutool/core/collection/CollStreamUtil.java
    @@ -179,7 +179,7 @@ public class CollStreamUtil {
     		Set key = new HashSet<>();
     		key.addAll(map1.keySet());
     		key.addAll(map2.keySet());
    -		Map map = new HashMap<>();
    +		Map map = MapUtil.newHashMap(key.size());
     		for (K t : key) {
     			X x = map1.get(t);
     			Y y = map2.get(t);
    
    From 4d9d2c4d90749aec60ec2f88e232bb887cbb02de Mon Sep 17 00:00:00 2001
    From: achao 
    Date: Wed, 10 Nov 2021 21:10:46 +0800
    Subject: [PATCH 15/43] =?UTF-8?q?=E5=A2=9E=E5=8A=A0CompletableFuture?=
     =?UTF-8?q?=E7=9B=B8=E5=85=B3=E5=B0=81=E8=A3=85?=
    MIME-Version: 1.0
    Content-Type: text/plain; charset=UTF-8
    Content-Transfer-Encoding: 8bit
    
    ---
     .../java/cn/hutool/core/thread/SyncUtil.java  | 53 +++++++++++++++++++
     1 file changed, 53 insertions(+)
     create mode 100644 hutool-core/src/main/java/cn/hutool/core/thread/SyncUtil.java
    
    diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/SyncUtil.java b/hutool-core/src/main/java/cn/hutool/core/thread/SyncUtil.java
    new file mode 100644
    index 000000000..5f4601b39
    --- /dev/null
    +++ b/hutool-core/src/main/java/cn/hutool/core/thread/SyncUtil.java
    @@ -0,0 +1,53 @@
    +package cn.hutool.core.thread;
    +
    +import cn.hutool.core.exceptions.ExceptionUtil;
    +
    +import java.lang.reflect.UndeclaredThrowableException;
    +import java.util.concurrent.CompletableFuture;
    +import java.util.concurrent.ExecutionException;
    +
    +/**
    + * CompletableFuture工具类,叫CompletableFutureUtil太长
    + *
    + * @author 
    + * @since 2021/11/10 0010 20:55
    + */
    +public class SyncUtil {
    +
    +	private SyncUtil() {
    +		/* Do not new me! */
    +	}
    +
    +	/**
    +	 * 等待所有任务执行完毕,包裹了异常
    +	 *
    +	 * @param tasks 并行任务
    +	 * @throws UndeclaredThrowableException 未受检异常
    +	 */
    +	public static void wait(CompletableFuture... tasks) {
    +		try {
    +			CompletableFuture.allOf(tasks).get();
    +		} catch (InterruptedException | ExecutionException e) {
    +			ExceptionUtil.wrapAndThrow(e);
    +		}
    +	}
    +
    +	/**
    +	 * 获取异步任务结果,包裹了异常
    +	 *
    +	 * @param task 异步任务
    +	 * @param   任务返回值类型
    +	 * @return 任务返回值
    +	 * @throws RuntimeException 未受检异常
    +	 */
    +	public static  T get(CompletableFuture task) {
    +		RuntimeException exception;
    +		try {
    +			return task.get();
    +		} catch (InterruptedException | ExecutionException e) {
    +			exception = ExceptionUtil.wrapRuntime(e);
    +		}
    +		throw exception;
    +	}
    +
    +}
    
    From e021f5256322f21c3cef158075f0c8dcd9841ed1 Mon Sep 17 00:00:00 2001
    From: achao 
    Date: Wed, 10 Nov 2021 21:21:01 +0800
    Subject: [PATCH 16/43] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B5=8B=E8=AF=95?=
     =?UTF-8?q?=E7=94=A8=E4=BE=8B?=
    MIME-Version: 1.0
    Content-Type: text/plain; charset=UTF-8
    Content-Transfer-Encoding: 8bit
    
    ---
     .../cn/hutool/core/thread/SyncUtilTest.java   | 36 +++++++++++++++++++
     1 file changed, 36 insertions(+)
     create mode 100644 hutool-core/src/test/java/cn/hutool/core/thread/SyncUtilTest.java
    
    diff --git a/hutool-core/src/test/java/cn/hutool/core/thread/SyncUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/thread/SyncUtilTest.java
    new file mode 100644
    index 000000000..846b4614d
    --- /dev/null
    +++ b/hutool-core/src/test/java/cn/hutool/core/thread/SyncUtilTest.java
    @@ -0,0 +1,36 @@
    +package cn.hutool.core.thread;
    +
    +import cn.hutool.core.lang.Assert;
    +import org.junit.Test;
    +
    +import java.util.concurrent.CompletableFuture;
    +import java.util.concurrent.TimeUnit;
    +
    +/**
    + * CompletableFuture工具类测试
    + *
    + * @author 
    + * @since 2021/11/10 0010 21:15
    + */
    +public class SyncUtilTest {
    +
    +	@Test
    +	public void waitAndGetTest() {
    +		CompletableFuture hutool = CompletableFuture.supplyAsync(() -> {
    +			ThreadUtil.sleep(3, TimeUnit.SECONDS);
    +			return "hutool";
    +		});
    +		CompletableFuture sweater = CompletableFuture.supplyAsync(() -> {
    +			ThreadUtil.sleep(4, TimeUnit.SECONDS);
    +			return "卫衣";
    +		});
    +		CompletableFuture warm = CompletableFuture.supplyAsync(() -> {
    +			ThreadUtil.sleep(5, TimeUnit.SECONDS);
    +			return "真暖和";
    +		});
    +		// 等待完成
    +		SyncUtil.wait(hutool, sweater, warm);
    +		// 获取结果
    +		Assert.isTrue("hutool卫衣真暖和".equals(SyncUtil.get(hutool) + SyncUtil.get(sweater) + SyncUtil.get(warm)));
    +	}
    +}
    
    From 662485fb2be7f70b0c3591638fd6d2e399235c4e Mon Sep 17 00:00:00 2001
    From: Looly 
    Date: Wed, 10 Nov 2021 22:48:11 +0800
    Subject: [PATCH 17/43] fix FileResourceBug
    
    ---
     CHANGELOG.md                                  |  3 +-
     .../hutool/core/io/resource/FileResource.java | 32 +++++++++++--------
     .../java/cn/hutool/core/date/ZodiacTest.java  |  4 +--
     .../excel/sax/handler/AbstractRowHandler.java |  4 +--
     .../poi/excel/sax/handler/BeanRowHandler.java |  6 ++--
     .../poi/excel/sax/handler/MapRowHandler.java  |  6 ++--
     .../poi/excel/sax/handler/RowHandler.java     |  4 +--
     .../cn/hutool/poi/excel/ExcelSaxReadTest.java |  4 +--
     8 files changed, 35 insertions(+), 28 deletions(-)
    
    diff --git a/CHANGELOG.md b/CHANGELOG.md
    index a7c09f083..6f9e9ef1e 100644
    --- a/CHANGELOG.md
    +++ b/CHANGELOG.md
    @@ -3,10 +3,11 @@
     
     -------------------------------------------------------------------------------------------------------------
     
    -# 5.7.17 (2021-11-07)
    +# 5.7.17 (2021-11-10)
     
     ### 🐣新特性
     ### 🐞Bug修复
    +* 【core   】     修复FileResource构造fileName参数无效问题(issue#1942@Github)
     
     -------------------------------------------------------------------------------------------------------------
     
    diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java
    index 3c44d8d50..9bac9521c 100644
    --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java
    +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java
    @@ -1,6 +1,8 @@
     package cn.hutool.core.io.resource;
     
     import cn.hutool.core.io.FileUtil;
    +import cn.hutool.core.lang.Assert;
    +import cn.hutool.core.util.ObjectUtil;
     import cn.hutool.core.util.URLUtil;
     
     import java.io.File;
    @@ -18,11 +20,21 @@ public class FileResource implements Resource, Serializable {
     	private static final long serialVersionUID = 1L;
     
     	private final File file;
    +	private final String name;
     
     	// ----------------------------------------------------------------------- Constructor start
     	/**
     	 * 构造
     	 *
    +	 * @param path 文件绝对路径或相对ClassPath路径,但是这个路径不能指向一个jar包中的文件
    +	 */
    +	public FileResource(String path) {
    +		this(FileUtil.file(path));
    +	}
    +
    +	/**
    +	 * 构造,文件名使用文件本身的名字,带扩展名
    +	 *
     	 * @param path 文件
     	 * @since 4.4.1
     	 */
    @@ -31,37 +43,31 @@ public class FileResource implements Resource, Serializable {
     	}
     
     	/**
    -	 * 构造
    +	 * 构造,文件名使用文件本身的名字,带扩展名
     	 *
     	 * @param file 文件
     	 */
     	public FileResource(File file) {
    -		this(file, file.getName());
    +		this(file, null);
     	}
     
     	/**
     	 * 构造
     	 *
     	 * @param file 文件
    -	 * @param fileName 文件名,如果为null获取文件本身的文件名
    +	 * @param fileName 文件名,带扩展名,如果为null获取文件本身的文件名
     	 */
     	public FileResource(File file, String fileName) {
    +		Assert.notNull(file, "File must be not null !");
     		this.file = file;
    +		this.name = ObjectUtil.defaultIfNull(fileName, file.getName());
     	}
     
    -	/**
    -	 * 构造
    -	 *
    -	 * @param path 文件绝对路径或相对ClassPath路径,但是这个路径不能指向一个jar包中的文件
    -	 */
    -	public FileResource(String path) {
    -		this(FileUtil.file(path));
    -	}
     	// ----------------------------------------------------------------------- Constructor end
     
     	@Override
     	public String getName() {
    -		return this.file.getName();
    +		return this.name;
     	}
     
     	@Override
    @@ -89,6 +95,6 @@ public class FileResource implements Resource, Serializable {
     	 */
     	@Override
     	public String toString() {
    -		return (null == this.file) ? "null" : this.file.toString();
    +		return this.file.toString();
     	}
     }
    diff --git a/hutool-core/src/test/java/cn/hutool/core/date/ZodiacTest.java b/hutool-core/src/test/java/cn/hutool/core/date/ZodiacTest.java
    index 8d4eacdd7..dac1329c3 100644
    --- a/hutool-core/src/test/java/cn/hutool/core/date/ZodiacTest.java
    +++ b/hutool-core/src/test/java/cn/hutool/core/date/ZodiacTest.java
    @@ -4,14 +4,14 @@ import org.junit.Assert;
     import org.junit.Test;
     
     public class ZodiacTest {
    -	
    +
     	@Test
     	public void getZodiacTest() {
     		Assert.assertEquals("摩羯座", Zodiac.getZodiac(Month.JANUARY, 19));
     		Assert.assertEquals("水瓶座", Zodiac.getZodiac(Month.JANUARY, 20));
     		Assert.assertEquals("巨蟹座", Zodiac.getZodiac(6, 17));
     	}
    -	
    +
     	@Test
     	public void getChineseZodiacTest() {
     		Assert.assertEquals("狗", Zodiac.getChineseZodiac(1994));
    diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/handler/AbstractRowHandler.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/handler/AbstractRowHandler.java
    index 95b71164e..71d31d1c8 100644
    --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/handler/AbstractRowHandler.java
    +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/handler/AbstractRowHandler.java
    @@ -40,12 +40,12 @@ public abstract class AbstractRowHandler implements RowHandler {
     	}
     
     	@Override
    -	public void handle(int sheetIndex, long rowIndex, List rowList) {
    +	public void handle(int sheetIndex, long rowIndex, List rowCells) {
     		Assert.notNull(convertFunc);
     		if (rowIndex < this.startRowIndex || rowIndex > this.endRowIndex) {
     			return;
     		}
    -		handleData(sheetIndex, rowIndex, convertFunc.callWithRuntimeException(rowList));
    +		handleData(sheetIndex, rowIndex, convertFunc.callWithRuntimeException(rowCells));
     	}
     
     	/**
    diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/handler/BeanRowHandler.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/handler/BeanRowHandler.java
    index 8ff603bcb..60d888ebc 100644
    --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/handler/BeanRowHandler.java
    +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/handler/BeanRowHandler.java
    @@ -42,11 +42,11 @@ public abstract class BeanRowHandler extends AbstractRowHandler {
     	}
     
     	@Override
    -	public void handle(int sheetIndex, long rowIndex, List rowList) {
    +	public void handle(int sheetIndex, long rowIndex, List rowCells) {
     		if (rowIndex == this.headerRowIndex) {
    -			this.headerList = ListUtil.unmodifiable(Convert.toList(String.class, rowList));
    +			this.headerList = ListUtil.unmodifiable(Convert.toList(String.class, rowCells));
     			return;
     		}
    -		super.handle(sheetIndex, rowIndex, rowList);
    +		super.handle(sheetIndex, rowIndex, rowCells);
     	}
     }
    diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/handler/MapRowHandler.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/handler/MapRowHandler.java
    index 32e3e1c52..a4c53dc0f 100644
    --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/handler/MapRowHandler.java
    +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/handler/MapRowHandler.java
    @@ -39,11 +39,11 @@ public abstract class MapRowHandler extends AbstractRowHandler rowList) {
    +	public void handle(int sheetIndex, long rowIndex, List rowCells) {
     		if (rowIndex == this.headerRowIndex) {
    -			this.headerList = ListUtil.unmodifiable(Convert.toList(String.class, rowList));
    +			this.headerList = ListUtil.unmodifiable(Convert.toList(String.class, rowCells));
     			return;
     		}
    -		super.handle(sheetIndex, rowIndex, rowList);
    +		super.handle(sheetIndex, rowIndex, rowCells);
     	}
     }
    diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/handler/RowHandler.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/handler/RowHandler.java
    index 34de9aa0a..0a85499bb 100644
    --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/handler/RowHandler.java
    +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/handler/RowHandler.java
    @@ -17,9 +17,9 @@ public interface RowHandler {
     	 *
     	 * @param sheetIndex 当前Sheet序号
     	 * @param rowIndex   当前行号,从0开始计数
    -	 * @param rowList    行数据列表
    +	 * @param rowCells   行数据,每个Object表示一个单元格的值
     	 */
    -	void handle(int sheetIndex, long rowIndex, List rowList);
    +	void handle(int sheetIndex, long rowIndex, List rowCells);
     
     	/**
     	 * 处理一个单元格的数据
    diff --git a/hutool-poi/src/test/java/cn/hutool/poi/excel/ExcelSaxReadTest.java b/hutool-poi/src/test/java/cn/hutool/poi/excel/ExcelSaxReadTest.java
    index cda21e304..a4f9d13c3 100644
    --- a/hutool-poi/src/test/java/cn/hutool/poi/excel/ExcelSaxReadTest.java
    +++ b/hutool-poi/src/test/java/cn/hutool/poi/excel/ExcelSaxReadTest.java
    @@ -125,7 +125,7 @@ public class ExcelSaxReadTest {
     					}
     
     					@Override
    -					public void handle(int sheetIndex, long rowIndex, List rowList) {
    +					public void handle(int sheetIndex, long rowIndex, List rowCells) {
     
     					}
     				}
    @@ -143,7 +143,7 @@ public class ExcelSaxReadTest {
     					}
     
     					@Override
    -					public void handle(int sheetIndex, long rowIndex, List rowList) {
    +					public void handle(int sheetIndex, long rowIndex, List rowCells) {
     					}
     				}
     		);
    
    From 8fda1b9ac5246b1a43af4b7fa38472a9766c37ca Mon Sep 17 00:00:00 2001
    From: Looly 
    Date: Wed, 10 Nov 2021 23:12:53 +0800
    Subject: [PATCH 18/43] add AsyncUtil
    
    ---
     CHANGELOG.md                                  |  1 +
     .../thread/{SyncUtil.java => AsyncUtil.java}  | 35 ++++++++++-------
     .../hutool/core/thread/ThreadException.java   | 38 +++++++++++++++++++
     .../java/cn/hutool/core/util/NumberUtil.java  |  5 +--
     .../{SyncUtilTest.java => AsyncUtilTest.java} | 14 ++++---
     .../cn/hutool/core/util/NumberUtilTest.java   |  6 +++
     6 files changed, 76 insertions(+), 23 deletions(-)
     rename hutool-core/src/main/java/cn/hutool/core/thread/{SyncUtil.java => AsyncUtil.java} (55%)
     create mode 100644 hutool-core/src/main/java/cn/hutool/core/thread/ThreadException.java
     rename hutool-core/src/test/java/cn/hutool/core/thread/{SyncUtilTest.java => AsyncUtilTest.java} (69%)
    
    diff --git a/CHANGELOG.md b/CHANGELOG.md
    index 6f9e9ef1e..7bbd7a4ad 100644
    --- a/CHANGELOG.md
    +++ b/CHANGELOG.md
    @@ -6,6 +6,7 @@
     # 5.7.17 (2021-11-10)
     
     ### 🐣新特性
    +* 【core   】     增加AsyncUtil(pr#457@Gitee)
     ### 🐞Bug修复
     * 【core   】     修复FileResource构造fileName参数无效问题(issue#1942@Github)
     
    diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/SyncUtil.java b/hutool-core/src/main/java/cn/hutool/core/thread/AsyncUtil.java
    similarity index 55%
    rename from hutool-core/src/main/java/cn/hutool/core/thread/SyncUtil.java
    rename to hutool-core/src/main/java/cn/hutool/core/thread/AsyncUtil.java
    index 5f4601b39..7a0d9fdf2 100644
    --- a/hutool-core/src/main/java/cn/hutool/core/thread/SyncUtil.java
    +++ b/hutool-core/src/main/java/cn/hutool/core/thread/AsyncUtil.java
    @@ -1,22 +1,17 @@
     package cn.hutool.core.thread;
     
    -import cn.hutool.core.exceptions.ExceptionUtil;
    -
     import java.lang.reflect.UndeclaredThrowableException;
     import java.util.concurrent.CompletableFuture;
     import java.util.concurrent.ExecutionException;
     
     /**
    - * CompletableFuture工具类,叫CompletableFutureUtil太长
    + * {@link CompletableFuture}异步工具类
    + * {@link CompletableFuture} 是 Future 的改进,可以通过传入回调对象,在任务完成后调用之 * * @author - * @since 2021/11/10 0010 20:55 + * @since 5.7.17 */ -public class SyncUtil { - - private SyncUtil() { - /* Do not new me! */ - } +public class AsyncUtil { /** * 等待所有任务执行完毕,包裹了异常 @@ -24,11 +19,25 @@ public class SyncUtil { * @param tasks 并行任务 * @throws UndeclaredThrowableException 未受检异常 */ - public static void wait(CompletableFuture... tasks) { + public static void waitAll(CompletableFuture... tasks) { try { CompletableFuture.allOf(tasks).get(); } catch (InterruptedException | ExecutionException e) { - ExceptionUtil.wrapAndThrow(e); + throw new ThreadException(e); + } + } + + /** + * 等待任意一个任务执行完毕,包裹了异常 + * + * @param tasks 并行任务 + * @throws UndeclaredThrowableException 未受检异常 + */ + public static void waitAny(CompletableFuture... tasks) { + try { + CompletableFuture.anyOf(tasks).get(); + } catch (InterruptedException | ExecutionException e) { + throw new ThreadException(e); } } @@ -41,13 +50,11 @@ public class SyncUtil { * @throws RuntimeException 未受检异常 */ public static T get(CompletableFuture task) { - RuntimeException exception; try { return task.get(); } catch (InterruptedException | ExecutionException e) { - exception = ExceptionUtil.wrapRuntime(e); + throw new ThreadException(e); } - throw exception; } } diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/ThreadException.java b/hutool-core/src/main/java/cn/hutool/core/thread/ThreadException.java new file mode 100644 index 000000000..b61963b2c --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/thread/ThreadException.java @@ -0,0 +1,38 @@ +package cn.hutool.core.thread; + +import cn.hutool.core.exceptions.ExceptionUtil; +import cn.hutool.core.util.StrUtil; + +/** + * 工具类异常 + * + * @author looly + * @since 5.7.17 + */ +public class ThreadException extends RuntimeException { + private static final long serialVersionUID = 5253124428623713216L; + + public ThreadException(Throwable e) { + super(ExceptionUtil.getMessage(e), e); + } + + public ThreadException(String message) { + super(message); + } + + public ThreadException(String messageTemplate, Object... params) { + super(StrUtil.format(messageTemplate, params)); + } + + public ThreadException(String message, Throwable throwable) { + super(message, throwable); + } + + public ThreadException(String message, Throwable throwable, boolean enableSuppression, boolean writableStackTrace) { + super(message, throwable, enableSuppression, writableStackTrace); + } + + public ThreadException(Throwable throwable, String messageTemplate, Object... params) { + super(StrUtil.format(messageTemplate, params), throwable); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java index ec92e1a21..8953e48b0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java @@ -12,7 +12,6 @@ import java.text.NumberFormat; import java.text.ParseException; import java.util.Collection; import java.util.HashSet; -import java.util.Random; import java.util.Set; /** @@ -1362,12 +1361,12 @@ public class NumberUtil { throw new UtilException("Size is larger than range between begin and end!"); } - Set set = new HashSet<>(Math.max((int) (size / .75f) + 1, 16)); + Set set = new HashSet<>(size, 1); while (set.size() < size) { set.add(begin + RandomUtil.randomInt(end - begin)); } - return set.toArray(new Integer[size]); + return set.toArray(new Integer[0]); } // ------------------------------------------------------------------------------------------- range diff --git a/hutool-core/src/test/java/cn/hutool/core/thread/SyncUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/thread/AsyncUtilTest.java similarity index 69% rename from hutool-core/src/test/java/cn/hutool/core/thread/SyncUtilTest.java rename to hutool-core/src/test/java/cn/hutool/core/thread/AsyncUtilTest.java index 846b4614d..f6a00a486 100644 --- a/hutool-core/src/test/java/cn/hutool/core/thread/SyncUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/thread/AsyncUtilTest.java @@ -1,6 +1,7 @@ package cn.hutool.core.thread; import cn.hutool.core.lang.Assert; +import org.junit.Ignore; import org.junit.Test; import java.util.concurrent.CompletableFuture; @@ -12,25 +13,26 @@ import java.util.concurrent.TimeUnit; * @author * @since 2021/11/10 0010 21:15 */ -public class SyncUtilTest { +public class AsyncUtilTest { @Test + @Ignore public void waitAndGetTest() { CompletableFuture hutool = CompletableFuture.supplyAsync(() -> { - ThreadUtil.sleep(3, TimeUnit.SECONDS); + ThreadUtil.sleep(1, TimeUnit.SECONDS); return "hutool"; }); CompletableFuture sweater = CompletableFuture.supplyAsync(() -> { - ThreadUtil.sleep(4, TimeUnit.SECONDS); + ThreadUtil.sleep(2, TimeUnit.SECONDS); return "卫衣"; }); CompletableFuture warm = CompletableFuture.supplyAsync(() -> { - ThreadUtil.sleep(5, TimeUnit.SECONDS); + ThreadUtil.sleep(3, TimeUnit.SECONDS); return "真暖和"; }); // 等待完成 - SyncUtil.wait(hutool, sweater, warm); + AsyncUtil.waitAll(hutool, sweater, warm); // 获取结果 - Assert.isTrue("hutool卫衣真暖和".equals(SyncUtil.get(hutool) + SyncUtil.get(sweater) + SyncUtil.get(warm))); + Assert.isTrue("hutool卫衣真暖和".equals(AsyncUtil.get(hutool) + AsyncUtil.get(sweater) + AsyncUtil.get(warm))); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/NumberUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/NumberUtilTest.java index ffdbd953e..137c849cd 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/NumberUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/NumberUtilTest.java @@ -400,4 +400,10 @@ public class NumberUtilTest { final String s = new BigDecimal(num).toPlainString(); Assert.assertEquals("5344342.34", s); } + + @Test + public void generateBySetTest(){ + final Integer[] integers = NumberUtil.generateBySet(10, 100, 5); + Assert.assertEquals(5, integers.length); + } } From f9e5625744433805a8417c40b27c892c953a0627 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 11 Nov 2021 01:19:53 +0800 Subject: [PATCH 19/43] add HttpResource --- CHANGELOG.md | 3 +- .../main/java/cn/hutool/core/io/IoUtil.java | 19 +++ .../io/resource/CharSequenceResource.java | 3 +- .../main/java/cn/hutool/http/ContentType.java | 6 +- .../java/cn/hutool/http/HttpResource.java | 56 +++++++++ .../cn/hutool/http/body/MultipartBody.java | 108 ++++++++++++++---- .../hutool/http/body/MultipartBodyTest.java | 27 +++++ 7 files changed, 197 insertions(+), 25 deletions(-) create mode 100644 hutool-http/src/main/java/cn/hutool/http/HttpResource.java create mode 100644 hutool-http/src/test/java/cn/hutool/http/body/MultipartBodyTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bbd7a4ad..810316039 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,10 +3,11 @@ ------------------------------------------------------------------------------------------------------------- -# 5.7.17 (2021-11-10) +# 5.7.17 (2021-11-11) ### 🐣新特性 * 【core 】 增加AsyncUtil(pr#457@Gitee) +* 【http 】 增加HttpResource(issue#1943@Github) ### 🐞Bug修复 * 【core 】 修复FileResource构造fileName参数无效问题(issue#1942@Github) diff --git a/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java index 02699d4a4..b9b3ecdda 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java @@ -32,6 +32,7 @@ import java.io.PushbackInputStream; import java.io.PushbackReader; import java.io.Reader; import java.io.Serializable; +import java.io.UnsupportedEncodingException; import java.io.Writer; import java.nio.CharBuffer; import java.nio.channels.FileChannel; @@ -622,6 +623,9 @@ public class IoUtil extends NioUtil { if (in == null) { throw new IllegalArgumentException("The InputStream must not be null"); } + if(null != clazz){ + in.accept(clazz); + } try { //noinspection unchecked return (T) in.readObject(); @@ -1331,4 +1335,19 @@ public class IoUtil extends NioUtil { public static LineIter lineIter(InputStream in, Charset charset) { return new LineIter(in, charset); } + + /** + * {@link ByteArrayOutputStream} 转换为String + * @param out {@link ByteArrayOutputStream} + * @param charset 编码 + * @return 字符串 + * @since 5.7.17 + */ + public static String toStr(ByteArrayOutputStream out, Charset charset){ + try { + return out.toString(charset.name()); + } catch (UnsupportedEncodingException e) { + throw new IORuntimeException(e); + } + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/CharSequenceResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/CharSequenceResource.java index cf0d851d5..a6950d94e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/CharSequenceResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/CharSequenceResource.java @@ -3,6 +3,7 @@ package cn.hutool.core.io.resource; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; import java.io.BufferedReader; import java.io.ByteArrayInputStream; @@ -59,7 +60,7 @@ public class CharSequenceResource implements Resource, Serializable { @Override public String getName() { - return this.name.toString(); + return StrUtil.str(this.name); } @Override diff --git a/hutool-http/src/main/java/cn/hutool/http/ContentType.java b/hutool-http/src/main/java/cn/hutool/http/ContentType.java index 29064428c..7c1759a2d 100644 --- a/hutool-http/src/main/java/cn/hutool/http/ContentType.java +++ b/hutool-http/src/main/java/cn/hutool/http/ContentType.java @@ -39,7 +39,11 @@ public enum ContentType { /** * text/html编码 */ - TEXT_HTML("text/html"); + TEXT_HTML("text/html"), + /** + * application/octet-stream编码 + */ + OCTET_STREAM("application/octet-stream"); private final String value; diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpResource.java b/hutool-http/src/main/java/cn/hutool/http/HttpResource.java new file mode 100644 index 000000000..9e6542efd --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/http/HttpResource.java @@ -0,0 +1,56 @@ +package cn.hutool.http; + +import cn.hutool.core.io.resource.Resource; +import cn.hutool.core.lang.Assert; + +import java.io.InputStream; +import java.io.Serializable; +import java.net.URL; + +/** + * HTTP资源,可自定义Content-Type + * + * @author looly + * @since 5.7.17 + */ +public class HttpResource implements Resource, Serializable { + private static final long serialVersionUID = 1L; + + private final Resource resource; + private final String contentType; + + /** + * 构造 + * + * @param resource 资源,非空 + * @param contentType Content-Type类型,{@code null}表示不设置 + */ + public HttpResource(Resource resource, String contentType) { + this.resource = Assert.notNull(resource, "Resource must be not null !"); + this.contentType = contentType; + } + + @Override + public String getName() { + return resource.getName(); + } + + @Override + public URL getUrl() { + return resource.getUrl(); + } + + @Override + public InputStream getStream() { + return resource.getStream(); + } + + /** + * 获取自定义Content-Type类型 + * + * @return Content-Type类型 + */ + public String getContentType() { + return this.contentType; + } +} diff --git a/hutool-http/src/main/java/cn/hutool/http/body/MultipartBody.java b/hutool-http/src/main/java/cn/hutool/http/body/MultipartBody.java index 0384a0e61..8df9c3902 100644 --- a/hutool-http/src/main/java/cn/hutool/http/body/MultipartBody.java +++ b/hutool-http/src/main/java/cn/hutool/http/body/MultipartBody.java @@ -5,31 +5,33 @@ import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.resource.MultiResource; import cn.hutool.core.io.resource.Resource; import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.ContentType; +import cn.hutool.http.HttpResource; import cn.hutool.http.HttpUtil; +import java.io.ByteArrayOutputStream; import java.io.OutputStream; import java.nio.charset.Charset; import java.util.Map; /** - * Multipart/form-data数据的请求体封装 + * Multipart/form-data数据的请求体封装
    + * 遵循RFC2388规范 * * @author looly * @since 5.3.5 */ -public class MultipartBody implements RequestBody{ +public class MultipartBody implements RequestBody { private static final String BOUNDARY = "--------------------Hutool_" + RandomUtil.randomString(16); private static final String BOUNDARY_END = StrUtil.format("--{}--\r\n", BOUNDARY); - private static final String CONTENT_DISPOSITION_TEMPLATE = "Content-Disposition: form-data; name=\"{}\"\r\n\r\n"; + private static final String CONTENT_DISPOSITION_TEMPLATE = "Content-Disposition: form-data; name=\"{}\"\r\n"; private static final String CONTENT_DISPOSITION_FILE_TEMPLATE = "Content-Disposition: form-data; name=\"{}\"; filename=\"{}\"\r\n"; private static final String CONTENT_TYPE_MULTIPART_PREFIX = ContentType.MULTIPART.getValue() + "; boundary="; - private static final String CONTENT_TYPE_FILE_TEMPLATE = "Content-Type: {}\r\n\r\n"; + private static final String CONTENT_TYPE_FILE_TEMPLATE = "Content-Type: {}\r\n"; /** * 存储表单数据 @@ -42,11 +44,12 @@ public class MultipartBody implements RequestBody{ /** * 根据已有表单内容,构建MultipartBody - * @param form 表单 + * + * @param form 表单 * @param charset 编码 * @return MultipartBody */ - public static MultipartBody create(Map form, Charset charset){ + public static MultipartBody create(Map form, Charset charset) { return new MultipartBody(form, charset); } @@ -55,15 +58,15 @@ public class MultipartBody implements RequestBody{ * * @return Multipart的Content-Type类型 */ - public static String getContentType(){ + public static String getContentType() { return CONTENT_TYPE_MULTIPART_PREFIX + BOUNDARY; } /** * 构造 * - * @param form 表单 - * @param charset 编码 + * @param form 表单 + * @param charset 编码 */ public MultipartBody(Map form, Charset charset) { this.form = form; @@ -81,6 +84,13 @@ public class MultipartBody implements RequestBody{ formEnd(out); } + @Override + public String toString() { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + write(out); + return IoUtil.toStr(out, this.charset); + } + // 普通字符串数据 /** @@ -97,10 +107,26 @@ public class MultipartBody implements RequestBody{ } /** - * 添加Multipart表单的数据项 + * 添加Multipart表单的数据项
    + *
    +	 *     --分隔符(boundary)[换行]
    +	 *     Content-Disposition: form-data; name="参数名"[换行]
    +	 *     [换行]
    +	 *     参数值[换行]
    +	 * 
    + * + * 或者: + * + *
    +	 *     --分隔符(boundary)[换行]
    +	 *     Content-Disposition: form-data; name="表单名"; filename="文件名"[换行]
    +	 *     Content-Type: MIME类型[换行]
    +	 *     [换行]
    +	 *     文件的二进制内容[换行]
    +	 * 
    * * @param formFieldName 表单名 - * @param value 值,可以是普通值、资源(如文件等) + * @param value 值,可以是普通值、资源(如文件等) * @param out Http流 * @throws IORuntimeException IO异常 */ @@ -113,25 +139,63 @@ public class MultipartBody implements RequestBody{ return; } + // --分隔符(boundary)[换行] write(out, "--", BOUNDARY, StrUtil.CRLF); - if(value instanceof Resource){ - // 文件资源(二进制资源) - final Resource resource = (Resource)value; - final String fileName = resource.getName(); - write(out, StrUtil.format(CONTENT_DISPOSITION_FILE_TEMPLATE, formFieldName, ObjectUtil.defaultIfNull(fileName, formFieldName))); - // 根据name的扩展名指定互联网媒体类型,默认二进制流数据 - write(out, StrUtil.format(CONTENT_TYPE_FILE_TEMPLATE, HttpUtil.getMimeType(fileName, "application/octet-stream"))); - resource.writeTo(out); - } else{ - // 普通数据 + if (value instanceof Resource) { + appendResource(formFieldName, (Resource) value, out); + } else { + /* + * Content-Disposition: form-data; name="参数名"[换行] + * [换行] + * 参数值 + */ write(out, StrUtil.format(CONTENT_DISPOSITION_TEMPLATE, formFieldName)); + write(out, StrUtil.CRLF); write(out, value); } write(out, StrUtil.CRLF); } + /** + * 添加Multipart表单的Resource数据项,支持包括{@link HttpResource}资源格式 + * + * @param formFieldName 表单名 + * @param resource 资源 + * @param out Http流 + * @throws IORuntimeException IO异常 + */ + private void appendResource(String formFieldName, Resource resource, OutputStream out) throws IORuntimeException { + final String fileName = resource.getName(); + + // Content-Disposition + if (null == fileName) { + // Content-Disposition: form-data; name="参数名"[换行] + write(out, StrUtil.format(CONTENT_DISPOSITION_TEMPLATE, formFieldName)); + } else { + // Content-Disposition: form-data; name="参数名"; filename="文件名"[换行] + write(out, StrUtil.format(CONTENT_DISPOSITION_FILE_TEMPLATE, formFieldName, fileName)); + } + + // Content-Type + if (resource instanceof HttpResource) { + final String contentType = ((HttpResource) resource).getContentType(); + if (StrUtil.isNotBlank(contentType)) { + // Content-Type: 类型[换行] + write(out, StrUtil.format(CONTENT_TYPE_FILE_TEMPLATE, contentType)); + } + } else { + // 根据name的扩展名指定互联网媒体类型,默认二进制流数据 + write(out, StrUtil.format(CONTENT_TYPE_FILE_TEMPLATE, + HttpUtil.getMimeType(fileName, ContentType.OCTET_STREAM.getValue()))); + } + + // 内容 + write(out, "\r\n"); + resource.writeTo(out); + } + /** * 上传表单结束 * diff --git a/hutool-http/src/test/java/cn/hutool/http/body/MultipartBodyTest.java b/hutool-http/src/test/java/cn/hutool/http/body/MultipartBodyTest.java new file mode 100644 index 000000000..edca37138 --- /dev/null +++ b/hutool-http/src/test/java/cn/hutool/http/body/MultipartBodyTest.java @@ -0,0 +1,27 @@ +package cn.hutool.http.body; + +import cn.hutool.core.io.resource.StringResource; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.http.HttpResource; +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +public class MultipartBodyTest { + + @Test + public void buildTest(){ + Map form = new HashMap<>(); + form.put("pic1", "pic1 content"); + form.put("pic2", new HttpResource( + new StringResource("pic2 content"), "text/plain")); + form.put("pic3", new HttpResource( + new StringResource("pic3 content", "pic3.jpg"), "image/jpeg")); + + final MultipartBody body = MultipartBody.create(form, CharsetUtil.CHARSET_UTF_8); + + Assert.assertNotNull(body.toString()); + } +} From 95afb479d91b5c25e313176be6c8f40345f5f6d6 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 11 Nov 2021 01:42:39 +0800 Subject: [PATCH 20/43] add XXXBody --- .../main/java/cn/hutool/http/HttpRequest.java | 37 +++++------------- .../java/cn/hutool/http/body/BytesBody.java | 39 +++++++++++++++++++ .../hutool/http/body/FormUrlEncodedBody.java | 38 ++++++++++++++++++ .../java/cn/hutool/http/body/RequestBody.java | 16 ++++++++ 4 files changed, 102 insertions(+), 28 deletions(-) create mode 100644 hutool-http/src/main/java/cn/hutool/http/body/BytesBody.java create mode 100644 hutool-http/src/main/java/cn/hutool/http/body/FormUrlEncodedBody.java diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index f52f570f2..12a14e699 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -3,7 +3,6 @@ package cn.hutool.http; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.convert.Convert; import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.resource.BytesResource; import cn.hutool.core.io.resource.FileResource; import cn.hutool.core.io.resource.MultiFileResource; @@ -12,18 +11,19 @@ import cn.hutool.core.lang.Assert; import cn.hutool.core.map.MapUtil; import cn.hutool.core.net.SSLUtil; import cn.hutool.core.net.url.UrlBuilder; -import cn.hutool.core.net.url.UrlQuery; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.http.body.BytesBody; +import cn.hutool.http.body.FormUrlEncodedBody; import cn.hutool.http.body.MultipartBody; +import cn.hutool.http.body.RequestBody; import cn.hutool.http.cookie.GlobalCookieManager; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLSocketFactory; import java.io.File; import java.io.IOException; -import java.io.OutputStream; import java.net.CookieManager; import java.net.HttpCookie; import java.net.HttpURLConnection; @@ -1212,23 +1212,13 @@ public class HttpRequest extends HttpBase { } // Write的时候会优先使用body中的内容,write时自动关闭OutputStream - byte[] content; + RequestBody body; if (ArrayUtil.isNotEmpty(this.bodyBytes)) { - content = this.bodyBytes; + body = BytesBody.create(this.bodyBytes); } else { - content = StrUtil.bytes(getFormUrlEncoded(), this.charset); + body = FormUrlEncodedBody.create(this.form, this.charset); } - IoUtil.write(this.httpConnection.getOutputStream(), true, content); - } - - /** - * 获取编码后的表单数据,无表单数据返回"" - * - * @return 编码后的表单数据,无表单数据返回"" - * @since 5.3.2 - */ - private String getFormUrlEncoded() { - return UrlQuery.of(this.form, true).build(this.charset); + body.writeClose(this.httpConnection.getOutputStream()); } /** @@ -1238,18 +1228,9 @@ public class HttpRequest extends HttpBase { * @throws IOException IO异常 */ private void sendMultipart() throws IOException { - setMultipart();// 设置表单类型为Multipart - - try (OutputStream out = this.httpConnection.getOutputStream()) { - MultipartBody.create(this.form, this.charset).write(out); - } - } - - /** - * 设置表单类型为Multipart(文件上传) - */ - private void setMultipart() { + //设置表单类型为Multipart(文件上传) this.httpConnection.header(Header.CONTENT_TYPE, MultipartBody.getContentType(), true); + MultipartBody.create(this.form, this.charset).writeClose(this.httpConnection.getOutputStream()); } /** diff --git a/hutool-http/src/main/java/cn/hutool/http/body/BytesBody.java b/hutool-http/src/main/java/cn/hutool/http/body/BytesBody.java new file mode 100644 index 000000000..138b48f3a --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/http/body/BytesBody.java @@ -0,0 +1,39 @@ +package cn.hutool.http.body; + +import cn.hutool.core.io.IoUtil; + +import java.io.OutputStream; + +/** + * bytes类型的Http request body,主要发送编码后的表单数据或rest body(如JSON或XML) + * + * @since 5.7.17 + * @author looly + */ +public class BytesBody implements RequestBody { + + private final byte[] content; + + /** + * 创建 Http request body + * @param content body内容,编码后 + * @return BytesBody + */ + public static BytesBody create(byte[] content){ + return new BytesBody(content); + } + + /** + * 构造 + * + * @param content Body内容,编码后 + */ + public BytesBody(byte[] content) { + this.content = content; + } + + @Override + public void write(OutputStream out) { + IoUtil.write(out, false, content); + } +} diff --git a/hutool-http/src/main/java/cn/hutool/http/body/FormUrlEncodedBody.java b/hutool-http/src/main/java/cn/hutool/http/body/FormUrlEncodedBody.java new file mode 100644 index 000000000..aa675b0a2 --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/http/body/FormUrlEncodedBody.java @@ -0,0 +1,38 @@ +package cn.hutool.http.body; + +import cn.hutool.core.net.url.UrlQuery; +import cn.hutool.core.util.StrUtil; + +import java.nio.charset.Charset; +import java.util.Map; + +/** + * application/x-www-form-urlencoded 类型请求body封装 + * + * @author looly + * @since 5.7.17 + */ +public class FormUrlEncodedBody extends BytesBody { + + /** + * 创建 Http request body + * + * @param form 表单 + * @param charset 编码 + * @return FormUrlEncodedBody + */ + public static FormUrlEncodedBody create(Map form, Charset charset) { + return new FormUrlEncodedBody(form, charset); + } + + /** + * 构造 + * + * @param form 表单 + * @param charset 编码 + */ + public FormUrlEncodedBody(Map form, Charset charset) { + super(StrUtil.bytes(UrlQuery.of(form, true).build(charset), charset)); + } + +} diff --git a/hutool-http/src/main/java/cn/hutool/http/body/RequestBody.java b/hutool-http/src/main/java/cn/hutool/http/body/RequestBody.java index 16b6a04e4..76fb9b378 100644 --- a/hutool-http/src/main/java/cn/hutool/http/body/RequestBody.java +++ b/hutool-http/src/main/java/cn/hutool/http/body/RequestBody.java @@ -1,5 +1,7 @@ package cn.hutool.http.body; +import cn.hutool.core.io.IoUtil; + import java.io.OutputStream; /** @@ -13,4 +15,18 @@ public interface RequestBody { * @param out out流 */ void write(OutputStream out); + + /** + * 写出并关闭{@link OutputStream} + * + * @param out {@link OutputStream} + * @since 5.7.17 + */ + default void writeClose(OutputStream out) { + try { + write(out); + } finally { + IoUtil.close(out); + } + } } From 237d6dbe2ed6c19c147f5c0384494c3dea9b610b Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 11 Nov 2021 02:25:56 +0800 Subject: [PATCH 21/43] add MultipartOutputStream --- CHANGELOG.md | 1 + .../cn/hutool/http/MultipartOutputStream.java | 172 ++++++++++++++++++ .../cn/hutool/http/body/MultipartBody.java | 146 +-------------- .../hutool/http/body/MultipartBodyTest.java | 1 + 4 files changed, 181 insertions(+), 139 deletions(-) create mode 100644 hutool-http/src/main/java/cn/hutool/http/MultipartOutputStream.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 810316039..d3f0448b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### 🐣新特性 * 【core 】 增加AsyncUtil(pr#457@Gitee) * 【http 】 增加HttpResource(issue#1943@Github) +* 【http 】 增加BytesBody、FormUrlEncodedBody ### 🐞Bug修复 * 【core 】 修复FileResource构造fileName参数无效问题(issue#1942@Github) diff --git a/hutool-http/src/main/java/cn/hutool/http/MultipartOutputStream.java b/hutool-http/src/main/java/cn/hutool/http/MultipartOutputStream.java new file mode 100644 index 000000000..5ca620ad1 --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/http/MultipartOutputStream.java @@ -0,0 +1,172 @@ +package cn.hutool.http; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.resource.MultiResource; +import cn.hutool.core.io.resource.Resource; +import cn.hutool.core.io.resource.StringResource; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.body.MultipartBody; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.Charset; + +/** + * Multipart/form-data输出流封装
    + * 遵循RFC2388规范 + * + * @since 5.7.17 + * @author looly + */ +public class MultipartOutputStream extends OutputStream { + + private static final String BOUNDARY = MultipartBody.BOUNDARY; + private static final String BOUNDARY_END = StrUtil.format("--{}--\r\n", BOUNDARY); + private static final String CONTENT_DISPOSITION_TEMPLATE = "Content-Disposition: form-data; name=\"{}\"\r\n"; + private static final String CONTENT_DISPOSITION_FILE_TEMPLATE = "Content-Disposition: form-data; name=\"{}\"; filename=\"{}\"\r\n"; + + private static final String CONTENT_TYPE_FILE_TEMPLATE = "Content-Type: {}\r\n"; + + private final OutputStream out; + private final Charset charset; + private boolean isFinish; + + /** + * 构造 + * + * @param out HTTP写出流 + * @param charset 编码 + */ + public MultipartOutputStream(OutputStream out, Charset charset) { + this.out = out; + this.charset = charset; + } + + /** + * 添加Multipart表单的数据项
    + *
    +	 *     --分隔符(boundary)[换行]
    +	 *     Content-Disposition: form-data; name="参数名"[换行]
    +	 *     [换行]
    +	 *     参数值[换行]
    +	 * 
    + *

    + * 或者: + * + *

    +	 *     --分隔符(boundary)[换行]
    +	 *     Content-Disposition: form-data; name="表单名"; filename="文件名"[换行]
    +	 *     Content-Type: MIME类型[换行]
    +	 *     [换行]
    +	 *     文件的二进制内容[换行]
    +	 * 
    + * + * @param formFieldName 表单名 + * @param value 值,可以是普通值、资源(如文件等) + * @throws IORuntimeException IO异常 + */ + public MultipartOutputStream write(String formFieldName, Object value) throws IORuntimeException { + // 多资源 + if (value instanceof MultiResource) { + for (Resource subResource : (MultiResource) value) { + write(formFieldName, subResource); + } + return this; + } + + // --分隔符(boundary)[换行] + beginPart(); + + if (value instanceof Resource) { + appendResource(formFieldName, (Resource) value); + } else { + appendResource(formFieldName, + new StringResource(Convert.toStr(value), null, this.charset)); + } + + write(StrUtil.CRLF); + return this; + } + + @Override + public void write(int b) throws IOException { + this.out.write(b); + } + + /** + * 上传表单结束 + * + * @throws IORuntimeException IO异常 + */ + public void finish() throws IORuntimeException { + if(false == isFinish){ + write(BOUNDARY_END); + this.isFinish = true; + } + } + + @Override + public void close() { + finish(); + IoUtil.close(this.out); + } + + /** + * 添加Multipart表单的Resource数据项,支持包括{@link HttpResource}资源格式 + * + * @param formFieldName 表单名 + * @param resource 资源 + * @throws IORuntimeException IO异常 + */ + private void appendResource(String formFieldName, Resource resource) throws IORuntimeException { + final String fileName = resource.getName(); + + // Content-Disposition + if (null == fileName) { + // Content-Disposition: form-data; name="参数名"[换行] + write(StrUtil.format(CONTENT_DISPOSITION_TEMPLATE, formFieldName)); + } else { + // Content-Disposition: form-data; name="参数名"; filename="文件名"[换行] + write(StrUtil.format(CONTENT_DISPOSITION_FILE_TEMPLATE, formFieldName, fileName)); + } + + // Content-Type + if (resource instanceof HttpResource) { + final String contentType = ((HttpResource) resource).getContentType(); + if (StrUtil.isNotBlank(contentType)) { + // Content-Type: 类型[换行] + write(StrUtil.format(CONTENT_TYPE_FILE_TEMPLATE, contentType)); + } + } else if(StrUtil.isNotEmpty(fileName)){ + // 根据name的扩展名指定互联网媒体类型,默认二进制流数据 + write(StrUtil.format(CONTENT_TYPE_FILE_TEMPLATE, + HttpUtil.getMimeType(fileName, ContentType.OCTET_STREAM.getValue()))); + } + + // 内容 + write("\r\n"); + resource.writeTo(this); + } + + /** + * part开始,写出:
    + *
    +	 *     --分隔符(boundary)[换行]
    +	 * 
    + */ + private void beginPart(){ + // --分隔符(boundary)[换行] + write("--", BOUNDARY, StrUtil.CRLF); + } + + /** + * 写出对象 + * + * @param objs 写出的对象(转换为字符串) + */ + private void write(Object... objs) { + IoUtil.write(this, this.charset, false, objs); + } +} diff --git a/hutool-http/src/main/java/cn/hutool/http/body/MultipartBody.java b/hutool-http/src/main/java/cn/hutool/http/body/MultipartBody.java index 8df9c3902..dbf955363 100644 --- a/hutool-http/src/main/java/cn/hutool/http/body/MultipartBody.java +++ b/hutool-http/src/main/java/cn/hutool/http/body/MultipartBody.java @@ -1,15 +1,10 @@ package cn.hutool.http.body; -import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; -import cn.hutool.core.io.resource.MultiResource; -import cn.hutool.core.io.resource.Resource; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.RandomUtil; -import cn.hutool.core.util.StrUtil; import cn.hutool.http.ContentType; -import cn.hutool.http.HttpResource; -import cn.hutool.http.HttpUtil; +import cn.hutool.http.MultipartOutputStream; import java.io.ByteArrayOutputStream; import java.io.OutputStream; @@ -25,13 +20,8 @@ import java.util.Map; */ public class MultipartBody implements RequestBody { - private static final String BOUNDARY = "--------------------Hutool_" + RandomUtil.randomString(16); - private static final String BOUNDARY_END = StrUtil.format("--{}--\r\n", BOUNDARY); - private static final String CONTENT_DISPOSITION_TEMPLATE = "Content-Disposition: form-data; name=\"{}\"\r\n"; - private static final String CONTENT_DISPOSITION_FILE_TEMPLATE = "Content-Disposition: form-data; name=\"{}\"; filename=\"{}\"\r\n"; - + public static final String BOUNDARY = "--------------------Hutool_" + RandomUtil.randomString(16); private static final String CONTENT_TYPE_MULTIPART_PREFIX = ContentType.MULTIPART.getValue() + "; boundary="; - private static final String CONTENT_TYPE_FILE_TEMPLATE = "Content-Type: {}\r\n"; /** * 存储表单数据 @@ -80,8 +70,11 @@ public class MultipartBody implements RequestBody { */ @Override public void write(OutputStream out) { - writeForm(out); - formEnd(out); + final MultipartOutputStream stream = new MultipartOutputStream(out, this.charset); + if (MapUtil.isNotEmpty(this.form)) { + this.form.forEach(stream::write); + } + stream.finish(); } @Override @@ -90,129 +83,4 @@ public class MultipartBody implements RequestBody { write(out); return IoUtil.toStr(out, this.charset); } - - // 普通字符串数据 - - /** - * 发送文件对象表单 - * - * @param out 输出流 - */ - private void writeForm(OutputStream out) { - if (MapUtil.isNotEmpty(this.form)) { - for (Map.Entry entry : this.form.entrySet()) { - appendPart(entry.getKey(), entry.getValue(), out); - } - } - } - - /** - * 添加Multipart表单的数据项
    - *
    -	 *     --分隔符(boundary)[换行]
    -	 *     Content-Disposition: form-data; name="参数名"[换行]
    -	 *     [换行]
    -	 *     参数值[换行]
    -	 * 
    - * - * 或者: - * - *
    -	 *     --分隔符(boundary)[换行]
    -	 *     Content-Disposition: form-data; name="表单名"; filename="文件名"[换行]
    -	 *     Content-Type: MIME类型[换行]
    -	 *     [换行]
    -	 *     文件的二进制内容[换行]
    -	 * 
    - * - * @param formFieldName 表单名 - * @param value 值,可以是普通值、资源(如文件等) - * @param out Http流 - * @throws IORuntimeException IO异常 - */ - private void appendPart(String formFieldName, Object value, OutputStream out) throws IORuntimeException { - // 多资源 - if (value instanceof MultiResource) { - for (Resource subResource : (MultiResource) value) { - appendPart(formFieldName, subResource, out); - } - return; - } - - // --分隔符(boundary)[换行] - write(out, "--", BOUNDARY, StrUtil.CRLF); - - if (value instanceof Resource) { - appendResource(formFieldName, (Resource) value, out); - } else { - /* - * Content-Disposition: form-data; name="参数名"[换行] - * [换行] - * 参数值 - */ - write(out, StrUtil.format(CONTENT_DISPOSITION_TEMPLATE, formFieldName)); - write(out, StrUtil.CRLF); - write(out, value); - } - - write(out, StrUtil.CRLF); - } - - /** - * 添加Multipart表单的Resource数据项,支持包括{@link HttpResource}资源格式 - * - * @param formFieldName 表单名 - * @param resource 资源 - * @param out Http流 - * @throws IORuntimeException IO异常 - */ - private void appendResource(String formFieldName, Resource resource, OutputStream out) throws IORuntimeException { - final String fileName = resource.getName(); - - // Content-Disposition - if (null == fileName) { - // Content-Disposition: form-data; name="参数名"[换行] - write(out, StrUtil.format(CONTENT_DISPOSITION_TEMPLATE, formFieldName)); - } else { - // Content-Disposition: form-data; name="参数名"; filename="文件名"[换行] - write(out, StrUtil.format(CONTENT_DISPOSITION_FILE_TEMPLATE, formFieldName, fileName)); - } - - // Content-Type - if (resource instanceof HttpResource) { - final String contentType = ((HttpResource) resource).getContentType(); - if (StrUtil.isNotBlank(contentType)) { - // Content-Type: 类型[换行] - write(out, StrUtil.format(CONTENT_TYPE_FILE_TEMPLATE, contentType)); - } - } else { - // 根据name的扩展名指定互联网媒体类型,默认二进制流数据 - write(out, StrUtil.format(CONTENT_TYPE_FILE_TEMPLATE, - HttpUtil.getMimeType(fileName, ContentType.OCTET_STREAM.getValue()))); - } - - // 内容 - write(out, "\r\n"); - resource.writeTo(out); - } - - /** - * 上传表单结束 - * - * @param out 输出流 - * @throws IORuntimeException IO异常 - */ - private void formEnd(OutputStream out) throws IORuntimeException { - write(out, BOUNDARY_END); - } - - /** - * 写出对象 - * - * @param out 输出流 - * @param objs 写出的对象(转换为字符串) - */ - private void write(OutputStream out, Object... objs) { - IoUtil.write(out, this.charset, false, objs); - } } diff --git a/hutool-http/src/test/java/cn/hutool/http/body/MultipartBodyTest.java b/hutool-http/src/test/java/cn/hutool/http/body/MultipartBodyTest.java index edca37138..471b6813c 100644 --- a/hutool-http/src/test/java/cn/hutool/http/body/MultipartBodyTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/body/MultipartBodyTest.java @@ -23,5 +23,6 @@ public class MultipartBodyTest { final MultipartBody body = MultipartBody.create(form, CharsetUtil.CHARSET_UTF_8); Assert.assertNotNull(body.toString()); +// Console.log(body); } } From 07a11f2baad0d81eec61697edd4f90c4d4421479 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 11 Nov 2021 20:49:26 +0800 Subject: [PATCH 22/43] fix method --- CHANGELOG.md | 1 + .../src/main/java/cn/hutool/cron/TaskTable.java | 17 ++++++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3f0448b1..024c64c0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * 【core 】 增加AsyncUtil(pr#457@Gitee) * 【http 】 增加HttpResource(issue#1943@Github) * 【http 】 增加BytesBody、FormUrlEncodedBody +* 【cron 】 TaskTable.remove增加返回值 ### 🐞Bug修复 * 【core 】 修复FileResource构造fileName参数无效问题(issue#1942@Github) diff --git a/hutool-cron/src/main/java/cn/hutool/cron/TaskTable.java b/hutool-cron/src/main/java/cn/hutool/cron/TaskTable.java index 9932ee328..9e1910822 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/TaskTable.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/TaskTable.java @@ -128,21 +128,24 @@ public class TaskTable implements Serializable { * 移除Task * * @param id Task的ID + * @return 是否成功移除,{@code false}表示未找到对应ID的任务 */ - public void remove(String id) { + public boolean remove(String id) { final Lock writeLock = lock.writeLock(); writeLock.lock(); try { final int index = ids.indexOf(id); - if (index > -1) { - tasks.remove(index); - patterns.remove(index); - ids.remove(index); - size--; + if (index < 0) { + return false; } + tasks.remove(index); + patterns.remove(index); + ids.remove(index); + size--; } finally { writeLock.unlock(); } + return true; } /** @@ -282,4 +285,4 @@ public class TaskTable implements Serializable { } } } -} \ No newline at end of file +} From bb1e4ba76a1173ab16d065bcff5bd3307614f9b8 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 11 Nov 2021 21:29:45 +0800 Subject: [PATCH 23/43] add methods --- CHANGELOG.md | 2 +- .../cn/hutool/core/lang/ConsoleTable.java | 14 ++++++------- .../main/java/cn/hutool/cron/CronUtil.java | 5 +++-- .../main/java/cn/hutool/cron/Scheduler.java | 13 +++++++++++- .../main/java/cn/hutool/cron/TaskTable.java | 11 ++++++++++ .../java/cn/hutool/cron/TaskTableTest.java | 21 +++++++++++++++++++ 6 files changed, 55 insertions(+), 11 deletions(-) create mode 100644 hutool-cron/src/test/java/cn/hutool/cron/TaskTableTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 024c64c0f..3e73db1e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ * 【core 】 增加AsyncUtil(pr#457@Gitee) * 【http 】 增加HttpResource(issue#1943@Github) * 【http 】 增加BytesBody、FormUrlEncodedBody -* 【cron 】 TaskTable.remove增加返回值 +* 【cron 】 TaskTable.remove增加返回值(issue#I4HX3B@Gitee) ### 🐞Bug修复 * 【core 】 修复FileResource构造fileName参数无效问题(issue#1942@Github) diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/ConsoleTable.java b/hutool-core/src/main/java/cn/hutool/core/lang/ConsoleTable.java index 23066a045..806f5cee2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/ConsoleTable.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/ConsoleTable.java @@ -25,11 +25,11 @@ public class ConsoleTable { /** * 表格头信息 */ - private final List> HEADER_LIST = new ArrayList<>(); + private final List> headerList = new ArrayList<>(); /** * 表格体信息 */ - private final List> BODY_LIST = new ArrayList<>(); + private final List> bodyList = new ArrayList<>(); /** * 每列最大字符个数 */ @@ -57,7 +57,7 @@ public class ConsoleTable { } List l = new ArrayList<>(); fillColumns(l, titles); - HEADER_LIST.add(l); + headerList.add(l); return this; } @@ -69,7 +69,7 @@ public class ConsoleTable { */ public ConsoleTable addBody(String... values) { List l = new ArrayList<>(); - BODY_LIST.add(l); + bodyList.add(l); fillColumns(l, values); return this; } @@ -101,9 +101,9 @@ public class ConsoleTable { public String toString() { StringBuilder sb = new StringBuilder(); fillBorder(sb); - fillRow(sb, HEADER_LIST); + fillRow(sb, headerList); fillBorder(sb); - fillRow(sb, BODY_LIST); + fillRow(sb, bodyList); fillBorder(sb); return sb.toString(); } @@ -158,4 +158,4 @@ public class ConsoleTable { Console.print(toString()); } -} \ No newline at end of file +} diff --git a/hutool-cron/src/main/java/cn/hutool/cron/CronUtil.java b/hutool-cron/src/main/java/cn/hutool/cron/CronUtil.java index f6d7eb1d9..dc0ef9618 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/CronUtil.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/CronUtil.java @@ -110,9 +110,10 @@ public class CronUtil { * 移除任务 * * @param schedulerId 任务ID + * @return 是否移除成功,{@code false}表示未找到对应ID的任务 */ - public static void remove(String schedulerId) { - scheduler.deschedule(schedulerId); + public static boolean remove(String schedulerId) { + return scheduler.descheduleWithStatus(schedulerId); } /** diff --git a/hutool-cron/src/main/java/cn/hutool/cron/Scheduler.java b/hutool-cron/src/main/java/cn/hutool/cron/Scheduler.java index 8b12f3d02..e2111b6a1 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/Scheduler.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/Scheduler.java @@ -289,10 +289,21 @@ public class Scheduler implements Serializable { * @return this */ public Scheduler deschedule(String id) { - this.taskTable.remove(id); + descheduleWithStatus(id); return this; } + /** + * 移除Task,并返回是否移除成功 + * + * @param id Task的ID + * @return 是否移除成功,{@code false}表示未找到对应ID的任务 + * @since 5.7.17 + */ + public boolean descheduleWithStatus(String id) { + return this.taskTable.remove(id); + } + /** * 更新Task执行的时间规则 * diff --git a/hutool-cron/src/main/java/cn/hutool/cron/TaskTable.java b/hutool-cron/src/main/java/cn/hutool/cron/TaskTable.java index 9e1910822..c68015408 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/TaskTable.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/TaskTable.java @@ -1,5 +1,6 @@ package cn.hutool.cron; +import cn.hutool.core.util.StrUtil; import cn.hutool.cron.pattern.CronPattern; import cn.hutool.cron.task.CronTask; import cn.hutool.cron.task.Task; @@ -271,6 +272,16 @@ public class TaskTable implements Serializable { } } + @Override + public String toString() { + final StringBuilder builder = StrUtil.builder(); + for (int i = 0; i < size; i++) { + builder.append(StrUtil.format("[{}] [{}] [{}]\n", + ids.get(i), patterns.get(i), tasks.get(i))); + } + return builder.toString(); + } + /** * 如果时间匹配则执行相应的Task,无锁 * diff --git a/hutool-cron/src/test/java/cn/hutool/cron/TaskTableTest.java b/hutool-cron/src/test/java/cn/hutool/cron/TaskTableTest.java new file mode 100644 index 000000000..318a25e1f --- /dev/null +++ b/hutool-cron/src/test/java/cn/hutool/cron/TaskTableTest.java @@ -0,0 +1,21 @@ +package cn.hutool.cron; + +import cn.hutool.core.lang.Console; +import cn.hutool.core.util.IdUtil; +import cn.hutool.cron.pattern.CronPattern; +import org.junit.Ignore; +import org.junit.Test; + +public class TaskTableTest { + + @Test + @Ignore + public void toStringTest(){ + final TaskTable taskTable = new TaskTable(); + taskTable.add(IdUtil.fastUUID(), new CronPattern("*/10 * * * * *"), ()-> Console.log("Task 1")); + taskTable.add(IdUtil.fastUUID(), new CronPattern("*/20 * * * * *"), ()-> Console.log("Task 2")); + taskTable.add(IdUtil.fastUUID(), new CronPattern("*/30 * * * * *"), ()-> Console.log("Task 3")); + + Console.log(taskTable); + } +} From ab1d8e84e423e95e5311fb93b9b1dbfd1df6bc13 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 11 Nov 2021 22:57:07 +0800 Subject: [PATCH 24/43] add method for Tree --- CHANGELOG.md | 1 + .../java/cn/hutool/core/lang/tree/Tree.java | 96 ++++++++++++++++++- .../cn/hutool/core/lang/tree/TreeTest.java | 44 +++++++++ .../hutool/cron/timingwheel/TimingWheel.java | 1 - 4 files changed, 137 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e73db1e8..73749004c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * 【http 】 增加HttpResource(issue#1943@Github) * 【http 】 增加BytesBody、FormUrlEncodedBody * 【cron 】 TaskTable.remove增加返回值(issue#I4HX3B@Gitee) +* 【core 】 Tree增加filter、filterNew、cloneTree、hasChild方法(issue#I4HFC6@Gitee) ### 🐞Bug修复 * 【core 】 修复FileResource构造fileName参数无效问题(issue#1942@Github) diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/tree/Tree.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/Tree.java index 85801c438..76272e9bc 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/tree/Tree.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/tree/Tree.java @@ -2,6 +2,7 @@ package cn.hutool.core.lang.tree; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; +import cn.hutool.core.lang.Filter; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.ObjectUtil; @@ -21,7 +22,7 @@ import java.util.function.Consumer; * @author liangbaikai * @since 5.2.1 */ - public class Tree extends LinkedHashMap implements Node { +public class Tree extends LinkedHashMap implements Node { private static final long serialVersionUID = 1L; private final TreeNodeConfig treeNodeConfig; @@ -175,6 +176,16 @@ import java.util.function.Consumer; return (List>) this.get(treeNodeConfig.getChildrenKey()); } + /** + * 是否有子节点,无子节点则此为叶子节点 + * + * @return 是否有子节点 + * @since 5.7.17 + */ + public boolean hasChild() { + return CollUtil.isNotEmpty(getChildren()); + } + /** * 递归树并处理子树下的节点: * @@ -184,18 +195,67 @@ import java.util.function.Consumer; public void walk(Consumer> consumer) { consumer.accept(this); final List> children = getChildren(); - if(CollUtil.isNotEmpty(children)){ - children.forEach((tree)-> tree.walk(consumer)); + if (CollUtil.isNotEmpty(children)) { + children.forEach((tree) -> tree.walk(consumer)); } } + /** + * 递归过滤并生成新的树
    + * 通过{@link Filter}指定的过滤规则,本节点或子节点满足过滤条件,则保留当前节点,否则抛弃节点及其子节点 + * + * @param filter 节点过滤规则函数,只需处理本级节点本身即可 + * @return 过滤后的节点,{@code null} 表示不满足过滤要求,丢弃之 + * @see #filter(Filter) + * @since 5.7.17 + */ + public Tree filterNew(Filter> filter) { + return cloneTree().filter(filter); + } + + /** + * 递归过滤当前树,注意此方法会修改当前树
    + * 通过{@link Filter}指定的过滤规则,本节点或子节点满足过滤条件,则保留当前节点,否则抛弃节点及其子节点 + * + * @param filter 节点过滤规则函数,只需处理本级节点本身即可 + * @return 过滤后的节点,{@code null} 表示不满足过滤要求,丢弃之 + * @see #filterNew(Filter) + * @since 5.7.17 + */ + public Tree filter(Filter> filter) { + final List> children = getChildren(); + if (CollUtil.isNotEmpty(children)) { + // 递归过滤子节点 + final List> filteredChildren = new ArrayList<>(children.size()); + Tree filteredChild; + for (Tree child : children) { + filteredChild = child.filter(filter); + if (null != filteredChild) { + filteredChildren.add(filteredChild); + } + } + if(CollUtil.isNotEmpty(filteredChildren)){ + // 子节点有符合过滤条件的节点,则本节点保留 + return this.setChildren(filteredChildren); + } else { + this.setChildren(null); + } + } + + // 子节点都不符合过滤条件,检查本节点 + return filter.accept(this) ? this : null; + } + /** * 设置子节点,设置后会覆盖所有原有子节点 * - * @param children 子节点列表 + * @param children 子节点列表,如果为{@code null}表示移除子节点 * @return this */ public Tree setChildren(List> children) { + if(null == children){ + this.remove(treeNodeConfig.getChildrenKey()); + } this.put(treeNodeConfig.getChildrenKey(), children); return this; } @@ -241,6 +301,34 @@ import java.util.function.Consumer; return stringWriter.toString(); } + /** + * 递归克隆当前节点(即克隆整个树,保留字段值)
    + * 注意,此方法只会克隆节点,节点属性如果是引用类型,不会克隆 + * + * @return 新的节点 + * @since 5.7.17 + */ + public Tree cloneTree() { + final Tree result = ObjectUtil.clone(this); + result.setChildren(cloneChildren()); + return result; + } + + /** + * 递归复制子节点 + * + * @return 新的子节点列表 + */ + private List> cloneChildren() { + final List> children = getChildren(); + if (null == children) { + return null; + } + final List> newChildren = new ArrayList<>(children.size()); + children.forEach((t) -> newChildren.add(t.cloneTree())); + return newChildren; + } + /** * 打印 * diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/tree/TreeTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/tree/TreeTest.java index 3007991cf..c695a707c 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/tree/TreeTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/tree/TreeTest.java @@ -76,4 +76,48 @@ public class TreeTest { Assert .assertEquals(7, ids.size()); } + + @Test + public void cloneTreeTest(){ + final Tree tree = TreeUtil.buildSingle(nodeList, "0"); + final Tree cloneTree = tree.cloneTree(); + + List ids = new ArrayList<>(); + cloneTree.walk((tr)-> ids.add(tr.getId())); + + Assert .assertEquals(7, ids.size()); + } + + @Test + public void filterTest(){ + // 经过过滤,丢掉"用户添加"节点 + final Tree tree = TreeUtil.buildSingle(nodeList, "0"); + tree.filter((t)->{ + final CharSequence name = t.getName(); + return null != name && name.toString().contains("管理"); + }); + + List ids = new ArrayList<>(); + tree.walk((tr)-> ids.add(tr.getId())); + Assert .assertEquals(6, ids.size()); + } + + @Test + public void filterNewTest(){ + final Tree tree = TreeUtil.buildSingle(nodeList, "0"); + + // 经过过滤,生成新的树 + Tree newTree = tree.filterNew((t)->{ + final CharSequence name = t.getName(); + return null != name && name.toString().contains("管理"); + }); + + List ids = new ArrayList<>(); + newTree.walk((tr)-> ids.add(tr.getId())); + Assert .assertEquals(6, ids.size()); + + List ids2 = new ArrayList<>(); + tree.walk((tr)-> ids2.add(tr.getId())); + Assert .assertEquals(7, ids2.size()); + } } diff --git a/hutool-cron/src/main/java/cn/hutool/cron/timingwheel/TimingWheel.java b/hutool-cron/src/main/java/cn/hutool/cron/timingwheel/TimingWheel.java index cfcf6591a..a67ff667d 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/timingwheel/TimingWheel.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/timingwheel/TimingWheel.java @@ -68,7 +68,6 @@ public class TimingWheel { * @param consumer 任务处理器 */ public TimingWheel(long tickMs, int wheelSize, long currentTime, Consumer consumer) { - this.currentTime = currentTime; this.tickMs = tickMs; this.wheelSize = wheelSize; this.interval = tickMs * wheelSize; From f4ca74f3763cb002d2e83c317f9edf4988c68da3 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 11 Nov 2021 23:24:30 +0800 Subject: [PATCH 25/43] add ColumnSheetReader --- CHANGELOG.md | 1 + .../java/cn/hutool/poi/excel/ExcelReader.java | 52 +++++++++++++++---- .../poi/excel/reader/ColumnSheetReader.java | 48 +++++++++++++++++ .../cn/hutool/poi/excel/ExcelReadTest.java | 11 ++++ 4 files changed, 101 insertions(+), 11 deletions(-) create mode 100644 hutool-poi/src/main/java/cn/hutool/poi/excel/reader/ColumnSheetReader.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 73749004c..66deb69cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * 【http 】 增加BytesBody、FormUrlEncodedBody * 【cron 】 TaskTable.remove增加返回值(issue#I4HX3B@Gitee) * 【core 】 Tree增加filter、filterNew、cloneTree、hasChild方法(issue#I4HFC6@Gitee) +* 【poi 】 增加ColumnSheetReader及ExcelReader.readColumn,支持读取某一列 ### 🐞Bug修复 * 【core 】 修复FileResource构造fileName参数无效问题(issue#1942@Github) diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java index 301d39309..2ff3d55e6 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java @@ -6,6 +6,7 @@ import cn.hutool.poi.excel.cell.CellEditor; import cn.hutool.poi.excel.cell.CellHandler; import cn.hutool.poi.excel.cell.CellUtil; import cn.hutool.poi.excel.reader.BeanSheetReader; +import cn.hutool.poi.excel.reader.ColumnSheetReader; import cn.hutool.poi.excel.reader.ListSheetReader; import cn.hutool.poi.excel.reader.MapSheetReader; import cn.hutool.poi.excel.reader.SheetReader; @@ -78,8 +79,8 @@ public class ExcelReader extends ExcelBase { /** * 构造 * - * @param bookStream Excel文件的流 - * @param sheetIndex sheet序号,0表示第一个sheet + * @param bookStream Excel文件的流 + * @param sheetIndex sheet序号,0表示第一个sheet */ public ExcelReader(InputStream bookStream, int sheetIndex) { this(WorkbookUtil.createBook(bookStream), sheetIndex); @@ -88,8 +89,8 @@ public class ExcelReader extends ExcelBase { /** * 构造 * - * @param bookStream Excel文件的流 - * @param sheetName sheet名,第一个默认是sheet1 + * @param bookStream Excel文件的流 + * @param sheetName sheet名,第一个默认是sheet1 */ public ExcelReader(InputStream bookStream, String sheetName) { this(WorkbookUtil.createBook(bookStream), sheetName); @@ -237,8 +238,8 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet * - * @param startRowIndex 起始行(包含,从0开始计数) - * @param endRowIndex 结束行(包含,从0开始计数) + * @param startRowIndex 起始行(包含,从0开始计数) + * @param endRowIndex 结束行(包含,从0开始计数) * @param aliasFirstLine 是否首行作为标题行转换别名 * @return 行的集合,一行使用List表示 * @since 5.4.4 @@ -251,11 +252,40 @@ public class ExcelReader extends ExcelBase { return read(reader); } + /** + * 读取工作簿中指定的Sheet中指定列 + * + * @param columnIndex 列号,从0开始计数 + * @param startRowIndex 起始行(包含,从0开始计数) + * @return 列的集合 + * @since 5.7.17 + */ + public List readColumn(int columnIndex, int startRowIndex) { + return readColumn(columnIndex, startRowIndex, Integer.MAX_VALUE); + } + + /** + * 读取工作簿中指定的Sheet中指定列 + * + * @param columnIndex 列号,从0开始计数 + * @param startRowIndex 起始行(包含,从0开始计数) + * @param endRowIndex 结束行(包含,从0开始计数) + * @return 列的集合 + * @since 5.7.17 + */ + public List readColumn(int columnIndex, int startRowIndex, int endRowIndex) { + final ColumnSheetReader reader = new ColumnSheetReader(columnIndex, startRowIndex, endRowIndex); + reader.setCellEditor(this.cellEditor); + reader.setIgnoreEmptyRow(this.ignoreEmptyRow); + reader.setHeaderAlias(headerAlias); + return read(reader); + } + /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口
    * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 * - * @param cellHandler 单元格处理器,用于处理读到的单元格及其数据 + * @param cellHandler 单元格处理器,用于处理读到的单元格及其数据 * @since 5.3.8 */ public void read(CellHandler cellHandler) { @@ -268,7 +298,7 @@ public class ExcelReader extends ExcelBase { * * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 结束行(包含,从0开始计数) - * @param cellHandler 单元格处理器,用于处理读到的单元格及其数据 + * @param cellHandler 单元格处理器,用于处理读到的单元格及其数据 * @since 5.3.8 */ public void read(int startRowIndex, int endRowIndex, CellHandler cellHandler) { @@ -281,7 +311,7 @@ public class ExcelReader extends ExcelBase { short columnSize; for (int y = startRowIndex; y <= endRowIndex; y++) { row = this.sheet.getRow(y); - if(null != row){ + if (null != row) { columnSize = row.getLastCellNum(); Cell cell; for (short x = 0; x < columnSize; x++) { @@ -365,12 +395,12 @@ public class ExcelReader extends ExcelBase { /** * 读取数据为指定类型 * - * @param 读取数据类型 + * @param 读取数据类型 * @param sheetReader {@link SheetReader}实现 * @return 数据读取结果 * @since 5.4.4 */ - public T read(SheetReader sheetReader){ + public T read(SheetReader sheetReader) { checkNotClosed(); return Assert.notNull(sheetReader).read(this.sheet); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/reader/ColumnSheetReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/reader/ColumnSheetReader.java new file mode 100644 index 000000000..ef1572012 --- /dev/null +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/reader/ColumnSheetReader.java @@ -0,0 +1,48 @@ +package cn.hutool.poi.excel.reader; + +import cn.hutool.poi.excel.cell.CellUtil; +import org.apache.poi.ss.usermodel.Sheet; + +import java.util.ArrayList; +import java.util.List; + +/** + * 读取单独一列 + * + * @author looly + * @since 5.7.17 + */ +public class ColumnSheetReader extends AbstractSheetReader> { + + private final int columnIndex; + + /** + * 构造 + * + * @param columnIndex 列号,从0开始计数 + * @param startRowIndex 起始行(包含,从0开始计数) + * @param endRowIndex 结束行(包含,从0开始计数) + */ + public ColumnSheetReader(int columnIndex, int startRowIndex, int endRowIndex) { + super(startRowIndex, endRowIndex); + this.columnIndex = columnIndex; + } + + @Override + public List read(Sheet sheet) { + final List resultList = new ArrayList<>(); + + int startRowIndex = Math.max(this.startRowIndex, sheet.getFirstRowNum());// 读取起始行(包含) + int endRowIndex = Math.min(this.endRowIndex, sheet.getLastRowNum());// 读取结束行(包含) + + Object value; + for (int i = startRowIndex; i <= endRowIndex; i++) { + value = CellUtil.getCellValue(CellUtil.getCell(sheet.getRow(i), columnIndex), cellEditor); + if(null != value || false == ignoreEmptyRow){ + resultList.add(value); + } + } + + return resultList; + } +} diff --git a/hutool-poi/src/test/java/cn/hutool/poi/excel/ExcelReadTest.java b/hutool-poi/src/test/java/cn/hutool/poi/excel/ExcelReadTest.java index ebe04bf0f..72ac31fab 100644 --- a/hutool-poi/src/test/java/cn/hutool/poi/excel/ExcelReadTest.java +++ b/hutool-poi/src/test/java/cn/hutool/poi/excel/ExcelReadTest.java @@ -233,4 +233,15 @@ public class ExcelReadTest { final ExcelReader reader = ExcelUtil.getReader("d:/test/1.-.xls"); reader.read((CellHandler) Console::log); } + + @Test + public void readColumnTest(){ + ExcelReader reader = ExcelUtil.getReader(ResourceUtil.getStream("aaa.xlsx")); + final List objects = reader.readColumn(0, 1); + + Assert.assertEquals(3, objects.size()); + Assert.assertEquals("张三", objects.get(0)); + Assert.assertEquals("李四", objects.get(1)); + Assert.assertEquals("", objects.get(2)); + } } From 01dcaee362e316576aa4c69126ae04a49b933aef Mon Sep 17 00:00:00 2001 From: looly Date: Fri, 12 Nov 2021 15:35:04 +0800 Subject: [PATCH 26/43] update dependency --- hutool-db/pom.xml | 2 +- hutool-extra/pom.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 0494c0d8a..835eede43 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -81,7 +81,7 @@ com.github.chris2018998 beecp - 3.2.7 + 3.2.9 slf4j-api diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index f59fd0e9a..9f32ccf43 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -19,7 +19,7 @@ 2.3 - 3.8.0.RELEASE + 3.8.1.RELEASE 1.4.1 2.3.31 4.9.16 @@ -384,7 +384,7 @@ ch.qos.logback logback-classic - 1.2.5 + 1.2.6 test From b1f35846493d0ae3918bdf63c515cb654306b72d Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 12 Nov 2021 21:51:14 +0800 Subject: [PATCH 27/43] fix comment --- hutool-core/src/main/java/cn/hutool/core/thread/AsyncUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/AsyncUtil.java b/hutool-core/src/main/java/cn/hutool/core/thread/AsyncUtil.java index 7a0d9fdf2..e7b7dc6d8 100644 --- a/hutool-core/src/main/java/cn/hutool/core/thread/AsyncUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/thread/AsyncUtil.java @@ -8,7 +8,7 @@ import java.util.concurrent.ExecutionException; * {@link CompletableFuture}异步工具类
    * {@link CompletableFuture} 是 Future 的改进,可以通过传入回调对象,在任务完成后调用之 * - * @author + * @author achao1441470436@gmail.com * @since 5.7.17 */ public class AsyncUtil { From 10acb1924bf2ef8002bccd6df8899c7158f7cfc2 Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 14 Nov 2021 21:47:07 +0800 Subject: [PATCH 28/43] fix code --- CHANGELOG.md | 3 ++- .../src/main/java/cn/hutool/core/util/IdcardUtil.java | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66deb69cc..2ab547f9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -# 5.7.17 (2021-11-11) +# 5.7.17 (2021-11-14) ### 🐣新特性 * 【core 】 增加AsyncUtil(pr#457@Gitee) @@ -12,6 +12,7 @@ * 【cron 】 TaskTable.remove增加返回值(issue#I4HX3B@Gitee) * 【core 】 Tree增加filter、filterNew、cloneTree、hasChild方法(issue#I4HFC6@Gitee) * 【poi 】 增加ColumnSheetReader及ExcelReader.readColumn,支持读取某一列 +* 【core 】 IdCardUtil.isValidCard不再自动trim(issue#I4I04O@Gitee) ### 🐞Bug修复 * 【core 】 修复FileResource构造fileName参数无效问题(issue#1942@Github) diff --git a/hutool-core/src/main/java/cn/hutool/core/util/IdcardUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/IdcardUtil.java index c0f871c3a..e7adc4a38 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/IdcardUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/IdcardUtil.java @@ -147,7 +147,8 @@ public class IdcardUtil { } /** - * 是否有效身份证号,忽略X的大小写 + * 是否有效身份证号,忽略X的大小写
    + * 如果身份证号码中含有空格始终返回{@code false} * * @param idCard 身份证号,支持18位、15位和港澳台的10位 * @return 是否有效 @@ -157,7 +158,7 @@ public class IdcardUtil { return false; } - idCard = idCard.trim(); + //idCard = idCard.trim(); int length = idCard.length(); switch (length) { case 18:// 18位身份证 From b48a80b24d32db08f71615ac87aa2874f31bab09 Mon Sep 17 00:00:00 2001 From: looly Date: Mon, 15 Nov 2021 10:50:18 +0800 Subject: [PATCH 29/43] fix code --- .../java/cn/hutool/core/util/ReflectUtil.java | 31 +++++-------------- 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java index 7a98696f4..affdcac6e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java @@ -77,20 +77,14 @@ public class ReflectUtil { * 获得一个类中所有构造列表 * * @param 构造的对象类型 - * @param beanClass 类 + * @param beanClass 类,非{@code null} * @return 字段列表 * @throws SecurityException 安全检查异常 */ @SuppressWarnings("unchecked") public static Constructor[] getConstructors(Class beanClass) throws SecurityException { Assert.notNull(beanClass); - Constructor[] constructors = CONSTRUCTORS_CACHE.get(beanClass); - if (null != constructors) { - return (Constructor[]) constructors; - } - - constructors = getConstructorsDirectly(beanClass); - return (Constructor[]) CONSTRUCTORS_CACHE.put(beanClass, constructors); + return (Constructor[]) CONSTRUCTORS_CACHE.get(beanClass, ()->getConstructorsDirectly(beanClass)); } /** @@ -101,7 +95,6 @@ public class ReflectUtil { * @throws SecurityException 安全检查异常 */ public static Constructor[] getConstructorsDirectly(Class beanClass) throws SecurityException { - Assert.notNull(beanClass); return beanClass.getDeclaredConstructors(); } @@ -179,13 +172,8 @@ public class ReflectUtil { * @throws SecurityException 安全检查异常 */ public static Field[] getFields(Class beanClass) throws SecurityException { - Field[] allFields = FIELDS_CACHE.get(beanClass); - if (null != allFields) { - return allFields; - } - - allFields = getFieldsDirectly(beanClass, true); - return FIELDS_CACHE.put(beanClass, allFields); + Assert.notNull(beanClass); + return FIELDS_CACHE.get(beanClass, ()->getFieldsDirectly(beanClass, true)); } @@ -641,18 +629,13 @@ public class ReflectUtil { /** * 获得一个类中所有方法列表,包括其父类中的方法 * - * @param beanClass 类 + * @param beanClass 类,非{@code null} * @return 方法列表 * @throws SecurityException 安全检查异常 */ public static Method[] getMethods(Class beanClass) throws SecurityException { - Method[] allMethods = METHODS_CACHE.get(beanClass); - if (null != allMethods) { - return allMethods; - } - - allMethods = getMethodsDirectly(beanClass, true); - return METHODS_CACHE.put(beanClass, allMethods); + Assert.notNull(beanClass); + return METHODS_CACHE.get(beanClass, ()-> getMethodsDirectly(beanClass, true)); } /** From 15753a5a31a8a3c0df1b5107aea1b08ca4280435 Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 15 Nov 2021 20:55:06 +0800 Subject: [PATCH 30/43] fix code --- hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java | 2 +- hutool-core/src/main/java/cn/hutool/core/text/StrFormatter.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java b/hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java index 39e563ea3..2c10260a4 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java @@ -14,7 +14,7 @@ public class RFC3986 { /** * gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@" */ - public static final PercentCodec GEN_DELIMS = PercentCodec.of(":/?#[]&"); + public static final PercentCodec GEN_DELIMS = PercentCodec.of(":/?#[]@"); /** * sub-delims = "!" / "$" / "{@code &}" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrFormatter.java b/hutool-core/src/main/java/cn/hutool/core/text/StrFormatter.java index a70d5542f..b90c23ce1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/StrFormatter.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/StrFormatter.java @@ -35,7 +35,7 @@ public class StrFormatter { * 如果想输出占位符使用 \\转义即可,如果想输出占位符之前的 \ 使用双转义符 \\\\ 即可
    * 例:
    * 通常使用:format("this is {} for {}", "{}", "a", "b") =》 this is a for b
    - * 转义{}: format("this is \\{} for {}", "{}", "a", "b") =》 this is \{} for a
    + * 转义{}: format("this is \\{} for {}", "{}", "a", "b") =》 this is {} for a
    * 转义\: format("this is \\\\{} for {}", "{}", "a", "b") =》 this is \a for b
    * * @param strPattern 字符串模板 From 600f2ff371d0935a0d1e4716026833aa3619b53d Mon Sep 17 00:00:00 2001 From: VampireAchao Date: Mon, 15 Nov 2021 23:09:03 +0800 Subject: [PATCH 31/43] =?UTF-8?q?1.=E6=8B=93=E5=B1=95ofEmptyAble=E5=87=BD?= =?UTF-8?q?=E6=95=B0=EF=BC=8C=E7=AE=80=E5=8C=96=E4=BA=86=E5=88=A4=E7=A9=BA?= =?UTF-8?q?=E5=85=83=E7=B4=A0list=E7=9A=84=E6=93=8D=E4=BD=9C=202.ifPresent?= =?UTF-8?q?OrElse=E7=8E=B0=E5=9C=A8=E6=94=AF=E6=8C=81=E9=93=BE=E5=BC=8F?= =?UTF-8?q?=E8=B0=83=E7=94=A8=203.=E6=8B=93=E5=B1=95mapOrElse=E5=87=BD?= =?UTF-8?q?=E6=95=B0,=E5=8F=AF=E7=94=A8=E4=BA=8E=E4=B8=80=E4=BA=9B?= =?UTF-8?q?=E8=BF=9E=E7=BB=AD=E7=9A=84=E5=8F=82=E6=95=B0=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=204.=E6=8B=93=E5=B1=95exec=E5=87=BD=E6=95=B0=EF=BC=8C=E8=83=BD?= =?UTF-8?q?=E5=AF=B9NPE=E5=92=8C=E6=95=B0=E7=BB=84=E8=B6=8A=E7=95=8C?= =?UTF-8?q?=E5=BC=82=E5=B8=B8=E8=BF=9B=E8=A1=8C=E5=8F=8B=E5=A5=BD=E8=BF=94?= =?UTF-8?q?=E5=9B=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/cn/hutool/core/lang/Opt.java | 59 ++++++++++++++++++- .../java/cn/hutool/core/lang/OptTest.java | 48 ++++++++++++++- 2 files changed, 104 insertions(+), 3 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Opt.java b/hutool-core/src/main/java/cn/hutool/core/lang/Opt.java index e4bfe9e80..9d12052de 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/Opt.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/Opt.java @@ -24,9 +24,11 @@ */ package cn.hutool.core.lang; +import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.lang.func.VoidFunc0; import cn.hutool.core.util.StrUtil; +import java.util.List; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Optional; @@ -97,6 +99,17 @@ public class Opt { return StrUtil.isBlankIfStr(value) ? empty() : new Opt<>(value); } + /** + * 返回一个包裹里{@code List}集合可能为空的{@code Opt},额外判断了集合内元素为空的情况 + * + * @param value 传入需要包裹的元素 + * @param 包裹里元素的类型 + * @return 一个包裹里元素可能为空的 {@code Opt} + */ + public static Opt> ofEmptyAble(List value) { + return CollectionUtil.isEmpty(value) ? empty() : new Opt<>(value); + } + /** * 包裹里实际的元素 */ @@ -176,11 +189,37 @@ public class Opt { * @param emptyAction 包裹里的值不存在时的操作 * @throws NullPointerException 如果包裹里的值存在时,执行的操作为 {@code null}, 或者包裹里的值不存在时的操作为 {@code null},则抛出{@code NPE} */ - public void ifPresentOrElse(Consumer action, VoidFunc0 emptyAction) { - if (value != null) { + public Opt ifPresentOrElse(Consumer action, VoidFunc0 emptyAction) { + if (isPresent()) { action.accept(value); + return ofNullable(value); } else { emptyAction.callWithRuntimeException(); + return empty(); + } + } + + + /** + * 如果包裹里的值存在,就执行传入的值存在时的操作({@link Function#apply(Object)})支持链式调用、转换为其他类型 + * 否则执行传入的值不存在时的操作({@link VoidFunc0}中的{@link VoidFunc0#call()}) + * + *

    + * 如果值存在就转换为大写,否则用{@code Console.error}打印另一句字符串 + *

    {@code
    +	 * String hutool = Opt.ofBlankAble("hutool").mapOrElse(String::toUpperCase, () -> Console.log("yes")).mapOrElse(String::intern, () -> Console.log("Value is not present~")).get();
    +	 * }
    + * + * @param mapper 包裹里的值存在时的操作 + * @param emptyAction 包裹里的值不存在时的操作 + * @throws NullPointerException 如果包裹里的值存在时,执行的操作为 {@code null}, 或者包裹里的值不存在时的操作为 {@code null},则抛出{@code NPE} + */ + public Opt mapOrElse(Function mapper, VoidFunc0 emptyAction) { + if (isPresent()) { + return ofNullable(mapper.apply(value)); + } else { + emptyAction.callWithRuntimeException(); + return empty(); } } @@ -427,6 +466,22 @@ public class Opt { return Optional.ofNullable(this.value); } + + /** + * 执行一系列操作,如果途中发生 {@code NPE} 和 {@code IndexOutOfBoundsException},返回一个空的{@code Opt} + * + * @param supplier 操作 + * @param 类型 + * @return 操作执行后的值 + */ + public static Opt exec(Supplier supplier) { + try { + return Opt.ofNullable(supplier.get()); + } catch (NullPointerException | IndexOutOfBoundsException e) { + return empty(); + } + } + /** * 判断传入参数是否与 {@code Opt}相等 * 在以下情况下返回true diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/OptTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/OptTest.java index 0da48a24a..5409afbb2 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/OptTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/OptTest.java @@ -1,5 +1,6 @@ package cn.hutool.core.lang; +import cn.hutool.core.collection.CollectionUtil; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -9,6 +10,7 @@ import org.junit.Ignore; import org.junit.Test; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.NoSuchElementException; import java.util.stream.Stream; @@ -46,11 +48,14 @@ public class OptTest { @Test @Ignore public void ifPresentOrElseTest() { - // 这是jdk9中的新函数,直接照搬了过来 // 存在就打印对应的值,不存在则用{@code System.err.println}打印另一句字符串 Opt.ofNullable("Hello Hutool!").ifPresentOrElse(Console::log, () -> Console.error("Ops!Something is wrong!")); Opt.empty().ifPresentOrElse(Console::log, () -> Console.error("Ops!Something is wrong!")); + + // 拓展为支持链式调用 + Opt.empty().ifPresentOrElse(Console::log, () -> Console.error("Ops!Something is wrong!")) + .ifPresentOrElse(Console::log, () -> Console.error("Ops!Something is wrong!")); } @Test @@ -142,6 +147,47 @@ public class OptTest { Assert.assertNull(user.getNickname()); } + @Test + public void ofEmptyAbleTest() { + // 以前,输入一个CollectionUtil感觉要命,类似前缀的类一大堆,代码补全形同虚设(在项目中起码要输入完CollectionU才能在第一个调出这个函数) + // 关键它还很常用,判空和判空集合真的太常用了... + List past = Opt.ofNullable(Collections.emptyList()).filter(CollectionUtil::isNotEmpty).orElseGet(() -> Collections.singletonList("hutool")); + // 现在,一个ofEmptyAble搞定 + List hutool = Opt.ofEmptyAble(Collections.emptyList()).orElseGet(() -> Collections.singletonList("hutool")); + Assert.assertEquals(past, hutool); + Assert.assertEquals(hutool, Collections.singletonList("hutool")); + } + + @Test + public void mapOrElseTest() { + // 如果值存在就转换为大写,否则打印一句字符串,支持链式调用、转换为其他类型 + String hutool = Opt.ofBlankAble("hutool").mapOrElse(String::toUpperCase, () -> Console.log("yes")).mapOrElse(String::intern, () -> Console.log("Value is not present~")).get(); + Assert.assertEquals("HUTOOL", hutool); + } + + @Test + public void execTest() { + // 有一些资深的程序员跟我说你这个lambda,双冒号语法糖看不懂... + // 为了尊重资深程序员的意见,并且提升代码可读性,封装了一下 "try catch NPE 和 数组越界"的情况 + + // 以前这种写法,简洁但可读性稍低,对资深程序员不太友好 + List last = null; + String npeSituation = Opt.ofEmptyAble(last).flattedMap(l -> l.stream().findFirst()).orElse("hutool"); + String indexOutSituation = Opt.ofEmptyAble(last).map(l -> l.get(0)).orElse("hutool"); + + // 现在代码整洁度降低,但可读性up,如果再人说看不懂这代码... + String npe = Opt.exec(() -> last.get(0)).orElse("hutool"); + String indexOut = Opt.exec(() -> { + List list = new ArrayList<>(); + // 你可以在里面写一长串调用链 list.get(0).getUser().getId() + return list.get(0); + }).orElse("hutool"); + Assert.assertEquals(npe, npeSituation); + Assert.assertEquals(indexOut, indexOutSituation); + Assert.assertEquals("hutool", npe); + Assert.assertEquals("hutool", indexOut); + } + @Data @Builder @NoArgsConstructor From 305fa44b55a2af9b7d8e206a879d0df314bca834 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 16 Nov 2021 00:09:40 +0800 Subject: [PATCH 32/43] fix textFinder --- CHANGELOG.md | 1 + .../cn/hutool/core/text/CharSequenceUtil.java | 85 +++++++------------ .../java/cn/hutool/core/text/StrSplitter.java | 3 +- .../hutool/core/text/finder/CharFinder.java | 7 +- .../core/text/finder/CharMatcherFinder.java | 7 +- .../cn/hutool/core/text/finder/Finder.java | 4 +- .../hutool/core/text/finder/LengthFinder.java | 6 +- .../core/text/finder/PatternFinder.java | 19 ++++- .../cn/hutool/core/text/finder/StrFinder.java | 52 ++++++++++-- .../hutool/core/text/finder/TextFinder.java | 35 +++++++- .../hutool/core/text/split/SplitIterTest.java | 14 +++ .../java/cn/hutool/core/util/StrUtilTest.java | 6 +- 12 files changed, 155 insertions(+), 84 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ab547f9c..0ab1185ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ * 【core 】 Tree增加filter、filterNew、cloneTree、hasChild方法(issue#I4HFC6@Gitee) * 【poi 】 增加ColumnSheetReader及ExcelReader.readColumn,支持读取某一列 * 【core 】 IdCardUtil.isValidCard不再自动trim(issue#I4I04O@Gitee) +* 【core 】 IdCardUtil.isValidCard不再自动trim(issue#I4I04O@Gitee) ### 🐞Bug修复 * 【core 】 修复FileResource构造fileName参数无效问题(issue#1942@Github) diff --git a/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java b/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java index 511fb3c6e..71392a3a7 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java @@ -7,6 +7,8 @@ import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Filter; import cn.hutool.core.lang.Matcher; import cn.hutool.core.lang.func.Func1; +import cn.hutool.core.text.finder.Finder; +import cn.hutool.core.text.finder.StrFinder; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.CharsetUtil; @@ -35,7 +37,7 @@ import java.util.function.Predicate; */ public class CharSequenceUtil { - public static final int INDEX_NOT_FOUND = -1; + public static final int INDEX_NOT_FOUND = Finder.INDEX_NOT_FOUND; /** * 字符串常量:{@code "null"}
    @@ -1101,9 +1103,13 @@ public class CharSequenceUtil { if (start < 0 || start > len) { start = 0; } - if (end > len || end < 0) { + if (end > len) { end = len; } + if (end < 0) { + end += len; + } + for (int i = start; i < end; i++) { if (str.charAt(i) == searchChar) { return i; @@ -1168,40 +1174,22 @@ public class CharSequenceUtil { /** * 指定范围内查找字符串 * - * @param str 字符串 - * @param searchStr 需要查找位置的字符串 - * @param fromIndex 起始位置 + * @param text 字符串,空则返回-1 + * @param searchStr 需要查找位置的字符串,空则返回-1 + * @param from 起始位置(包含) * @param ignoreCase 是否忽略大小写 * @return 位置 * @since 3.2.1 */ - public static int indexOf(final CharSequence str, CharSequence searchStr, int fromIndex, boolean ignoreCase) { - if (str == null || searchStr == null) { - return INDEX_NOT_FOUND; - } - if (fromIndex < 0) { - fromIndex = 0; - } - - final int endLimit = str.length() - searchStr.length() + 1; - if (fromIndex > endLimit) { - return INDEX_NOT_FOUND; - } - if (searchStr.length() == 0) { - return fromIndex; - } - - if (false == ignoreCase) { - // 不忽略大小写调用JDK方法 - return str.toString().indexOf(searchStr.toString(), fromIndex); - } - - for (int i = fromIndex; i < endLimit; i++) { - if (isSubEquals(str, i, searchStr, 0, searchStr.length(), true)) { - return i; + public static int indexOf(CharSequence text, CharSequence searchStr, int from, boolean ignoreCase) { + if (isEmpty(text) || isEmpty(searchStr)) { + if (StrUtil.equals(text, searchStr)) { + return 0; + } else { + return INDEX_NOT_FOUND; } } - return INDEX_NOT_FOUND; + return new StrFinder(searchStr, ignoreCase).setText(text).start(from); } /** @@ -1212,7 +1200,7 @@ public class CharSequenceUtil { * @return 位置 * @since 3.2.1 */ - public static int lastIndexOfIgnoreCase(final CharSequence str, final CharSequence searchStr) { + public static int lastIndexOfIgnoreCase(CharSequence str, CharSequence searchStr) { return lastIndexOfIgnoreCase(str, searchStr, str.length()); } @@ -1226,7 +1214,7 @@ public class CharSequenceUtil { * @return 位置 * @since 3.2.1 */ - public static int lastIndexOfIgnoreCase(final CharSequence str, final CharSequence searchStr, int fromIndex) { + public static int lastIndexOfIgnoreCase(CharSequence str, CharSequence searchStr, int fromIndex) { return lastIndexOf(str, searchStr, fromIndex, true); } @@ -1234,37 +1222,22 @@ public class CharSequenceUtil { * 指定范围内查找字符串
    * fromIndex 为搜索起始位置,从后往前计数 * - * @param str 字符串 + * @param text 字符串 * @param searchStr 需要查找位置的字符串 - * @param fromIndex 起始位置,从后往前计数 + * @param from 起始位置,从后往前计数 * @param ignoreCase 是否忽略大小写 * @return 位置 * @since 3.2.1 */ - public static int lastIndexOf(final CharSequence str, final CharSequence searchStr, int fromIndex, boolean ignoreCase) { - if (str == null || searchStr == null) { - return INDEX_NOT_FOUND; - } - if (fromIndex < 0) { - fromIndex = 0; - } - fromIndex = Math.min(fromIndex, str.length()); - - if (searchStr.length() == 0) { - return fromIndex; - } - - if (false == ignoreCase) { - // 不忽略大小写调用JDK方法 - return str.toString().lastIndexOf(searchStr.toString(), fromIndex); - } - - for (int i = fromIndex; i >= 0; i--) { - if (isSubEquals(str, i, searchStr, 0, searchStr.length(), true)) { - return i; + public static int lastIndexOf(CharSequence text, CharSequence searchStr, int from, boolean ignoreCase) { + if (isEmpty(text) || isEmpty(searchStr)) { + if (StrUtil.equals(text, searchStr)) { + return 0; + } else { + return INDEX_NOT_FOUND; } } - return INDEX_NOT_FOUND; + return new StrFinder(searchStr, ignoreCase, true).setText(text).start(from); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrSplitter.java b/hutool-core/src/main/java/cn/hutool/core/text/StrSplitter.java index 90f041352..eb47e7630 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/StrSplitter.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/StrSplitter.java @@ -169,10 +169,9 @@ public class StrSplitter { * @param ignoreEmpty 是否忽略空串 * @param ignoreCase 是否忽略大小写 * @return 切分后的集合 - * @since 3.2.1 */ public static List split(CharSequence text, char separator, int limit, boolean isTrim, boolean ignoreEmpty, boolean ignoreCase) { - return split(text, separator, limit, ignoreEmpty, trimFunc(isTrim)); + return split(text, separator, limit, ignoreEmpty, ignoreCase, trimFunc(isTrim)); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/text/finder/CharFinder.java b/hutool-core/src/main/java/cn/hutool/core/text/finder/CharFinder.java index 0fed9fefc..358815c1a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/finder/CharFinder.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/finder/CharFinder.java @@ -4,7 +4,8 @@ import cn.hutool.core.lang.Assert; import cn.hutool.core.util.NumberUtil; /** - * 字符查找器 + * 字符查找器
    + * 查找指定字符在字符串中的位置信息 * * @author looly * @since 5.7.14 @@ -38,8 +39,8 @@ public class CharFinder extends TextFinder { @Override public int start(int from) { Assert.notNull(this.text, "Text to find must be not null!"); - final int length = text.length(); - for (int i = from; i < length; i++) { + final int limit = getValidEndIndex(false); + for (int i = from; i < limit; i++) { if (NumberUtil.equals(c, text.charAt(i), caseInsensitive)) { return i; } diff --git a/hutool-core/src/main/java/cn/hutool/core/text/finder/CharMatcherFinder.java b/hutool-core/src/main/java/cn/hutool/core/text/finder/CharMatcherFinder.java index 301f3a257..7f3537a7a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/finder/CharMatcherFinder.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/finder/CharMatcherFinder.java @@ -4,7 +4,8 @@ import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Matcher; /** - * 字符匹配查找器 + * 字符匹配查找器
    + * 查找满足指定{@link Matcher} 匹配的字符所在位置,此类长用于查找某一类字符,如数字等 * * @since 5.7.14 * @author looly @@ -25,8 +26,8 @@ public class CharMatcherFinder extends TextFinder { @Override public int start(int from) { Assert.notNull(this.text, "Text to find must be not null!"); - final int length = text.length(); - for (int i = from; i < length; i++) { + final int limit = getValidEndIndex(false); + for (int i = from; i < limit; i++) { if(matcher.match(text.charAt(i))){ return i; } diff --git a/hutool-core/src/main/java/cn/hutool/core/text/finder/Finder.java b/hutool-core/src/main/java/cn/hutool/core/text/finder/Finder.java index 82ab597ed..f6a1aa049 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/finder/Finder.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/finder/Finder.java @@ -8,10 +8,12 @@ package cn.hutool.core.text.finder; */ public interface Finder { + int INDEX_NOT_FOUND = -1; + /** * 返回开始位置,即起始字符位置(包含),未找到返回-1 * - * @param from 查找的开始位置(包含 + * @param from 查找的开始位置(包含) * @return 起始字符位置,未找到返回-1 */ int start(int from); diff --git a/hutool-core/src/main/java/cn/hutool/core/text/finder/LengthFinder.java b/hutool-core/src/main/java/cn/hutool/core/text/finder/LengthFinder.java index ca19d4424..665d2438d 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/finder/LengthFinder.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/finder/LengthFinder.java @@ -3,7 +3,8 @@ package cn.hutool.core.text.finder; import cn.hutool.core.lang.Assert; /** - * 固定长度查找器 + * 固定长度查找器
    + * 给定一个长度,查找的位置为from + length,一般用于分段截取 * * @since 5.7.14 * @author looly @@ -25,7 +26,8 @@ public class LengthFinder extends TextFinder { public int start(int from) { Assert.notNull(this.text, "Text to find must be not null!"); final int result = from + length; - if(result < text.length()){ + final int limit = getValidEndIndex(false); + if(result < limit){ return result; } return -1; diff --git a/hutool-core/src/main/java/cn/hutool/core/text/finder/PatternFinder.java b/hutool-core/src/main/java/cn/hutool/core/text/finder/PatternFinder.java index a393b9e23..af0d3db57 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/finder/PatternFinder.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/finder/PatternFinder.java @@ -4,7 +4,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; /** - * 正则查找器 + * 正则查找器
    + * 通过传入正则表达式,查找指定字符串中匹配正则的开始和结束位置 * * @author looly * @since 5.7.14 @@ -43,14 +44,24 @@ public class PatternFinder extends TextFinder { @Override public int start(int from) { if (matcher.find(from)) { - return matcher.start(); + // 只有匹配到的字符串结尾在limit范围内,才算找到 + if(matcher.end() <= getValidEndIndex(false)){ + return matcher.start(); + } } - return -1; + return INDEX_NOT_FOUND; } @Override public int end(int start) { - return matcher.end(); + final int end = matcher.end(); + final int limit; + if(endIndex < 0){ + limit = text.length(); + }else{ + limit = Math.min(endIndex, text.length()); + } + return end < limit ? end : INDEX_NOT_FOUND; } @Override diff --git a/hutool-core/src/main/java/cn/hutool/core/text/finder/StrFinder.java b/hutool-core/src/main/java/cn/hutool/core/text/finder/StrFinder.java index 43ecb8b82..2b11b0a15 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/finder/StrFinder.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/finder/StrFinder.java @@ -1,10 +1,10 @@ package cn.hutool.core.text.finder; import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.StrUtil; +import cn.hutool.core.text.CharSequenceUtil; /** - * 字符查找器 + * 字符串查找器 * * @author looly * @since 5.7.14 @@ -12,25 +12,59 @@ import cn.hutool.core.util.StrUtil; public class StrFinder extends TextFinder { private static final long serialVersionUID = 1L; - private final CharSequence str; + private final CharSequence strToFind; private final boolean caseInsensitive; + private final boolean negative; /** * 构造 * - * @param str 被查找的字符串 + * @param strToFind 被查找的字符串 * @param caseInsensitive 是否忽略大小写 */ - public StrFinder(CharSequence str, boolean caseInsensitive) { - Assert.notEmpty(str); - this.str = str; + public StrFinder(CharSequence strToFind, boolean caseInsensitive) { + this(strToFind, caseInsensitive, false); + } + + /** + * 构造 + * + * @param strToFind 被查找的字符串 + * @param caseInsensitive 是否忽略大小写 + * @param negative 是否从后向前查找模式 + */ + public StrFinder(CharSequence strToFind, boolean caseInsensitive, boolean negative ) { + Assert.notEmpty(strToFind); + this.strToFind = strToFind; this.caseInsensitive = caseInsensitive; + this.negative = negative ; } @Override public int start(int from) { Assert.notNull(this.text, "Text to find must be not null!"); - return StrUtil.indexOf(text, str, from, caseInsensitive); + final int subLen = strToFind.length(); + + if (from < 0) { + from = 0; + } + int endLimit = getValidEndIndex(negative); + if(negative){ + for (int i = from; i > endLimit; i--) { + if (CharSequenceUtil.isSubEquals(text, i, strToFind, 0, subLen, caseInsensitive)) { + return i; + } + } + } else { + endLimit = endLimit - subLen + 1; + for (int i = from; i < endLimit; i++) { + if (CharSequenceUtil.isSubEquals(text, i, strToFind, 0, subLen, caseInsensitive)) { + return i; + } + } + } + + return INDEX_NOT_FOUND; } @Override @@ -38,6 +72,6 @@ public class StrFinder extends TextFinder { if (start < 0) { return -1; } - return start + str.length(); + return start + strToFind.length(); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/text/finder/TextFinder.java b/hutool-core/src/main/java/cn/hutool/core/text/finder/TextFinder.java index 7045aa8fd..5c0088ec9 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/finder/TextFinder.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/finder/TextFinder.java @@ -1,5 +1,7 @@ package cn.hutool.core.text.finder; +import cn.hutool.core.lang.Assert; + import java.io.Serializable; /** @@ -12,6 +14,7 @@ public abstract class TextFinder implements Finder, Serializable { private static final long serialVersionUID = 1L; protected CharSequence text; + protected int endIndex = -1; /** * 设置被查找的文本 @@ -20,7 +23,37 @@ public abstract class TextFinder implements Finder, Serializable { * @return this */ public TextFinder setText(CharSequence text) { - this.text = text; + this.text = Assert.notNull(text, "Text must be not null!"); return this; } + + /** + * 设置查找的结束位置
    + * 如果从前向后查找,结束位置最大为text.length()
    + * 如果从后向前,结束位置为-1 + * + * @param endIndex 结束位置(不包括) + * @return this + */ + public TextFinder setEndIndex(int endIndex) { + this.endIndex = endIndex; + return this; + } + + /** + * 获取有效结束位置
    + * 如果{@link #endIndex}小于0,在反向模式下是开头(-1),正向模式是结尾(text.length()) + * + * @param negative 是否从后向前查找模式 + * @return 有效结束位置 + */ + protected int getValidEndIndex(boolean negative) { + final int limit; + if (endIndex < 0) { + limit = negative ? -1 : text.length(); + } else { + limit = Math.min(endIndex, text.length()); + } + return limit; + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/split/SplitIterTest.java b/hutool-core/src/test/java/cn/hutool/core/text/split/SplitIterTest.java index 8bc3027fc..18050df58 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/split/SplitIterTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/split/SplitIterTest.java @@ -135,4 +135,18 @@ public class SplitIterTest { final List strings = splitIter.toList(false); Assert.assertEquals(1, strings.size()); } + + // 切割字符串是空字符串时报错 + @Test(expected = IllegalArgumentException.class) + public void splitByEmptyTest(){ + String text = "aa,bb,cc"; + SplitIter splitIter = new SplitIter(text, + new StrFinder("", false), + 3, + false + ); + + final List strings = splitIter.toList(false); + Assert.assertEquals(1, strings.size()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java index b0aca478c..9b9ebc7b4 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java @@ -177,7 +177,7 @@ public class StrUtilTest { Assert.assertEquals(5, StrUtil.indexOfIgnoreCase("aabaabaa", "B", 3)); Assert.assertEquals(-1, StrUtil.indexOfIgnoreCase("aabaabaa", "B", 9)); Assert.assertEquals(2, StrUtil.indexOfIgnoreCase("aabaabaa", "B", -1)); - Assert.assertEquals(2, StrUtil.indexOfIgnoreCase("aabaabaa", "", 2)); + Assert.assertEquals(-1, StrUtil.indexOfIgnoreCase("aabaabaa", "", 2)); Assert.assertEquals(-1, StrUtil.indexOfIgnoreCase("abc", "", 9)); } @@ -199,8 +199,8 @@ public class StrUtilTest { Assert.assertEquals(2, StrUtil.lastIndexOfIgnoreCase("aabaabaa", "B", 3)); Assert.assertEquals(5, StrUtil.lastIndexOfIgnoreCase("aabaabaa", "B", 9)); Assert.assertEquals(-1, StrUtil.lastIndexOfIgnoreCase("aabaabaa", "B", -1)); - Assert.assertEquals(2, StrUtil.lastIndexOfIgnoreCase("aabaabaa", "", 2)); - Assert.assertEquals(3, StrUtil.lastIndexOfIgnoreCase("abc", "", 9)); + Assert.assertEquals(-1, StrUtil.lastIndexOfIgnoreCase("aabaabaa", "", 2)); + Assert.assertEquals(-1, StrUtil.lastIndexOfIgnoreCase("abc", "", 9)); Assert.assertEquals(0, StrUtil.lastIndexOfIgnoreCase("AAAcsd", "aaa")); } From cf515598766e19eac5d0317532fe310e63a92653 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 16 Nov 2021 00:22:31 +0800 Subject: [PATCH 33/43] fix code --- .../main/java/cn/hutool/core/text/finder/TextFinder.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/text/finder/TextFinder.java b/hutool-core/src/main/java/cn/hutool/core/text/finder/TextFinder.java index 5c0088ec9..9b6f3d366 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/finder/TextFinder.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/finder/TextFinder.java @@ -48,9 +48,13 @@ public abstract class TextFinder implements Finder, Serializable { * @return 有效结束位置 */ protected int getValidEndIndex(boolean negative) { + if(negative && -1 == endIndex){ + // 反向查找模式下,-1表示0前面的位置,即字符串反向末尾的位置 + return -1; + } final int limit; if (endIndex < 0) { - limit = negative ? -1 : text.length(); + limit = endIndex + text.length(); } else { limit = Math.min(endIndex, text.length()); } From 67ea5c13ed7ae36bbfedfe922b847d68d1b6b495 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 16 Nov 2021 00:23:20 +0800 Subject: [PATCH 34/43] fix code --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ab1185ec..9deda06da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -# 5.7.17 (2021-11-14) +# 5.7.17 (2021-11-16) ### 🐣新特性 * 【core 】 增加AsyncUtil(pr#457@Gitee) @@ -14,6 +14,7 @@ * 【poi 】 增加ColumnSheetReader及ExcelReader.readColumn,支持读取某一列 * 【core 】 IdCardUtil.isValidCard不再自动trim(issue#I4I04O@Gitee) * 【core 】 IdCardUtil.isValidCard不再自动trim(issue#I4I04O@Gitee) +* 【core 】 改进TextFinder,支持限制结束位置及反向查找模式 ### 🐞Bug修复 * 【core 】 修复FileResource构造fileName参数无效问题(issue#1942@Github) From a16b0ed025910d89c889dc065b4b90e661128897 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 16 Nov 2021 00:38:10 +0800 Subject: [PATCH 35/43] fix code --- .../src/main/java/cn/hutool/core/lang/Opt.java | 13 ++++++++----- .../src/test/java/cn/hutool/core/lang/OptTest.java | 3 ++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Opt.java b/hutool-core/src/main/java/cn/hutool/core/lang/Opt.java index 9d12052de..6f95a8c7a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/Opt.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/Opt.java @@ -167,12 +167,14 @@ public class Opt { * } * * @param action 你想要执行的操作 + * @return this * @throws NullPointerException 如果包裹里的值存在,但你传入的操作为{@code null}时抛出 */ - public void ifPresent(Consumer action) { - if (value != null) { + public Opt ifPresent(Consumer action) { + if (isPresent()) { action.accept(value); } + return this; } /** @@ -187,16 +189,16 @@ public class Opt { * * @param action 包裹里的值存在时的操作 * @param emptyAction 包裹里的值不存在时的操作 + * @return this; * @throws NullPointerException 如果包裹里的值存在时,执行的操作为 {@code null}, 或者包裹里的值不存在时的操作为 {@code null},则抛出{@code NPE} */ public Opt ifPresentOrElse(Consumer action, VoidFunc0 emptyAction) { if (isPresent()) { action.accept(value); - return ofNullable(value); } else { emptyAction.callWithRuntimeException(); - return empty(); } + return this; } @@ -459,10 +461,11 @@ public class Opt { /** * 转换为 {@link Optional}对象 + * * @return {@link Optional}对象 * @since 5.7.16 */ - public Optional toOptional(){ + public Optional toOptional() { return Optional.ofNullable(this.value); } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/OptTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/OptTest.java index 5409afbb2..013e7897d 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/OptTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/OptTest.java @@ -149,7 +149,7 @@ public class OptTest { @Test public void ofEmptyAbleTest() { - // 以前,输入一个CollectionUtil感觉要命,类似前缀的类一大堆,代码补全形同虚设(在项目中起码要输入完CollectionU才能在第一个调出这个函数) + // 以前,输入一个CollectionUtil感觉要命,类似前缀的类一大堆,代码补全形同虚设(在项目中起码要输入完CollectionUtil才能在第一个调出这个函数) // 关键它还很常用,判空和判空集合真的太常用了... List past = Opt.ofNullable(Collections.emptyList()).filter(CollectionUtil::isNotEmpty).orElseGet(() -> Collections.singletonList("hutool")); // 现在,一个ofEmptyAble搞定 @@ -165,6 +165,7 @@ public class OptTest { Assert.assertEquals("HUTOOL", hutool); } + @SuppressWarnings({"MismatchedQueryAndUpdateOfCollection", "ConstantConditions"}) @Test public void execTest() { // 有一些资深的程序员跟我说你这个lambda,双冒号语法糖看不懂... From 22c69f6c4983df6f4e152df6dae7682ec72c9341 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 16 Nov 2021 00:38:46 +0800 Subject: [PATCH 36/43] fix code --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9deda06da..15e5af7e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ * 【core 】 IdCardUtil.isValidCard不再自动trim(issue#I4I04O@Gitee) * 【core 】 IdCardUtil.isValidCard不再自动trim(issue#I4I04O@Gitee) * 【core 】 改进TextFinder,支持限制结束位置及反向查找模式 +* 【core 】 Opt增加部分方法(pr#459@Gitee) +* ### 🐞Bug修复 * 【core 】 修复FileResource构造fileName参数无效问题(issue#1942@Github) From 50654f821f7fc9f70fe5525a92879f93c5fd9e67 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 16 Nov 2021 01:19:40 +0800 Subject: [PATCH 37/43] fix code --- .../cn/hutool/core/text/CharSequenceUtil.java | 30 +++++-------------- .../hutool/core/text/finder/CharFinder.java | 16 +++++++--- .../core/text/finder/CharMatcherFinder.java | 16 +++++++--- .../hutool/core/text/finder/LengthFinder.java | 16 +++++++--- .../core/text/finder/PatternFinder.java | 7 ++++- .../cn/hutool/core/text/finder/StrFinder.java | 19 ++---------- .../hutool/core/text/finder/TextFinder.java | 17 +++++++++-- .../core/text/CharSequenceUtilTest.java | 19 +++++++++++- .../core/text/finder/CharFinderTest.java | 30 +++++++++++++++++++ 9 files changed, 115 insertions(+), 55 deletions(-) create mode 100644 hutool-core/src/test/java/cn/hutool/core/text/finder/CharFinderTest.java diff --git a/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java b/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java index 71392a3a7..9a9d66a11 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java @@ -7,6 +7,7 @@ import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Filter; import cn.hutool.core.lang.Matcher; import cn.hutool.core.lang.func.Func1; +import cn.hutool.core.text.finder.CharFinder; import cn.hutool.core.text.finder.Finder; import cn.hutool.core.text.finder.StrFinder; import cn.hutool.core.util.ArrayUtil; @@ -1066,7 +1067,7 @@ public class CharSequenceUtil { * @param searchChar 被查找的字符 * @return 位置 */ - public static int indexOf(final CharSequence str, char searchChar) { + public static int indexOf(CharSequence str, char searchChar) { return indexOf(str, searchChar, 0); } @@ -1089,33 +1090,17 @@ public class CharSequenceUtil { /** * 指定范围内查找指定字符 * - * @param str 字符串 + * @param text 字符串 * @param searchChar 被查找的字符 * @param start 起始位置,如果小于0,从0开始查找 * @param end 终止位置,如果超过str.length()则默认查找到字符串末尾 * @return 位置 */ - public static int indexOf(final CharSequence str, char searchChar, int start, int end) { - if (isEmpty(str)) { + public static int indexOf(CharSequence text, char searchChar, int start, int end) { + if (isEmpty(text)) { return INDEX_NOT_FOUND; } - final int len = str.length(); - if (start < 0 || start > len) { - start = 0; - } - if (end > len) { - end = len; - } - if (end < 0) { - end += len; - } - - for (int i = start; i < end; i++) { - if (str.charAt(i) == searchChar) { - return i; - } - } - return INDEX_NOT_FOUND; + return new CharFinder(searchChar).setText(text).setEndIndex(end).start(start); } /** @@ -1237,7 +1222,8 @@ public class CharSequenceUtil { return INDEX_NOT_FOUND; } } - return new StrFinder(searchStr, ignoreCase, true).setText(text).start(from); + return new StrFinder(searchStr, ignoreCase) + .setText(text).setNegative(true).start(from); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/text/finder/CharFinder.java b/hutool-core/src/main/java/cn/hutool/core/text/finder/CharFinder.java index 358815c1a..4328581e1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/finder/CharFinder.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/finder/CharFinder.java @@ -39,10 +39,18 @@ public class CharFinder extends TextFinder { @Override public int start(int from) { Assert.notNull(this.text, "Text to find must be not null!"); - final int limit = getValidEndIndex(false); - for (int i = from; i < limit; i++) { - if (NumberUtil.equals(c, text.charAt(i), caseInsensitive)) { - return i; + final int limit = getValidEndIndex(); + if(negative){ + for (int i = from; i > limit; i--) { + if (NumberUtil.equals(c, text.charAt(i), caseInsensitive)) { + return i; + } + } + } else{ + for (int i = from; i < limit; i++) { + if (NumberUtil.equals(c, text.charAt(i), caseInsensitive)) { + return i; + } } } return -1; diff --git a/hutool-core/src/main/java/cn/hutool/core/text/finder/CharMatcherFinder.java b/hutool-core/src/main/java/cn/hutool/core/text/finder/CharMatcherFinder.java index 7f3537a7a..216d68ee7 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/finder/CharMatcherFinder.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/finder/CharMatcherFinder.java @@ -26,10 +26,18 @@ public class CharMatcherFinder extends TextFinder { @Override public int start(int from) { Assert.notNull(this.text, "Text to find must be not null!"); - final int limit = getValidEndIndex(false); - for (int i = from; i < limit; i++) { - if(matcher.match(text.charAt(i))){ - return i; + final int limit = getValidEndIndex(); + if(negative){ + for (int i = from; i > limit; i--) { + if(matcher.match(text.charAt(i))){ + return i; + } + } + } else { + for (int i = from; i < limit; i++) { + if(matcher.match(text.charAt(i))){ + return i; + } } } return -1; diff --git a/hutool-core/src/main/java/cn/hutool/core/text/finder/LengthFinder.java b/hutool-core/src/main/java/cn/hutool/core/text/finder/LengthFinder.java index 665d2438d..3face4f30 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/finder/LengthFinder.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/finder/LengthFinder.java @@ -25,10 +25,18 @@ public class LengthFinder extends TextFinder { @Override public int start(int from) { Assert.notNull(this.text, "Text to find must be not null!"); - final int result = from + length; - final int limit = getValidEndIndex(false); - if(result < limit){ - return result; + final int limit = getValidEndIndex(); + int result; + if(negative){ + result = from - length; + if(result > limit){ + return result; + } + } else { + result = from + length; + if(result < limit){ + return result; + } } return -1; } diff --git a/hutool-core/src/main/java/cn/hutool/core/text/finder/PatternFinder.java b/hutool-core/src/main/java/cn/hutool/core/text/finder/PatternFinder.java index af0d3db57..92afc408d 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/finder/PatternFinder.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/finder/PatternFinder.java @@ -41,11 +41,16 @@ public class PatternFinder extends TextFinder { return super.setText(text); } + @Override + public TextFinder setNegative(boolean negative) { + throw new UnsupportedOperationException("Negative is invalid for Pattern!"); + } + @Override public int start(int from) { if (matcher.find(from)) { // 只有匹配到的字符串结尾在limit范围内,才算找到 - if(matcher.end() <= getValidEndIndex(false)){ + if(matcher.end() <= getValidEndIndex()){ return matcher.start(); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/text/finder/StrFinder.java b/hutool-core/src/main/java/cn/hutool/core/text/finder/StrFinder.java index 2b11b0a15..edf931dde 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/finder/StrFinder.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/finder/StrFinder.java @@ -14,7 +14,6 @@ public class StrFinder extends TextFinder { private final CharSequence strToFind; private final boolean caseInsensitive; - private final boolean negative; /** * 构造 @@ -23,21 +22,9 @@ public class StrFinder extends TextFinder { * @param caseInsensitive 是否忽略大小写 */ public StrFinder(CharSequence strToFind, boolean caseInsensitive) { - this(strToFind, caseInsensitive, false); - } - - /** - * 构造 - * - * @param strToFind 被查找的字符串 - * @param caseInsensitive 是否忽略大小写 - * @param negative 是否从后向前查找模式 - */ - public StrFinder(CharSequence strToFind, boolean caseInsensitive, boolean negative ) { Assert.notEmpty(strToFind); this.strToFind = strToFind; this.caseInsensitive = caseInsensitive; - this.negative = negative ; } @Override @@ -48,15 +35,15 @@ public class StrFinder extends TextFinder { if (from < 0) { from = 0; } - int endLimit = getValidEndIndex(negative); - if(negative){ + int endLimit = getValidEndIndex(); + if (negative) { for (int i = from; i > endLimit; i--) { if (CharSequenceUtil.isSubEquals(text, i, strToFind, 0, subLen, caseInsensitive)) { return i; } } } else { - endLimit = endLimit - subLen + 1; + endLimit = endLimit - subLen + 1; for (int i = from; i < endLimit; i++) { if (CharSequenceUtil.isSubEquals(text, i, strToFind, 0, subLen, caseInsensitive)) { return i; diff --git a/hutool-core/src/main/java/cn/hutool/core/text/finder/TextFinder.java b/hutool-core/src/main/java/cn/hutool/core/text/finder/TextFinder.java index 9b6f3d366..f2a5f9b91 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/finder/TextFinder.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/finder/TextFinder.java @@ -15,6 +15,7 @@ public abstract class TextFinder implements Finder, Serializable { protected CharSequence text; protected int endIndex = -1; + protected boolean negative; /** * 设置被查找的文本 @@ -40,21 +41,31 @@ public abstract class TextFinder implements Finder, Serializable { return this; } + /** + * 设置是否反向查找,{@code true}表示从后向前查找 + * + * @param negative 结束位置(不包括) + * @return this + */ + public TextFinder setNegative(boolean negative) { + this.negative = negative; + return this; + } + /** * 获取有效结束位置
    * 如果{@link #endIndex}小于0,在反向模式下是开头(-1),正向模式是结尾(text.length()) * - * @param negative 是否从后向前查找模式 * @return 有效结束位置 */ - protected int getValidEndIndex(boolean negative) { + protected int getValidEndIndex() { if(negative && -1 == endIndex){ // 反向查找模式下,-1表示0前面的位置,即字符串反向末尾的位置 return -1; } final int limit; if (endIndex < 0) { - limit = endIndex + text.length(); + limit = endIndex + text.length() + 1; } else { limit = Math.min(endIndex, text.length()); } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/CharSequenceUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/text/CharSequenceUtilTest.java index 85a35a18c..a8b1e53c1 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/CharSequenceUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/CharSequenceUtilTest.java @@ -47,5 +47,22 @@ public class CharSequenceUtilTest { Assert.assertEquals(str1, str2); } - // ------------------------------------------------------------------------ remove + @Test + public void indexOfTest(){ + int index = CharSequenceUtil.indexOf("abc123", '1'); + Assert.assertEquals(3, index); + index = CharSequenceUtil.indexOf("abc123", '3'); + Assert.assertEquals(5, index); + index = CharSequenceUtil.indexOf("abc123", 'a'); + Assert.assertEquals(0, index); + } + + @Test + public void indexOfTest2(){ + int index = CharSequenceUtil.indexOf("abc123", '1', 0, 3); + Assert.assertEquals(-1, index); + + index = CharSequenceUtil.indexOf("abc123", 'b', 0, 3); + Assert.assertEquals(1, index); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/finder/CharFinderTest.java b/hutool-core/src/test/java/cn/hutool/core/text/finder/CharFinderTest.java new file mode 100644 index 000000000..4a2cba983 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/text/finder/CharFinderTest.java @@ -0,0 +1,30 @@ +package cn.hutool.core.text.finder; + +import org.junit.Assert; +import org.junit.Test; + +public class CharFinderTest { + + @Test + public void startTest(){ + int start = new CharFinder('a').setText("cba123").start(2); + Assert.assertEquals(2, start); + + start = new CharFinder('c').setText("cba123").start(2); + Assert.assertEquals(-1, start); + + start = new CharFinder('3').setText("cba123").start(2); + Assert.assertEquals(5, start); + } + @Test + public void negativeStartTest(){ + int start = new CharFinder('a').setText("cba123").setNegative(true).start(2); + Assert.assertEquals(2, start); + + start = new CharFinder('2').setText("cba123").setNegative(true).start(2); + Assert.assertEquals(-1, start); + + start = new CharFinder('c').setText("cba123").setNegative(true).start(2); + Assert.assertEquals(0, start); + } +} From 212fc2497ae66b073fb7cc0e85afeb4d17babd51 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 16 Nov 2021 01:23:35 +0800 Subject: [PATCH 38/43] add pack info --- .../cn/hutool/core/text/finder/package-info.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 hutool-core/src/main/java/cn/hutool/core/text/finder/package-info.java diff --git a/hutool-core/src/main/java/cn/hutool/core/text/finder/package-info.java b/hutool-core/src/main/java/cn/hutool/core/text/finder/package-info.java new file mode 100644 index 000000000..98fccb90c --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/text/finder/package-info.java @@ -0,0 +1,13 @@ +/** + * 文本查找实现,包括: + *
      + *
    • 查找文本中的字符(正向、反向)
    • + *
    • 查找文本中的匹配字符(正向、反向)
    • + *
    • 查找文本中的字符串(正向、反向)
    • + *
    • 查找文本中匹配正则的字符串(正向)
    • + *
    + * + * @author looly + * + */ +package cn.hutool.core.text.finder; From 3894bc88c3ad92a4f9266f43cd2a55dcd188c045 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=85=B6=E6=B7=87?= Date: Tue, 16 Nov 2021 12:11:42 +0800 Subject: [PATCH 39/43] =?UTF-8?q?=E6=B7=BB=E5=8A=A0clone=E7=9A=84=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E6=8E=A5=E5=8F=A3=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cn/hutool/core/clone/DefaultClone.java | 29 +++++++ .../hutool/core/clone/DefaultCloneTest.java | 86 +++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 hutool-core/src/main/java/cn/hutool/core/clone/DefaultClone.java create mode 100644 hutool-core/src/test/java/cn/hutool/core/clone/DefaultCloneTest.java diff --git a/hutool-core/src/main/java/cn/hutool/core/clone/DefaultClone.java b/hutool-core/src/main/java/cn/hutool/core/clone/DefaultClone.java new file mode 100644 index 000000000..acfb5f6c4 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/clone/DefaultClone.java @@ -0,0 +1,29 @@ +package cn.hutool.core.clone; + + +import java.lang.reflect.Method; + +public interface DefaultClone extends java.lang.Cloneable { + + /** + * 浅拷贝 + * 功能与 {@link CloneSupport#clone()} 类似, 是一种接口版的实现 + * 一个类,一般只能继承一个类,但是可以实现多个接口,所以该接口会有一定程度的灵活性 + * + * @param T + * @return obj + */ + @SuppressWarnings({"unchecked"}) + default T clone0() { + try { + final Class objectClass = Object.class; + final Method clone = objectClass.getDeclaredMethod("clone"); + clone.setAccessible(true); + return (T) clone.invoke(this); + } catch (Exception e) { + throw new CloneRuntimeException(e); + } + } +} + + diff --git a/hutool-core/src/test/java/cn/hutool/core/clone/DefaultCloneTest.java b/hutool-core/src/test/java/cn/hutool/core/clone/DefaultCloneTest.java new file mode 100644 index 000000000..c2c45285a --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/clone/DefaultCloneTest.java @@ -0,0 +1,86 @@ +package cn.hutool.core.clone; + + +import org.junit.Assert; +import org.junit.Test; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class DefaultCloneTest { + + @Test + public void clone0() { + Car oldCar = new Car(); + oldCar.setId(1); + oldCar.setWheelList(Stream.of(new Wheel("h")).collect(Collectors.toList())); + + Car newCar = oldCar.clone0(); + Assert.assertEquals(oldCar.getId(),newCar.getId()); + Assert.assertEquals(oldCar.getWheelList(),newCar.getWheelList()); + + newCar.setId(2); + Assert.assertNotEquals(oldCar.getId(),newCar.getId()); + newCar.getWheelList().add(new Wheel("s")); + + Assert.assertNotSame(oldCar, newCar); + + } + +} + +class Car implements DefaultClone { + private Integer id; + private List wheelList; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public List getWheelList() { + return wheelList; + } + + public void setWheelList(List wheelList) { + this.wheelList = wheelList; + } + + @Override + public String toString() { + return "Car{" + + "id=" + id + + ", wheelList=" + wheelList + + '}'; + } +} + + +class Wheel { + private String direction; + + public Wheel(String direction) { + this.direction = direction; + } + + public String getDirection() { + return direction; + } + + public void setDirection(String direction) { + this.direction = direction; + } + + @Override + public String toString() { + return "Wheel{" + + "direction='" + direction + '\'' + + '}'; + } +} + + From ff70f92e9c726cdd07700e61dd3c55db0581e06a Mon Sep 17 00:00:00 2001 From: looly Date: Tue, 16 Nov 2021 16:12:23 +0800 Subject: [PATCH 40/43] add DefaultCloneable --- CHANGELOG.md | 1 + .../cn/hutool/core/clone/DefaultClone.java | 29 --------- .../hutool/core/clone/DefaultCloneable.java | 28 ++++++++ .../hutool/core/clone/DefaultCloneTest.java | 65 ++++--------------- 4 files changed, 42 insertions(+), 81 deletions(-) delete mode 100644 hutool-core/src/main/java/cn/hutool/core/clone/DefaultClone.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/clone/DefaultCloneable.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 15e5af7e2..872cd4e56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ * 【core 】 IdCardUtil.isValidCard不再自动trim(issue#I4I04O@Gitee) * 【core 】 改进TextFinder,支持限制结束位置及反向查找模式 * 【core 】 Opt增加部分方法(pr#459@Gitee) +* 【core 】 增加DefaultCloneable(pr#459@Gitee) * ### 🐞Bug修复 * 【core 】 修复FileResource构造fileName参数无效问题(issue#1942@Github) diff --git a/hutool-core/src/main/java/cn/hutool/core/clone/DefaultClone.java b/hutool-core/src/main/java/cn/hutool/core/clone/DefaultClone.java deleted file mode 100644 index acfb5f6c4..000000000 --- a/hutool-core/src/main/java/cn/hutool/core/clone/DefaultClone.java +++ /dev/null @@ -1,29 +0,0 @@ -package cn.hutool.core.clone; - - -import java.lang.reflect.Method; - -public interface DefaultClone extends java.lang.Cloneable { - - /** - * 浅拷贝 - * 功能与 {@link CloneSupport#clone()} 类似, 是一种接口版的实现 - * 一个类,一般只能继承一个类,但是可以实现多个接口,所以该接口会有一定程度的灵活性 - * - * @param T - * @return obj - */ - @SuppressWarnings({"unchecked"}) - default T clone0() { - try { - final Class objectClass = Object.class; - final Method clone = objectClass.getDeclaredMethod("clone"); - clone.setAccessible(true); - return (T) clone.invoke(this); - } catch (Exception e) { - throw new CloneRuntimeException(e); - } - } -} - - diff --git a/hutool-core/src/main/java/cn/hutool/core/clone/DefaultCloneable.java b/hutool-core/src/main/java/cn/hutool/core/clone/DefaultCloneable.java new file mode 100644 index 000000000..8c74f57a0 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/clone/DefaultCloneable.java @@ -0,0 +1,28 @@ +package cn.hutool.core.clone; + + +import cn.hutool.core.util.ReflectUtil; + +/** + * 克隆默认实现接口,用于实现返回指定泛型类型的克隆方法 + * + * @param 泛型类型 + * @since 5.7.17 + */ +public interface DefaultCloneable extends java.lang.Cloneable { + + /** + * 浅拷贝,提供默认的泛型返回值的clone方法。 + * + * @return obj + */ + default T clone0() { + try { + return ReflectUtil.invoke(this, "clone"); + } catch (Exception e) { + throw new CloneRuntimeException(e); + } + } +} + + diff --git a/hutool-core/src/test/java/cn/hutool/core/clone/DefaultCloneTest.java b/hutool-core/src/test/java/cn/hutool/core/clone/DefaultCloneTest.java index c2c45285a..da7be7da0 100644 --- a/hutool-core/src/test/java/cn/hutool/core/clone/DefaultCloneTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/clone/DefaultCloneTest.java @@ -1,6 +1,8 @@ package cn.hutool.core.clone; +import lombok.AllArgsConstructor; +import lombok.Data; import org.junit.Assert; import org.junit.Test; @@ -17,70 +19,29 @@ public class DefaultCloneTest { oldCar.setWheelList(Stream.of(new Wheel("h")).collect(Collectors.toList())); Car newCar = oldCar.clone0(); - Assert.assertEquals(oldCar.getId(),newCar.getId()); - Assert.assertEquals(oldCar.getWheelList(),newCar.getWheelList()); + Assert.assertEquals(oldCar.getId(), newCar.getId()); + Assert.assertEquals(oldCar.getWheelList(), newCar.getWheelList()); newCar.setId(2); - Assert.assertNotEquals(oldCar.getId(),newCar.getId()); + Assert.assertNotEquals(oldCar.getId(), newCar.getId()); newCar.getWheelList().add(new Wheel("s")); Assert.assertNotSame(oldCar, newCar); } -} - -class Car implements DefaultClone { - private Integer id; - private List wheelList; - - public Integer getId() { - return id; + @Data + static class Car implements DefaultCloneable { + private Integer id; + private List wheelList; } - public void setId(Integer id) { - this.id = id; + @Data + @AllArgsConstructor + static class Wheel { + private String direction; } - public List getWheelList() { - return wheelList; - } - - public void setWheelList(List wheelList) { - this.wheelList = wheelList; - } - - @Override - public String toString() { - return "Car{" + - "id=" + id + - ", wheelList=" + wheelList + - '}'; - } -} - - -class Wheel { - private String direction; - - public Wheel(String direction) { - this.direction = direction; - } - - public String getDirection() { - return direction; - } - - public void setDirection(String direction) { - this.direction = direction; - } - - @Override - public String toString() { - return "Wheel{" + - "direction='" + direction + '\'' + - '}'; - } } From a02a8d75d8c41a05d4bf149f44e259aed4f9fcd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9F=A9=E5=B8=85?= Date: Wed, 17 Nov 2021 15:40:41 +0800 Subject: [PATCH 41/43] =?UTF-8?q?StrUtil.toCamelCase=20=E6=96=B9=E6=B3=95?= =?UTF-8?q?=E9=87=8D=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cn/hutool/core/text/CharSequenceUtil.java | 13 +++++++++++++ .../java/cn/hutool/core/text/NamingCase.java | 16 ++++++++++++++-- .../java/cn/hutool/core/util/StrUtilTest.java | 6 ++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java b/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java index 9a9d66a11..2ff065d9f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java @@ -4022,6 +4022,19 @@ public class CharSequenceUtil { return NamingCase.toCamelCase(name); } + /** + * 将连接符方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。
    + * 例如:hello_world=》helloWorld; hello-world=》helloWorld + * + * @param name 转换前的下划线大写方式命名的字符串 + * @param symbol 连接符 + * @return 转换后的驼峰式命名的字符串 + * @see NamingCase#toCamelCase(CharSequence, char) + */ + public static String toCamelCase(CharSequence name, char symbol) { + return NamingCase.toCamelCase(name, symbol); + } + // ------------------------------------------------------------------------ isSurround /** diff --git a/hutool-core/src/main/java/cn/hutool/core/text/NamingCase.java b/hutool-core/src/main/java/cn/hutool/core/text/NamingCase.java index 2c720967f..bd1c4a749 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/NamingCase.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/NamingCase.java @@ -150,19 +150,30 @@ public class NamingCase { * @return 转换后的驼峰式命名的字符串 */ public static String toCamelCase(CharSequence name) { + return toCamelCase(name, CharUtil.UNDERLINE); + } + + /** + * 将连接符方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。
    + * + * @param name 转换前的自定义方式命名的字符串 + * @param symbol 连接符 + * @return 转换后的驼峰式命名的字符串 + */ + public static String toCamelCase(CharSequence name, char symbol) { if (null == name) { return null; } final String name2 = name.toString(); - if (StrUtil.contains(name2, CharUtil.UNDERLINE)) { + if (StrUtil.contains(name2, symbol)) { final int length = name2.length(); final StringBuilder sb = new StringBuilder(length); boolean upperCase = false; for (int i = 0; i < length; i++) { char c = name2.charAt(i); - if (c == CharUtil.UNDERLINE) { + if (c == symbol) { upperCase = true; } else if (upperCase) { sb.append(Character.toUpperCase(c)); @@ -176,4 +187,5 @@ public class NamingCase { return name2; } } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java index 9b9ebc7b4..e98ff4dbf 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java @@ -384,6 +384,12 @@ public class StrUtilTest { String abc1d = StrUtil.toCamelCase("abc_1d"); Assert.assertEquals("abc1d", abc1d); + + + String str2 = "Table-Test-Of-day"; + String result2 = StrUtil.toCamelCase(str2, CharUtil.DASHED); + System.out.println(result2); + Assert.assertEquals("tableTestOfDay", result2); } @Test From 1dc14f29978b0f06e5bd6fef2dd5d1ca55fd8681 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E6=AD=A2=E6=9C=88=E6=AE=87?= Date: Thu, 18 Nov 2021 01:23:33 +0000 Subject: [PATCH 42/43] =?UTF-8?q?CPU=E5=A4=A7=E5=B0=8F=E5=86=99=E4=BF=9D?= =?UTF-8?q?=E6=8C=81=E4=B8=80=E8=87=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/cn/hutool/system/oshi/CpuInfo.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hutool-system/src/main/java/cn/hutool/system/oshi/CpuInfo.java b/hutool-system/src/main/java/cn/hutool/system/oshi/CpuInfo.java index 040fdd104..531e06cc2 100644 --- a/hutool-system/src/main/java/cn/hutool/system/oshi/CpuInfo.java +++ b/hutool-system/src/main/java/cn/hutool/system/oshi/CpuInfo.java @@ -14,7 +14,7 @@ public class CpuInfo { private static final DecimalFormat LOAD_FORMAT = new DecimalFormat("#.00"); /** - * cpu核心数 + * CPU核心数 */ private Integer cpuNum; @@ -157,7 +157,7 @@ public class CpuInfo { @Override public String toString() { return "CpuInfo{" + - "cpu核心数=" + cpuNum + + "CPU核心数=" + cpuNum + ", CPU总的使用率=" + toTal + ", CPU系统使用率=" + sys + ", CPU用户使用率=" + used + From ae36ce7431a73bbe72cab6a45fe9a8ea59b11549 Mon Sep 17 00:00:00 2001 From: looly Date: Thu, 18 Nov 2021 11:52:22 +0800 Subject: [PATCH 43/43] fix code --- .../src/main/java/cn/hutool/core/text/NamingCase.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/text/NamingCase.java b/hutool-core/src/main/java/cn/hutool/core/text/NamingCase.java index bd1c4a749..fc5a753a3 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/NamingCase.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/NamingCase.java @@ -58,7 +58,7 @@ public class NamingCase { } /** - * 将驼峰式命名的字符串转换为使用符号连接方式。如果转换前的驼峰式命名的字符串为空,则返回空字符串。
    + * 将驼峰式命名的字符串转换为使用符号连接方式。如果转换前的驼峰式命名的字符串为空,则返回空字符串。 * * @param str 转换前的驼峰式命名的字符串,也可以为符号连接形式 * @param symbol 连接符 @@ -154,11 +154,12 @@ public class NamingCase { } /** - * 将连接符方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。
    + * 将连接符方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 * - * @param name 转换前的自定义方式命名的字符串 + * @param name 转换前的自定义方式命名的字符串 * @param symbol 连接符 * @return 转换后的驼峰式命名的字符串 + * @since 5.7.17 */ public static String toCamelCase(CharSequence name, char symbol) { if (null == name) {