From 815fbbc2b83bbfaff93ada2b73c9dbb6a6edfd33 Mon Sep 17 00:00:00 2001 From: "z.s.d." Date: Thu, 21 Dec 2023 17:01:27 -0500 Subject: [PATCH] Collectors completed, including testing and docs --- .../collectors/EitherCollectors.java | 86 +++++++++++--- .../collectors/ResultCollectors.java | 93 ++++++++++++--- .../dichotomy/collectors/package-info.java | 4 + .../collectors/EitherCollectorsTest.java | 108 ++++++++++++++++++ .../collectors/ResultCollectorsTest.java | 89 ++++++++++----- 5 files changed, 318 insertions(+), 62 deletions(-) create mode 100644 src/main/java/net/xyzsd/dichotomy/collectors/package-info.java create mode 100644 src/test/java/net/xyzsd/dichotomy/collectors/EitherCollectorsTest.java diff --git a/src/main/java/net/xyzsd/dichotomy/collectors/EitherCollectors.java b/src/main/java/net/xyzsd/dichotomy/collectors/EitherCollectors.java index c0724eb..98bc149 100644 --- a/src/main/java/net/xyzsd/dichotomy/collectors/EitherCollectors.java +++ b/src/main/java/net/xyzsd/dichotomy/collectors/EitherCollectors.java @@ -2,57 +2,115 @@ import net.xyzsd.dichotomy.Conversion; import net.xyzsd.dichotomy.Either; -import net.xyzsd.dichotomy.Result; +import org.jetbrains.annotations.NotNull; import java.util.List; import java.util.Objects; import java.util.stream.Collector; - +/** + * Collectors for {@link Either}s. + *

+ * Three collectors are provided. The default collector ({@link #collector()} + * is left-biased. However, both right-biased and a collector which will return + * all left and right values encountered are included. + *

+ *

+ * A left-biased collector will only return {@link net.xyzsd.dichotomy.Either.Right} values + * if no {@link net.xyzsd.dichotomy.Either.Left} values have been encountered, and at least + * one Right value was found. Otherwise, + * all encountered {@link net.xyzsd.dichotomy.Either.Left} values will be returned. + *

+ */ public interface EitherCollectors { + /** + * A left-biased collector of Eithers. + *

+ * This will return right values iff there are no left values, and + * at least one right value is present. + *

+ * @return An Either containing a List of Left or Right values, as above. + * @param Left type + * @param Right type + */ static Collector, ?, Either, List>> collector() { return Collector., Accumulator, Either, List>>of( Accumulator::new, EitherCollectors::add, Accumulator::append, - accum -> Conversion.toEither(accum.finishBiasErr()) + accum -> Conversion.toEither( accum.finishBiasErr() ) ); } + /** + * A right-biased collector of Eithers. + *

+ * This will return Left values iff there are no right values + * and at least a single left value is present. + *

+ * @return An Either containing a List of Left or Right values, as above. + * @param Left type + * @param Right type + */ static Collector, ?, Either, List>> rightBiasedCollector() { return Collector., Accumulator, Either, List>>of( Accumulator::new, EitherCollectors::add, Accumulator::append, - accum -> Conversion.toEither(accum.finishBiasOK()) + accum -> Conversion.toEither( accum.finishBiasOK() ) ); } - static Collector, ?, Both> both() { - return Collector., Accumulator, Both>of( + /** + * An unbiased collector of Eithers. + *

+ * This will return both Left and Right values + *

+ * @return a tuple containing both Left and Right values. + * @param Left type + * @param Right type + */ + static Collector, ?, LeftsAndRights> both() { + return Collector., Accumulator, LeftsAndRights>of( Accumulator::new, EitherCollectors::add, Accumulator::append, - accum -> new Both<>( accum.errList, accum.okList ) + accum -> new LeftsAndRights<>( accum.errList, accum.okList ) ); } - static private void add(Accumulator listBox, Either either) { + + static private void add(Accumulator listBox, Either either) { switch (either) { case Either.Left left -> listBox.errList.add( left.value() ); case Either.Right right -> listBox.okList.add( right.value() ); } } - record Both(List errs, List oks) { + /** + * Tuple containing lists of Left and Right values + *

+ * Contained lists are immutable and never null, but may be empty. + *

+ * @param lefts Left values + * @param rights Right values + * @param Left type + * @param Right type + */ + record LeftsAndRights(@NotNull List lefts, @NotNull List rights) { - public Both { - Objects.requireNonNull( oks ); - Objects.requireNonNull( errs ); - oks = List.copyOf( oks ); - errs = List.copyOf( errs); + /** + * Create a LeftAndRights + * @param lefts left values + * @param rights right values + */ + public LeftsAndRights { + Objects.requireNonNull( lefts ); + Objects.requireNonNull( rights ); + lefts = List.copyOf( lefts ); + rights = List.copyOf( rights ); } } diff --git a/src/main/java/net/xyzsd/dichotomy/collectors/ResultCollectors.java b/src/main/java/net/xyzsd/dichotomy/collectors/ResultCollectors.java index 7705d8f..1fe957d 100644 --- a/src/main/java/net/xyzsd/dichotomy/collectors/ResultCollectors.java +++ b/src/main/java/net/xyzsd/dichotomy/collectors/ResultCollectors.java @@ -2,16 +2,37 @@ import net.xyzsd.dichotomy.Result; +import org.jetbrains.annotations.NotNull; import java.util.List; -import java.util.Objects; import java.util.stream.Collector; +import static java.util.Objects.requireNonNull; + +/** + * Collectors for {@link Result}s. + *

+ * Three collectors are provided. The default collector ({@link #collector()} + * is error-biased. However, both ok-biased and a collector which will return + * all ok and error values encountered are included. + *

+ */ public interface ResultCollectors { + /** + * Default error-biased collector. + *

+ * This will return Err values, unless there are none; then OK values will be returned. + * If there are no OK values (and no Err values), an Err with an empty List will be returned. + *

+ * + * @param OK type + * @param Err type + * @return A Result containing a List of OK or Err values as described above. + */ static Collector, ?, Result, List>> collector() { - return Collector., Accumulator, Result, List>>of( + return Collector., Accumulator, Result, List>>of( Accumulator::new, ResultCollectors::add, Accumulator::append, @@ -19,8 +40,19 @@ public interface ResultCollectors { ); } + /** + * OK-biased collector. + *

+ * This will return OK values, unless there are none; then Err values will be returned. + * If there are no Err values (and no OK values), an OK with an empty List will be returned. + *

+ * + * @param OK type + * @param Err type + * @return A Result containing a List of OK or Err values as described above. + */ static Collector, ?, Result, List>> okBiasedCollector() { - return Collector., Accumulator, Result, List>>of( + return Collector., Accumulator, Result, List>>of( Accumulator::new, ResultCollectors::add, Accumulator::append, @@ -28,35 +60,58 @@ public interface ResultCollectors { ); } - static Collector, ?, Both> both() { - return Collector., Accumulator, Both>of( + /** + * Unbiased Result collector. + *

+ * This will return both OK and Err values + *

+ * + * @param OK type + * @param Err type + * @return Tuple containing OK and Err values + */ + static Collector, ?, OKsAndErrs> both() { + return Collector., Accumulator, OKsAndErrs>of( Accumulator::new, ResultCollectors::add, Accumulator::append, - accum -> new Both<>( accum.okList, accum.errList ) + accum -> new OKsAndErrs<>( accum.okList, accum.errList ) ); } - - static private void add(Accumulator listBox, Result result) { + static private void add(Accumulator listBox, Result result) { switch (result) { - case Result.OK ok -> listBox.okList.add( ok.get() ); - case Result.Err err -> listBox.errList.add( err.get() ); - } + case Result.OK ok -> listBox.okList.add( ok.get() ); + case Result.Err err -> listBox.errList.add( err.get() ); + } } - - - record Both(List oks, List errs) { - - public Both { - Objects.requireNonNull( oks ); - Objects.requireNonNull( errs ); + /** + * Tuple containing lists of OK and Err values + *

+ * Contained lists are immutable and never null, but may be empty. + *

+ * + * @param oks OK values + * @param errs Err values + * @param OK type + * @param Err type + */ + record OKsAndErrs(@NotNull List oks, @NotNull List errs) { + + /** + * Create a OKsAndErrs + * @param oks OK values + * @param errs Err values + */ + public OKsAndErrs { + requireNonNull( oks ); + requireNonNull( errs ); oks = List.copyOf( oks ); - errs = List.copyOf( errs); + errs = List.copyOf( errs ); } } diff --git a/src/main/java/net/xyzsd/dichotomy/collectors/package-info.java b/src/main/java/net/xyzsd/dichotomy/collectors/package-info.java new file mode 100644 index 0000000..ddbc278 --- /dev/null +++ b/src/main/java/net/xyzsd/dichotomy/collectors/package-info.java @@ -0,0 +1,4 @@ +/** + * Customized Stream Collectors for Either and Result types. + */ +package net.xyzsd.dichotomy.collectors; \ No newline at end of file diff --git a/src/test/java/net/xyzsd/dichotomy/collectors/EitherCollectorsTest.java b/src/test/java/net/xyzsd/dichotomy/collectors/EitherCollectorsTest.java new file mode 100644 index 0000000..cf5a1b9 --- /dev/null +++ b/src/test/java/net/xyzsd/dichotomy/collectors/EitherCollectorsTest.java @@ -0,0 +1,108 @@ +package net.xyzsd.dichotomy.collectors; + +import net.xyzsd.dichotomy.Either; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class EitherCollectorsTest { + + + List> RESLIST = List.of( + Either.ofRight("First"), + Either.ofRight("Second"), + Either.ofLeft(-3), + Either.ofRight("Fourth"), + Either.ofRight("Fifth"), + Either.ofLeft( -6) + ); + + List> RESLIST_EMPTY = List.of(); + List> RESLIST_ONLY_SUCCESS = List.of( + Either.ofRight("First"), + Either.ofRight("Second") + ); + + List> RESLIST_ONLY_FAIL = List.of( + Either.ofLeft(-111), + Either.ofLeft(-222) + ); + + + + @Test + void collector() { + assertEquals( + Either.ofLeft( List.of(-3, -6) ), + RESLIST.stream().collect( EitherCollectors.collector() ) + ); + + assertEquals( + Either.ofRight(List.of("First","Second")), + RESLIST_ONLY_SUCCESS.stream().collect( EitherCollectors.collector() ) + ); + + assertEquals( + Either.ofLeft(List.of(-111,-222)), + RESLIST_ONLY_FAIL.stream().collect( EitherCollectors.collector() ) + ); + + assertEquals( + Either.ofLeft(List.of()), + RESLIST_EMPTY.stream().collect( EitherCollectors.collector() ) + ); + } + + @Test + void rightBiasedCollector() { + assertEquals( + Either.ofRight( List.of("First", "Second", "Fourth", "Fifth") ), + RESLIST.stream().collect( EitherCollectors.rightBiasedCollector() ) + ); + + assertEquals( + Either.ofRight( List.of("First", "Second") ), + RESLIST_ONLY_SUCCESS.stream().collect( EitherCollectors.rightBiasedCollector() ) + ); + + assertEquals( + Either.ofLeft( List.of(-111,-222) ), + RESLIST_ONLY_FAIL.stream().collect( EitherCollectors.rightBiasedCollector() ) + ); + + assertEquals( + Either.ofRight( List.of() ), + RESLIST_EMPTY.stream().collect( EitherCollectors.rightBiasedCollector() ) + ); + } + + @Test + void both() { + assertEquals( + new EitherCollectors.LeftsAndRights<>( + List.of(-3, -6), + List.of("First", "Second", "Fourth", "Fifth") ), + RESLIST.stream().collect( EitherCollectors.both() ) + ); + + assertEquals( + new EitherCollectors.LeftsAndRights<>( List.of(), + List.of("First", "Second") ), + RESLIST_ONLY_SUCCESS.stream().collect( EitherCollectors.both() ) + ); + + assertEquals( + new EitherCollectors.LeftsAndRights<>( List.of(-111,-222), List.of() ), + RESLIST_ONLY_FAIL.stream().collect( EitherCollectors.both() ) + ); + + assertEquals( + new EitherCollectors.LeftsAndRights<>( List.of(),List.of() ), + RESLIST_EMPTY.stream().collect( EitherCollectors.both() ) + ); + } + + +} \ No newline at end of file diff --git a/src/test/java/net/xyzsd/dichotomy/collectors/ResultCollectorsTest.java b/src/test/java/net/xyzsd/dichotomy/collectors/ResultCollectorsTest.java index 3bc4b15..c9489b0 100644 --- a/src/test/java/net/xyzsd/dichotomy/collectors/ResultCollectorsTest.java +++ b/src/test/java/net/xyzsd/dichotomy/collectors/ResultCollectorsTest.java @@ -2,11 +2,10 @@ import net.xyzsd.dichotomy.Result; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; import java.util.List; -import static org.junit.jupiter.api.Assertions.*; - class ResultCollectorsTest { List> RESLIST = List.of( @@ -29,43 +28,75 @@ class ResultCollectorsTest { Result.ofErr(-222) ); + + @Test void collector() { - Result, List> collect = RESLIST.stream().collect( ResultCollectors.collector() ); - System.out.println(collect); - collect = RESLIST_ONLY_SUCCESS.stream().collect( ResultCollectors.collector() ); - System.out.println(collect); - collect = RESLIST_ONLY_FAIL.stream().collect( ResultCollectors.collector() ); - System.out.println(collect); - collect = RESLIST_EMPTY.stream().collect( ResultCollectors.collector() ); - System.out.println(collect); - throw new UnsupportedOperationException("TODO: *validate*"); + assertEquals( + Result.ofErr( List.of(-3, -6) ), + RESLIST.stream().collect( ResultCollectors.collector() ) + ); + + assertEquals( + Result.ofOK(List.of("First","Second")), + RESLIST_ONLY_SUCCESS.stream().collect( ResultCollectors.collector() ) + ); + + assertEquals( + Result.ofErr(List.of(-111,-222)), + RESLIST_ONLY_FAIL.stream().collect( ResultCollectors.collector() ) + ); + + assertEquals( + Result.ofErr(List.of()), + RESLIST_EMPTY.stream().collect( ResultCollectors.collector() ) + ); } @Test void okBiasedCollector() { - Result, List> collect = RESLIST.stream().collect( ResultCollectors.okBiasedCollector() ); - System.out.println(collect); - collect = RESLIST_ONLY_SUCCESS.stream().collect( ResultCollectors.okBiasedCollector() ); - System.out.println(collect); - collect = RESLIST_ONLY_FAIL.stream().collect( ResultCollectors.okBiasedCollector() ); - System.out.println(collect); - collect = RESLIST_EMPTY.stream().collect( ResultCollectors.okBiasedCollector() ); - System.out.println(collect); - throw new UnsupportedOperationException("TODO: *validate*"); + assertEquals( + Result.ofOK( List.of("First", "Second", "Fourth", "Fifth") ), + RESLIST.stream().collect( ResultCollectors.okBiasedCollector() ) + ); + + assertEquals( + Result.ofOK( List.of("First", "Second") ), + RESLIST_ONLY_SUCCESS.stream().collect( ResultCollectors.okBiasedCollector() ) + ); + + assertEquals( + Result.ofErr( List.of(-111,-222) ), + RESLIST_ONLY_FAIL.stream().collect( ResultCollectors.okBiasedCollector() ) + ); + + assertEquals( + Result.ofOK( List.of() ), + RESLIST_EMPTY.stream().collect( ResultCollectors.okBiasedCollector() ) + ); } @Test void both() { - ResultCollectors.Both collect = RESLIST.stream().collect( ResultCollectors.both() ); - System.out.println(collect); - collect = RESLIST_ONLY_SUCCESS.stream().collect( ResultCollectors.both() ); - System.out.println(collect); - collect = RESLIST_ONLY_FAIL.stream().collect( ResultCollectors.both() ); - System.out.println(collect); - collect = RESLIST_EMPTY.stream().collect( ResultCollectors.both() ); - System.out.println(collect); - throw new UnsupportedOperationException("TODO: *validate*"); + assertEquals( + new ResultCollectors.OKsAndErrs<>( List.of("First", "Second", "Fourth", "Fifth"),List.of(-3,-6) ), + RESLIST.stream().collect( ResultCollectors.both() ) + ); + + assertEquals( + new ResultCollectors.OKsAndErrs<>( List.of("First", "Second"),List.of() ), + RESLIST_ONLY_SUCCESS.stream().collect( ResultCollectors.both() ) + ); + + assertEquals( + new ResultCollectors.OKsAndErrs<>( List.of(),List.of(-111,-222) ), + RESLIST_ONLY_FAIL.stream().collect( ResultCollectors.both() ) + ); + + assertEquals( + new ResultCollectors.OKsAndErrs<>( List.of(),List.of() ), + RESLIST_EMPTY.stream().collect( ResultCollectors.both() ) + ); } } \ No newline at end of file