diff --git a/CHANGELOG.md b/CHANGELOG.md index 370dee44b3..872cd4e566 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,27 @@ ------------------------------------------------------------------------------------------------------------- -# 5.7.16 (2021-11-04) +# 5.7.17 (2021-11-16) + +### 🐣新特性 +* 【core 】 增加AsyncUtil(pr#457@Gitee) +* 【http 】 增加HttpResource(issue#1943@Github) +* 【http 】 增加BytesBody、FormUrlEncodedBody +* 【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) +* 【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) + +------------------------------------------------------------------------------------------------------------- + +# 5.7.16 (2021-11-07) ### 🐣新特性 * 【core 】 增加DateTime.toLocalDateTime @@ -26,6 +46,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) @@ -35,6 +56,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/README-EN.md b/README-EN.md index f73d4c3044..f7d4fd9de4 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 ae1aea6e88..6b096cd290 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 e8406ce304..f3ca4b63cf 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 3e6f04e998..2b2bc35df8 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 b97625af45..b9f63e9df8 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.17-SNAPSHOT hutool-all diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index 5590a0b537..204296f550 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.17-SNAPSHOT hutool-aop @@ -19,7 +19,7 @@ 3.3.0 - 5.3.10 + 5.3.12 diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml index 96f02bf665..5199fb9a5a 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.17-SNAPSHOT hutool-bloomFilter diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml index a484343e3f..1763613e36 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.17-SNAPSHOT hutool-bom diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml index 7e84b9cea4..c5597d752b 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.17-SNAPSHOT hutool-cache diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml index b22ac7ab2b..f09694d5be 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.17-SNAPSHOT hutool-captcha diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index ed9ac8a21b..810fcc936c 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.17-SNAPSHOT hutool-core 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 0000000000..8c74f57a00 --- /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/main/java/cn/hutool/core/codec/PercentCodec.java b/hutool-core/src/main/java/cn/hutool/core/codec/PercentCodec.java index 91faa74f63..71ae0ec8ba 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 @@ -174,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/collection/CollStreamUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/CollStreamUtil.java index b418ddcc18..53243cb718 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/date/DateUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java index 705e09ca2e..a43a053dc6 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); } /** 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 02699d4a47..b9b3ecdda9 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 cf0d851d51..a6950d94e1 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-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 3c44d8d50b..9bac9521c5 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/main/java/cn/hutool/core/lang/ConsoleTable.java b/hutool-core/src/main/java/cn/hutool/core/lang/ConsoleTable.java index 23066a045b..806f5cee27 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-core/src/main/java/cn/hutool/core/lang/Opt.java b/hutool-core/src/main/java/cn/hutool/core/lang/Opt.java index 7a7acb17f8..6f95a8c7a7 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,11 +24,14 @@ */ 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; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; @@ -49,20 +52,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} * @@ -110,6 +99,31 @@ 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); + } + + /** + * 包裹里实际的元素 + */ + 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()} @@ -153,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; } /** @@ -173,14 +189,40 @@ public class Opt { * * @param action 包裹里的值存在时的操作 * @param emptyAction 包裹里的值不存在时的操作 + * @return this; * @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); } else { emptyAction.callWithRuntimeException(); } + return this; + } + + + /** + * 如果包裹里的值存在,就执行传入的值存在时的操作({@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(); + } } /** @@ -242,6 +284,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} + * @see Optional#flatMap(Function) + * @since 5.7.16 + */ + public Opt flattedMap(Function> mapper) { + Objects.requireNonNull(mapper); + if (isEmpty()) { + return empty(); + } else { + return ofNullable(mapper.apply(value).orElse(null)); + } + } + /** * 如果包裹里元素的值存在,就执行对应的操作,并返回本身 * 如果不存在,返回一个空的{@code Opt} @@ -395,6 +459,32 @@ public class Opt { } } + /** + * 转换为 {@link Optional}对象 + * + * @return {@link Optional}对象 + * @since 5.7.16 + */ + public Optional toOptional() { + 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/main/java/cn/hutool/core/lang/RegexPool.java b/hutool-core/src/main/java/cn/hutool/core/lang/RegexPool.java index 3307758873..7d1295e0c2 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/lang/tree/Tree.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/Tree.java index 85801c4386..76272e9bc1 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/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 0000000000..1b41f023ea --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/net/FormUrlencoded.java @@ -0,0 +1,19 @@ +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,默认除"-", "_", ".", "*"外都编码
+ * 这个类似于JDK提供的{@link java.net.URLEncoder} + */ + 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 713b082c2e..2c10260a4b 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 @@ -13,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 &}" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" @@ -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 = PercentCodec.of(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 8ae9bc59f4..106d973e4b 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. 跳过不符合规范的%形式,直接输出
 	 * 
@@ -66,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/URLEncodeUtil.java b/hutool-core/src/main/java/cn/hutool/core/net/URLEncodeUtil.java index 94d462bd77..cc01cd212a 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/URLEncoder.java b/hutool-core/src/main/java/cn/hutool/core/net/URLEncoder.java index f90609e0d9..cbed10e1e1 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/UrlPath.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java index bc91edf9b7..890c14b663 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 cd7fff6405..6260c727ae 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,13 +1,15 @@ 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; 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; @@ -25,6 +27,10 @@ import java.util.Map; public class UrlQuery { private final TableMap query; + /** + * 是否为x-www-form-urlencoded模式,此模式下空格会编码为'+' + */ + private final boolean isFormUrlEncoded; /** * 构建UrlQuery @@ -36,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 * @@ -57,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); } /** @@ -72,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; } /** @@ -144,6 +195,103 @@ public class UrlQuery { } } + return doParse(queryStr, charset); + } + + /** + * 获得查询的Map + * + * @return 查询的Map,只读 + */ + public Map getQueryMap() { + return MapUtil.unmodifiable(this.query); + } + + /** + * 获取查询值 + * + * @param key 键 + * @return 值 + */ + public CharSequence get(CharSequence key) { + if (MapUtil.isEmpty(this.query)) { + return null; + } + return this.query.get(key); + } + + /** + * 构建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 charset encode编码,null表示不做encode编码 + * @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); + } + + /** + * 构建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; + } + + final StringBuilder sb = new StringBuilder(); + CharSequence name; + CharSequence value; + for (Map.Entry entry : this.query) { + name = entry.getKey(); + if (null != name) { + if (sb.length() > 0) { + sb.append("&"); + } + sb.append(keyCoder.encode(name, charset)); + value = entry.getValue(); + if (null != value) { + sb.append("=").append(valueCoder.encode(value, charset)); + } + } + } + return sb.toString(); + } + + @Override + public String toString() { + 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; // 未处理字符开始位置 @@ -188,68 +336,6 @@ public class UrlQuery { return this; } - /** - * 获得查询的Map - * - * @return 查询的Map,只读 - */ - public Map getQueryMap() { - return MapUtil.unmodifiable(this.query); - } - - /** - * 获取查询值 - * - * @param key 键 - * @return 值 - */ - public CharSequence get(CharSequence key) { - if (MapUtil.isEmpty(this.query)) { - return null; - } - return this.query.get(key); - } - - /** - * 构建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 charset encode编码,null表示不做encode编码 - * @return URL查询字符串 - */ - public String build(Charset charset) { - if (MapUtil.isEmpty(this.query)) { - return StrUtil.EMPTY; - } - - final StringBuilder sb = new StringBuilder(); - CharSequence name; - CharSequence value; - for (Map.Entry entry : this.query) { - name = entry.getKey(); - if (null != name) { - if(sb.length() >0){ - sb.append("&"); - } - sb.append(RFC3986.QUERY_PARAM_NAME.encode(name, charset)); - value = entry.getValue(); - if (null != value) { - sb.append("=").append(RFC3986.QUERY_PARAM_VALUE.encode(value, charset)); - } - } - } - return sb.toString(); - } - - @Override - public String toString() { - return build(null); - } - /** * 对象转换为字符串,用于URL的Query中 * @@ -283,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/text/CharSequenceUtil.java b/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java index 511fb3c6e1..2ff065d9f6 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,9 @@ 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; import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.CharsetUtil; @@ -35,7 +38,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"}
@@ -1064,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); } @@ -1087,29 +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 < 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); } /** @@ -1168,40 +1159,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 +1185,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 +1199,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 +1207,23 @@ 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) + .setText(text).setNegative(true).start(from); } /** @@ -4063,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 2c720967f8..fc5a753a3c 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 连接符 @@ -150,19 +150,31 @@ public class NamingCase { * @return 转换后的驼峰式命名的字符串 */ public static String toCamelCase(CharSequence name) { + return toCamelCase(name, CharUtil.UNDERLINE); + } + + /** + * 将连接符方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 + * + * @param name 转换前的自定义方式命名的字符串 + * @param symbol 连接符 + * @return 转换后的驼峰式命名的字符串 + * @since 5.7.17 + */ + 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 +188,5 @@ public class NamingCase { return name2; } } + } 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 a70d5542f8..b90c23ce19 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 字符串模板 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 90f0413526..eb47e76305 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 0fed9fefcf..4328581e15 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,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 length = text.length(); - for (int i = from; i < length; 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 301f3a257d..216d68ee7b 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,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 length = text.length(); - for (int i = from; i < length; 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/Finder.java b/hutool-core/src/main/java/cn/hutool/core/text/finder/Finder.java index 82ab597ed0..f6a1aa0491 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 ca19d4424a..3face4f306 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 @@ -24,9 +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; - if(result < text.length()){ - 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 a393b9e234..92afc408d6 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 @@ -40,17 +41,32 @@ 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)) { - return matcher.start(); + // 只有匹配到的字符串结尾在limit范围内,才算找到 + if(matcher.end() <= getValidEndIndex()){ + 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 43ecb8b82f..edf931dde6 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,46 @@ 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; /** * 构造 * - * @param str 被查找的字符串 + * @param strToFind 被查找的字符串 * @param caseInsensitive 是否忽略大小写 */ - public StrFinder(CharSequence str, boolean caseInsensitive) { - Assert.notEmpty(str); - this.str = str; + public StrFinder(CharSequence strToFind, boolean caseInsensitive) { + Assert.notEmpty(strToFind); + this.strToFind = strToFind; this.caseInsensitive = caseInsensitive; } @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(); + 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 +59,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 7045aa8fdd..f2a5f9b919 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,8 @@ public abstract class TextFinder implements Finder, Serializable { private static final long serialVersionUID = 1L; protected CharSequence text; + protected int endIndex = -1; + protected boolean negative; /** * 设置被查找的文本 @@ -20,7 +24,51 @@ 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; + } + + /** + * 设置是否反向查找,{@code true}表示从后向前查找 + * + * @param negative 结束位置(不包括) + * @return this + */ + public TextFinder setNegative(boolean negative) { + this.negative = negative; + return this; + } + + /** + * 获取有效结束位置
+ * 如果{@link #endIndex}小于0,在反向模式下是开头(-1),正向模式是结尾(text.length()) + * + * @return 有效结束位置 + */ + protected int getValidEndIndex() { + if(negative && -1 == endIndex){ + // 反向查找模式下,-1表示0前面的位置,即字符串反向末尾的位置 + return -1; + } + final int limit; + if (endIndex < 0) { + limit = endIndex + text.length() + 1; + } else { + limit = Math.min(endIndex, text.length()); + } + return limit; + } } 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 0000000000..98fccb90c7 --- /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; 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 new file mode 100644 index 0000000000..e7b7dc6d82 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/thread/AsyncUtil.java @@ -0,0 +1,60 @@ +package cn.hutool.core.thread; + +import java.lang.reflect.UndeclaredThrowableException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +/** + * {@link CompletableFuture}异步工具类
+ * {@link CompletableFuture} 是 Future 的改进,可以通过传入回调对象,在任务完成后调用之 + * + * @author achao1441470436@gmail.com + * @since 5.7.17 + */ +public class AsyncUtil { + + /** + * 等待所有任务执行完毕,包裹了异常 + * + * @param tasks 并行任务 + * @throws UndeclaredThrowableException 未受检异常 + */ + public static void waitAll(CompletableFuture... tasks) { + try { + CompletableFuture.allOf(tasks).get(); + } catch (InterruptedException | ExecutionException 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); + } + } + + /** + * 获取异步任务结果,包裹了异常 + * + * @param task 异步任务 + * @param 任务返回值类型 + * @return 任务返回值 + * @throws RuntimeException 未受检异常 + */ + public static T get(CompletableFuture task) { + try { + return task.get(); + } catch (InterruptedException | ExecutionException e) { + throw new ThreadException(e); + } + } + +} 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 0000000000..b61963b2cb --- /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/IdcardUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/IdcardUtil.java index c0f871c3a5..e7adc4a38e 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位身份证 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 08f30132aa..8953e48b03 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,13 +1361,12 @@ 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<>(size, 1); while (set.size() < size) { - set.add(begin + ran.nextInt(end - begin)); + 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/main/java/cn/hutool/core/util/ReflectUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java index 7a98696f4d..affdcac6e4 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)); } /** 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 045fc72b81..7243126951 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表示不解码 @@ -327,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); } @@ -344,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); } @@ -360,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/clone/DefaultCloneTest.java b/hutool-core/src/test/java/cn/hutool/core/clone/DefaultCloneTest.java new file mode 100644 index 0000000000..da7be7da01 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/clone/DefaultCloneTest.java @@ -0,0 +1,47 @@ +package cn.hutool.core.clone; + + +import lombok.AllArgsConstructor; +import lombok.Data; +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); + + } + + @Data + static class Car implements DefaultCloneable { + private Integer id; + private List wheelList; + } + + @Data + @AllArgsConstructor + static class Wheel { + private String direction; + } + +} + + 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 8d4eacdd74..dac1329c39 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-core/src/test/java/cn/hutool/core/lang/OptTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/OptTest.java index 8e7f5da2f0..013e7897d8 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; @@ -8,7 +9,11 @@ import org.junit.Assert; 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; /** * {@link Opt}的单元测试 @@ -43,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 @@ -126,6 +134,61 @@ 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); + Assert.assertNull(user.getUsername()); + Assert.assertNull(user.getNickname()); + } + + @Test + public void ofEmptyAbleTest() { + // 以前,输入一个CollectionUtil感觉要命,类似前缀的类一大堆,代码补全形同虚设(在项目中起码要输入完CollectionUtil才能在第一个调出这个函数) + // 关键它还很常用,判空和判空集合真的太常用了... + 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); + } + + @SuppressWarnings({"MismatchedQueryAndUpdateOfCollection", "ConstantConditions"}) + @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 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 3007991cf8..c695a707c9 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-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 0000000000..8006dbf9c9 --- /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 0000000000..c76f8134de --- /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 b59e0bd80f..0000000000 --- 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-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java index 85d11ec000..4bdaabc0de 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,26 @@ 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 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" + final String a = UrlQuery.of(MapUtil.of("a ", " ")).build(CharsetUtil.CHARSET_UTF_8); + Assert.assertEquals("a%20=%20", a); + } } 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 85a35a18c5..a8b1e53c19 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 0000000000..4a2cba983d --- /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); + } +} 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 8bc3027fc8..18050df586 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/thread/AsyncUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/thread/AsyncUtilTest.java new file mode 100644 index 0000000000..f6a00a4867 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/thread/AsyncUtilTest.java @@ -0,0 +1,38 @@ +package cn.hutool.core.thread; + +import cn.hutool.core.lang.Assert; +import org.junit.Ignore; +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 AsyncUtilTest { + + @Test + @Ignore + public void waitAndGetTest() { + CompletableFuture hutool = CompletableFuture.supplyAsync(() -> { + ThreadUtil.sleep(1, TimeUnit.SECONDS); + return "hutool"; + }); + CompletableFuture sweater = CompletableFuture.supplyAsync(() -> { + ThreadUtil.sleep(2, TimeUnit.SECONDS); + return "卫衣"; + }); + CompletableFuture warm = CompletableFuture.supplyAsync(() -> { + ThreadUtil.sleep(3, TimeUnit.SECONDS); + return "真暖和"; + }); + // 等待完成 + AsyncUtil.waitAll(hutool, sweater, 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 ffdbd953e3..137c849cd6 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); + } } 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 b0aca478c9..e98ff4dbf4 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")); } @@ -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 diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 4d3a77ef26..b7f6f5e641 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.17-SNAPSHOT hutool-cron 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 f6d7eb1d90..dc0ef96183 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 8b12f3d02c..e2111b6a11 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 9932ee328a..c68015408a 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; @@ -128,21 +129,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; } /** @@ -268,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,无锁 * @@ -282,4 +296,4 @@ public class TaskTable implements Serializable { } } } -} \ No newline at end of file +} 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 cfcf6591ae..a67ff667d7 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; 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 0000000000..318a25e1f4 --- /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); + } +} diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index b58ed22290..7f112cfb5c 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.17-SNAPSHOT hutool-crypto diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 99574b86d3..835eede431 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.17-SNAPSHOT hutool-db @@ -81,7 +81,7 @@ com.github.chris2018998 beecp - 3.2.7 + 3.2.9 slf4j-api @@ -149,7 +149,7 @@ org.postgresql postgresql - 42.3.0 + 42.3.1 test diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 2e73f80808..b6c87f3f3e 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.17-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index fd49b4779f..9f32ccf435 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.17-SNAPSHOT hutool-extra @@ -19,7 +19,7 @@ 2.3 - 3.7.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 diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index b86a358194..3c35d03d4f 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.17-SNAPSHOT hutool-http 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 29064428c5..7c1759a2de 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/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index e0135e77a7..12a14e6990 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; @@ -15,14 +14,16 @@ import cn.hutool.core.net.url.UrlBuilder; 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; @@ -1211,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 HttpUtil.toParams(this.form, this.charset); + body.writeClose(this.httpConnection.getOutputStream()); } /** @@ -1237,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/HttpResource.java b/hutool-http/src/main/java/cn/hutool/http/HttpResource.java new file mode 100644 index 0000000000..9e6542efd4 --- /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/HttpUtil.java b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java index b7f485b0b8..0257cd4640 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; @@ -452,7 +453,8 @@ public class HttpUtil { /** * 将Map形式的Form表单数据转换为Url参数形式
* paramMap中如果key为空(null和"")会被忽略,如果value为null,会被做为空白符("")
- * 会自动url编码键和值 + * 会自动url编码键和值
+ * 此方法用于拼接URL中的Query部分,并不适用于POST请求中的表单 * *
 	 * key1=v1&key2=&key3=v3
@@ -461,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);
 	}
 
 	/**
@@ -477,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);
 	}
 
 	/**
@@ -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;
 				}
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 0000000000..5ca620ad1e
--- /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/BytesBody.java b/hutool-http/src/main/java/cn/hutool/http/body/BytesBody.java new file mode 100644 index 0000000000..138b48f3a5 --- /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 0000000000..aa675b0a28 --- /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/MultipartBody.java b/hutool-http/src/main/java/cn/hutool/http/body/MultipartBody.java index 0384a0e614..dbf955363b 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,35 +1,27 @@ 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.ObjectUtil; import cn.hutool.core.util.RandomUtil; -import cn.hutool.core.util.StrUtil; import cn.hutool.http.ContentType; -import cn.hutool.http.HttpUtil; +import cn.hutool.http.MultipartOutputStream; +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{ - - 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_FILE_TEMPLATE = "Content-Disposition: form-data; name=\"{}\"; filename=\"{}\"\r\n"; +public class MultipartBody implements RequestBody { + 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\r\n"; /** * 存储表单数据 @@ -42,11 +34,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 +48,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; @@ -77,78 +70,17 @@ public class MultipartBody implements RequestBody{ */ @Override public void write(OutputStream out) { - writeForm(out); - formEnd(out); - } - - // 普通字符串数据 - - /** - * 发送文件对象表单 - * - * @param out 输出流 - */ - private void writeForm(OutputStream out) { + final MultipartOutputStream stream = new MultipartOutputStream(out, this.charset); if (MapUtil.isNotEmpty(this.form)) { - for (Map.Entry entry : this.form.entrySet()) { - appendPart(entry.getKey(), entry.getValue(), out); - } + this.form.forEach(stream::write); } + stream.finish(); } - /** - * 添加Multipart表单的数据项 - * - * @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; - } - - 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{ - // 普通数据 - write(out, StrUtil.format(CONTENT_DISPOSITION_TEMPLATE, formFieldName)); - write(out, value); - } - - write(out, StrUtil.CRLF); - } - - /** - * 上传表单结束 - * - * @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); + @Override + public String toString() { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + write(out); + return IoUtil.toStr(out, this.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 16b6a04e40..76fb9b3788 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); + } + } } 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 2aabe1afea..c1cb54e9d1 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()); } + } 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 0000000000..471b6813c7 --- /dev/null +++ b/hutool-http/src/test/java/cn/hutool/http/body/MultipartBodyTest.java @@ -0,0 +1,28 @@ +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()); +// Console.log(body); + } +} diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 7a94609b1d..70797ce5a6 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.17-SNAPSHOT hutool-json diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml index efd206d247..062dd810fa 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.17-SNAPSHOT hutool-jwt diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index f0175f355d..b1cd3eea17 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.17-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 90a2094c17..3aafbb163c 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.17-SNAPSHOT hutool-poi 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 301d393091..2ff3d55e65 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 0000000000..ef15720127 --- /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/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 95b71164ee..71d31d1c8f 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 8ff603bcba..60d888ebc0 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 32e3e1c52c..a4c53dc0f2 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 34de9aa0a7..0a85499bb9 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/ExcelReadTest.java b/hutool-poi/src/test/java/cn/hutool/poi/excel/ExcelReadTest.java index ebe04bf0f9..72ac31fabb 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)); + } } 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 cda21e3049..a4f9d13c38 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) { } } ); diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index 9661de3561..5dd7116ea5 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.17-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index f70bafd701..e18a4b6803 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.17-SNAPSHOT hutool-setting diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index cb37a29e16..5bca495ab7 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.17-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index 916d120173..ec82d73d88 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.17-SNAPSHOT hutool-system @@ -30,7 +30,7 @@ com.github.oshi oshi-core - 5.8.2 + 5.8.3 provided 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 040fdd104e..531e06cc2e 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 + diff --git a/pom.xml b/pom.xml index 3ea2cdd166..431e1a36e2 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.7.16-SNAPSHOT + 5.7.17-SNAPSHOT hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/dromara/hutool