diff --git a/hutool-core/src/main/java/cn/hutool/core/date/DatePattern.java b/hutool-core/src/main/java/cn/hutool/core/date/DatePattern.java index 0f54f5331..27ca09c95 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/DatePattern.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/DatePattern.java @@ -11,6 +11,42 @@ import java.util.regex.Pattern; /** * 日期格式化类,提供常用的日期格式化对象 * + *

所有的jdk日期格式模式字符串 jdk18 date format pattern + * + * jdk date format pattern (Pattern Letters and Symbols) 日期格式模式字符串 + * + *

+ * + *

工具类,提供格式化字符串很多,但是对于具体什么含义,不够清晰,这里进行说明:

+ *

+ * 常见日期格式模式字符串: + *

+ *

+ * + * 系统提供的,请查看,有大量定义好的格式化对象,可以直接使用,如: + * {@link DateTimeFormatter#ISO_DATE} + * {@link DateTimeFormatter#ISO_DATE_TIME} + * 查看更多,请参阅上述官方文档 + * * @author Looly */ public class DatePattern { @@ -291,11 +327,11 @@ public class DatePattern { public static final FastDateFormat UTC_MS_FORMAT = FastDateFormat.getInstance(UTC_MS_PATTERN, TimeZone.getTimeZone("UTC")); /** - * UTC时间:yyyy-MM-dd'T'HH:mm:ssZ + * UTC时间:yyyy-MM-dd'T'HH:mm:ss.SSSZ */ public static final String UTC_MS_WITH_ZONE_OFFSET_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; /** - * UTC时间{@link FastDateFormat}:yyyy-MM-dd'T'HH:mm:ssZ + * UTC时间{@link FastDateFormat}:yyyy-MM-dd'T'HH:mm:ss.SSSZ */ public static final FastDateFormat UTC_MS_WITH_ZONE_OFFSET_FORMAT = FastDateFormat.getInstance(UTC_MS_WITH_ZONE_OFFSET_PATTERN, TimeZone.getTimeZone("UTC")); diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/FastStream.java b/hutool-core/src/main/java/cn/hutool/core/stream/FastStream.java index 7619a5303..13fdd7182 100644 --- a/hutool-core/src/main/java/cn/hutool/core/stream/FastStream.java +++ b/hutool-core/src/main/java/cn/hutool/core/stream/FastStream.java @@ -158,7 +158,7 @@ public class FastStream implements Stream, Iterable { public static FastStream iterate(T seed, Predicate hasNext, UnaryOperator next) { Objects.requireNonNull(next); Objects.requireNonNull(hasNext); - return new FastStream<>(StreamHelper.iterate(seed, hasNext, next)); + return new FastStream<>(StreamUtil.iterate(seed, hasNext, next)); } /** @@ -674,10 +674,8 @@ public class FastStream implements Stream, Iterable { * @param 给定的数组类型 * @return 包含此流元素的指定的数组 * @throws ArrayStoreException 如果元素转换失败,例如不是该元素类型及其父类,则抛出该异常 - *

- * 例如以下代码编译正常,但运行时会抛出 {@link ArrayStoreException} - *

{@code String[] strings = Stream.builder().add(1).build().toArray(String[]::new); }
- *

+ * 例如以下代码编译正常,但运行时会抛出 {@link ArrayStoreException} + *
{@code String[] strings = Stream.builder().add(1).build().toArray(String[]::new); }
*/ @Override public
A[] toArray(IntFunction generator) { @@ -1441,7 +1439,7 @@ public class FastStream implements Stream, Iterable { */ public FastStream takeWhile(Predicate predicate) { Objects.requireNonNull(predicate); - return of(StreamHelper.takeWhile(stream, predicate)); + return of(StreamUtil.takeWhile(stream, predicate)); } /** @@ -1477,7 +1475,7 @@ public class FastStream implements Stream, Iterable { */ public FastStream dropWhile(Predicate predicate) { Objects.requireNonNull(predicate); - return of(StreamHelper.dropWhile(stream, predicate)); + return of(StreamUtil.dropWhile(stream, predicate)); } /** @@ -1492,6 +1490,24 @@ public class FastStream implements Stream, Iterable { return dropWhile(predicate); } + /** + * 流是否为空 + * + * @return 流是否为空 + */ + public boolean isEmpty() { + return !findAny().isPresent(); + } + + /** + * 流是否不为空 + * + * @return 流是否不为空 + */ + public boolean isNotEmpty() { + return !isEmpty(); + } + public interface FastStreamBuilder extends Consumer, cn.hutool.core.builder.Builder> { /** diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/StreamUtil.java b/hutool-core/src/main/java/cn/hutool/core/stream/StreamUtil.java index bbd4b011a..3cb1bd849 100644 --- a/hutool-core/src/main/java/cn/hutool/core/stream/StreamUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/stream/StreamUtil.java @@ -3,6 +3,9 @@ package cn.hutool.core.stream; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.lang.Assert; +import cn.hutool.core.stream.spliterators.DropWhileSpliterator; +import cn.hutool.core.stream.spliterators.IterateSpliterator; +import cn.hutool.core.stream.spliterators.TakeWhileSpliterator; import cn.hutool.core.util.CharsetUtil; import java.io.File; @@ -10,16 +13,20 @@ import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Spliterator; import java.util.Spliterators; import java.util.function.Function; +import java.util.function.Predicate; import java.util.function.UnaryOperator; import java.util.stream.Stream; import java.util.stream.StreamSupport; +import static java.util.Objects.requireNonNull; + /** * {@link Stream} 工具类 * - * @author looly + * @author looly emptypoint VampireAchao * @since 5.6.7 */ public class StreamUtil { @@ -141,4 +148,84 @@ public class StreamUtil { final Function toStringFunc) { return stream.collect(CollectorUtil.joining(delimiter, toStringFunc)); } + + + /** + * 返回无限有序流 + * 该流由 初始值 然后判断条件 以及执行 迭代函数 进行迭代获取到元素 + * + * @param 元素类型 + * @param seed 初始值 + * @param hasNext 条件值 + * @param next 用上一个元素作为参数执行并返回一个新的元素 + * @return 无限有序流 + */ + public static Stream iterate(T seed, Predicate hasNext, UnaryOperator next) { + requireNonNull(next); + requireNonNull(hasNext); + return StreamSupport.stream(IterateSpliterator.create(seed, hasNext, next), false); + } + + /** + * 保留 与指定断言 匹配时的元素, 在第一次不匹配时终止, 抛弃当前(第一个不匹配元素)及后续所有元素 + *

与 jdk9 中的 takeWhile 方法不太一样, 这里的实现是个 顺序的、有状态的中间操作

+ *

本环节中是顺序执行的, 但是后续操作可以支持并行流

+ *

但是不建议在并行流中使用, 除非你确定 takeWhile 之后的操作能在并行流中受益很多

+ * + * @param source 源流 + * @param 元素类型 + * @param predicate 断言 + * @return 与指定断言匹配的元素组成的流 + */ + public static Stream takeWhile(Stream source, Predicate predicate) { + requireNonNull(source); + requireNonNull(predicate); + return createStatefulNewStream(source, TakeWhileSpliterator.create(source.spliterator(), predicate)); + } + + /** + * 删除 与指定断言 匹配的元素, 在第一次不匹配时终止, 返回当前(第一个不匹配元素)及剩余元素组成的新流 + *

与 jdk9 中的 dropWhile 方法不太一样, 这里的实现是个 顺序的、有状态的中间操作

+ *

本环节中是顺序执行的, 但是后续操作可以支持并行流

+ *

但是不建议在并行流中使用, 除非你确定 dropWhile 之后的操作能在并行流中受益很多

+ * + * @param source 源流 + * @param 元素类型 + * @param predicate 断言 + * @return 剩余元素组成的流 + */ + public static Stream dropWhile(Stream source, Predicate predicate) { + requireNonNull(source); + requireNonNull(predicate); + return createStatefulNewStream(source, DropWhileSpliterator.create(source.spliterator(), predicate)); + } + + // region 私有方法 + /* ================================================== 私有方法 =================================================== */ + + /** + * 根据 源流 和 新的Spliterator 生成新的流 + *

这是一个 顺序的、有状态的流

+ *

在新流的第一个节点是顺序执行的, 但是后续操作可以支持并行流

+ * + * @param source 源流 + * @param newSpliterator 新流的Spliterator + * @param 旧流的元素类型 + * @param 新流的元素类型 + * @return 新流 + */ + private static Stream createStatefulNewStream(Stream source, Spliterator newSpliterator) { + // 创建新流 + Stream newStream = StreamSupport.stream(newSpliterator, source.isParallel()); + // 如果旧流是并行流, 新流主动调用一个有状态的操作, 虽然没有意义, 但是可以让后续的无状态节点正常并发 + if (source.isParallel()) { + newStream = newStream.limit(Long.MAX_VALUE); + } + // 由于新流不与旧流的节点关联, 所以需要主动设置旧流的close方法, 哪怕几乎不可能有人在旧流上设置onClose操作 + return newStream.onClose(source::close); + } + + /* ============================================================================================================== */ + // endregion + } diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/spliterators/DropWhileSpliterator.java b/hutool-core/src/main/java/cn/hutool/core/stream/spliterators/DropWhileSpliterator.java new file mode 100644 index 000000000..4bb620670 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/stream/spliterators/DropWhileSpliterator.java @@ -0,0 +1,72 @@ +package cn.hutool.core.stream.spliterators; + +import java.util.Comparator; +import java.util.Spliterator; +import java.util.function.Consumer; +import java.util.function.Predicate; + +/** + * dropWhile 的 Spliterator + *

借鉴自StreamEx

+ * + * @author emptypoint + * @since 6.0.0 + */ +public class DropWhileSpliterator implements Spliterator { + + public static DropWhileSpliterator create(Spliterator source, Predicate predicate) { + return new DropWhileSpliterator<>(source, predicate); + } + + private final Spliterator source; + private final Predicate predicate; + private boolean isFound = false; + + private DropWhileSpliterator(Spliterator source, Predicate predicate) { + this.source = source; + this.predicate = predicate; + } + + @Override + public boolean tryAdvance(Consumer action) { + boolean hasNext = true; + // 如果 还没找到 并且 流中还有元素 继续找 + while (!isFound && hasNext) { + hasNext = source.tryAdvance(e -> { + if (!predicate.test(e)) { + // 第一次不匹配 + isFound = true; + action.accept(e); + } + }); + } + + // 对找到的元素进行后续处理 + if (isFound) { + source.forEachRemaining(action); + } + + // 该环节已经处理完成 + return false; + } + + @Override + public Spliterator trySplit() { + return null; + } + + @Override + public long estimateSize() { + return Long.MAX_VALUE; + } + + @Override + public int characteristics() { + return source.characteristics() & ~Spliterator.SIZED; + } + + @Override + public Comparator getComparator() { + return source.getComparator(); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/spliterators/IterateSpliterator.java b/hutool-core/src/main/java/cn/hutool/core/stream/spliterators/IterateSpliterator.java new file mode 100644 index 000000000..edf2170f5 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/stream/spliterators/IterateSpliterator.java @@ -0,0 +1,76 @@ +package cn.hutool.core.stream.spliterators; + +import java.util.Objects; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.function.UnaryOperator; + +/** + * 无限有序流 的Spliterator + * + * @author VampireAchao + * @since 6.0.0 + */ +public class IterateSpliterator extends Spliterators.AbstractSpliterator { + private final T seed; + private final Predicate hasNext; + private final UnaryOperator next; + private T prev; + private boolean started; + private boolean finished; + + /** + * Creates a spliterator reporting the given estimated size and + * additionalCharacteristics. + */ + IterateSpliterator(T seed, Predicate hasNext, UnaryOperator next) { + super(Long.MAX_VALUE, Spliterator.ORDERED | Spliterator.IMMUTABLE); + this.seed = seed; + this.hasNext = hasNext; + this.next = next; + } + + public static IterateSpliterator create(T seed, Predicate hasNext, UnaryOperator next) { + return new IterateSpliterator<>(seed, hasNext, next); + } + + @Override + public boolean tryAdvance(Consumer action) { + Objects.requireNonNull(action); + if (finished) { + return false; + } + T t; + if (started) { + t = next.apply(prev); + } else { + t = seed; + started = true; + } + if (!hasNext.test(t)) { + prev = null; + finished = true; + return false; + } + prev = t; + action.accept(prev); + return true; + } + + @Override + public void forEachRemaining(Consumer action) { + Objects.requireNonNull(action); + if (finished) { + return; + } + finished = true; + T t = started ? next.apply(prev) : seed; + prev = null; + while (hasNext.test(t)) { + action.accept(t); + t = next.apply(t); + } + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/spliterators/TakeWhileSpliterator.java b/hutool-core/src/main/java/cn/hutool/core/stream/spliterators/TakeWhileSpliterator.java new file mode 100644 index 000000000..318e33351 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/stream/spliterators/TakeWhileSpliterator.java @@ -0,0 +1,68 @@ +package cn.hutool.core.stream.spliterators; + +import java.util.Comparator; +import java.util.Spliterator; +import java.util.function.Consumer; +import java.util.function.Predicate; + +/** + * takeWhile 的 Spliterator + *

借鉴自StreamEx

+ * + * @author emptypoint + * @since 6.0.0 + */ +public class TakeWhileSpliterator implements Spliterator { + + public static TakeWhileSpliterator create(Spliterator source, Predicate predicate) { + return new TakeWhileSpliterator<>(source, predicate); + } + + private final Spliterator source; + private final Predicate predicate; + private boolean isContinue = true; + + TakeWhileSpliterator(Spliterator source, Predicate predicate) { + this.source = source; + this.predicate = predicate; + } + + @Override + public boolean tryAdvance(Consumer action) { + boolean hasNext = true; + // 如果 还可以继续 并且 流中还有元素 则继续遍历 + while (isContinue && hasNext) { + hasNext = source.tryAdvance(e -> { + if (predicate.test(e)) { + action.accept(e); + } else { + // 终止遍历剩下的元素 + isContinue = false; + } + }); + } + // 该环节已经处理完成 + return false; + } + + @Override + public Spliterator trySplit() { + return null; + } + + @Override + public long estimateSize() { + return isContinue ? source.estimateSize() : 0; + } + + @Override + public int characteristics() { + return source.characteristics() & ~(Spliterator.SIZED | Spliterator.SUBSIZED); + } + + @Override + public Comparator getComparator() { + return source.getComparator(); + } +} + diff --git a/hutool-core/src/main/java/cn/hutool/core/util/RandomUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/RandomUtil.java index c376f2b60..12364e7f8 100755 --- a/hutool-core/src/main/java/cn/hutool/core/util/RandomUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/RandomUtil.java @@ -575,11 +575,11 @@ public class RandomUtil { if (StrUtil.isEmpty(baseString)) { return StrUtil.EMPTY; } - final StringBuilder sb = new StringBuilder(length); - - if (length < 1) { + if(length < 1){ length = 1; } + + final StringBuilder sb = new StringBuilder(length); final int baseLength = baseString.length(); for (int i = 0; i < length; i++) { final int number = randomInt(baseLength); diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateTimeTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateTimeTest.java index b5f067915..8729ec3ec 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateTimeTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateTimeTest.java @@ -140,4 +140,5 @@ public class DateTimeTest { final String a = "2021-09-27 00:00:99"; new DateTime(a, DatePattern.NORM_DATETIME_FORMAT, false); } + } 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 ff3d4ace6..f1542d46a 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/OptTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/OptTest.java @@ -8,13 +8,7 @@ import lombok.NoArgsConstructor; import org.junit.Assert; import org.junit.Test; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; +import java.util.*; import java.util.stream.Stream; /** @@ -24,6 +18,12 @@ import java.util.stream.Stream; */ public class OptTest { + @Test + public void ofTest() { + Assert.assertTrue(Opt.of(Optional.empty()).isEmpty()); + Assert.assertTrue(Opt.of(Optional.of(1)).isPresent()); + } + @Test public void ofBlankAbleTest() { // ofBlankAble相对于ofNullable考虑了字符串为空串的情况 diff --git a/hutool-core/src/test/java/cn/hutool/core/stream/FastStreamTest.java b/hutool-core/src/test/java/cn/hutool/core/stream/FastStreamTest.java index 937b7378d..e08dfd2b3 100644 --- a/hutool-core/src/test/java/cn/hutool/core/stream/FastStreamTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/stream/FastStreamTest.java @@ -193,6 +193,8 @@ public class FastStreamTest { flatMapIter = FastStream.of(list).flatMapIter(e -> Arrays.asList(e, e * 10)).toList(); Assert.assertEquals(ListUtil.of(1, 10, 2, 20, 3, 30), flatMapIter); + // 不报npe测试 + Assert.assertTrue(FastStream.of(list).flatMapIter(e -> null).isEmpty()); } @Test @@ -310,6 +312,9 @@ public class FastStreamTest { List list = Arrays.asList("dromara", "hutool", "sweet"); List zip = FastStream.of(orders).zip(list, (e1, e2) -> e1 + "." + e2).toList(); Assert.assertEquals(Arrays.asList("1.dromara", "2.hutool", "3.sweet"), zip); + + zip = FastStream.iterate(1, i -> i + 1).zip(list, (e1, e2) -> e1 + "." + e2).toList(); + Assert.assertEquals(Arrays.asList("1.dromara", "2.hutool", "3.sweet"), zip); } @Test @@ -388,4 +393,9 @@ public class FastStreamTest { Assert.assertEquals(Arrays.asList(5, 7, 9), res2); } + @Test + public void testIsNotEmpty() { + Assert.assertTrue(FastStream.of(1).isNotEmpty()); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/RandomUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/RandomUtilTest.java index 25c4c67a0..1fc845ee9 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/RandomUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/RandomUtilTest.java @@ -75,6 +75,12 @@ public class RandomUtilTest { } } + @Test + public void randomStringOfLengthTest(){ + final String s = RandomUtil.randomString("123", -1); + Assert.assertNotNull(s); + } + @Test public void generateRandomNumberTest(){ final int[] ints = RandomUtil.randomPickInts(5, NumberUtil.range(5, 20)); diff --git a/hutool-db/src/test/java/cn/hutool/db/CRUDTest.java b/hutool-db/src/test/java/cn/hutool/db/CRUDTest.java index 0e99f1dda..2005ca077 100644 --- a/hutool-db/src/test/java/cn/hutool/db/CRUDTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/CRUDTest.java @@ -195,4 +195,10 @@ public class CRUDTest { MapUtil.of("ids", new int[]{1, 2, 3})); Assert.assertEquals(2, results.size()); } + + @Test + @Ignore + public void findWithDotTest(){ + db.find(Entity.of("user").set("WTUR.Other.Rg.S.WTName", "value")); + } } diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2507Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2507Test.java new file mode 100755 index 000000000..5101aec1b --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2507Test.java @@ -0,0 +1,17 @@ +package cn.hutool.json; + +import cn.hutool.core.lang.Console; +import org.junit.Ignore; +import org.junit.Test; + +public class Issue2507Test { + + @Test + @Ignore + public void xmlToJsonTest(){ + String xml = " 低盐饮食[嘱托]]>]]> 流质饮食]]> "; + JSONObject jsonObject = JSONUtil.xmlToJson(xml); + + Console.log(jsonObject.toStringPretty()); + } +}