Skip to content

Commit

Permalink
Collectors completed, including testing and docs
Browse files Browse the repository at this point in the history
  • Loading branch information
xyzsd committed Dec 21, 2023
1 parent 188e358 commit 815fbbc
Show file tree
Hide file tree
Showing 5 changed files with 318 additions and 62 deletions.
86 changes: 72 additions & 14 deletions src/main/java/net/xyzsd/dichotomy/collectors/EitherCollectors.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
*/
public interface EitherCollectors {


/**
* A left-biased collector of Eithers.
* <p>
* This will return right values iff there are no left values, and
* at least one right value is present.
* </p>
* @return An Either containing a List of Left or Right values, as above.
* @param <L> Left type
* @param <R> Right type
*/
static <L, R> Collector<Either<L, R>, ?, Either<List<L>, List<R>>> collector() {
return Collector.<Either<L, R>, Accumulator<R, L>, Either<List<L>, List<R>>>of(
Accumulator::new,
EitherCollectors::add,
Accumulator::append,
accum -> Conversion.toEither(accum.finishBiasErr())
accum -> Conversion.toEither( accum.finishBiasErr() )
);
}

/**
* A right-biased collector of Eithers.
* <p>
* This will return Left values iff there are no right values
* and at least a single left value is present.
* </p>
* @return An Either containing a List of Left or Right values, as above.
* @param <L> Left type
* @param <R> Right type
*/
static <L, R> Collector<Either<L, R>, ?, Either<List<L>, List<R>>> rightBiasedCollector() {
return Collector.<Either<L, R>, Accumulator<R, L>, Either<List<L>, List<R>>>of(
Accumulator::new,
EitherCollectors::add,
Accumulator::append,
accum -> Conversion.toEither(accum.finishBiasOK())
accum -> Conversion.toEither( accum.finishBiasOK() )
);
}

static <L, R> Collector<Either<L, R>, ?, Both<L, R>> both() {
return Collector.<Either<L, R>, Accumulator<R, L>, Both<L, R>>of(
/**
* An unbiased collector of Eithers.
* <p>
* This will return <b>both</b> Left and Right values
* </p>
* @return a tuple containing both Left and Right values.
* @param <L> Left type
* @param <R> Right type
*/
static <L, R> Collector<Either<L, R>, ?, LeftsAndRights<L, R>> both() {
return Collector.<Either<L, R>, Accumulator<R, L>, LeftsAndRights<L, R>>of(
Accumulator::new,
EitherCollectors::add,
Accumulator::append,
accum -> new Both<>( accum.errList, accum.okList )
accum -> new LeftsAndRights<>( accum.errList, accum.okList )
);
}

static private <L,R> void add(Accumulator<R,L> listBox, Either<L,R> either) {

static private <L, R> void add(Accumulator<R, L> listBox, Either<L, R> either) {
switch (either) {
case Either.Left<L, R> left -> listBox.errList.add( left.value() );
case Either.Right<L, R> right -> listBox.okList.add( right.value() );
}
}

record Both<ERR, OK>(List<ERR> errs, List<OK> oks) {
/**
* Tuple containing lists of Left and Right values
* <p>
* Contained lists are immutable and never null, but may be empty.
* </p>
* @param lefts Left values
* @param rights Right values
* @param <L> Left type
* @param <R> Right type
*/
record LeftsAndRights<L, R>(@NotNull List<L> lefts, @NotNull List<R> 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 );
}

}
Expand Down
93 changes: 74 additions & 19 deletions src/main/java/net/xyzsd/dichotomy/collectors/ResultCollectors.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,61 +2,116 @@


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.
* <p>
* 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.
* </p>
*/
public interface ResultCollectors {


/**
* Default error-biased collector.
* <p>
* 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.
* </p>
*
* @param <OK> OK type
* @param <ERR> Err type
* @return A Result containing a List of OK or Err values as described above.
*/
static <OK, ERR> Collector<Result<OK, ERR>, ?, Result<List<OK>, List<ERR>>> collector() {
return Collector.<Result<OK, ERR>, Accumulator<OK,ERR>, Result<List<OK>, List<ERR>>>of(
return Collector.<Result<OK, ERR>, Accumulator<OK, ERR>, Result<List<OK>, List<ERR>>>of(
Accumulator::new,
ResultCollectors::add,
Accumulator::append,
Accumulator::finishBiasErr
);
}

/**
* OK-biased collector.
* <p>
* 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.
* </p>
*
* @param <OK> OK type
* @param <ERR> Err type
* @return A Result containing a List of OK or Err values as described above.
*/
static <OK, ERR> Collector<Result<OK, ERR>, ?, Result<List<OK>, List<ERR>>> okBiasedCollector() {
return Collector.<Result<OK, ERR>, Accumulator<OK,ERR>, Result<List<OK>, List<ERR>>>of(
return Collector.<Result<OK, ERR>, Accumulator<OK, ERR>, Result<List<OK>, List<ERR>>>of(
Accumulator::new,
ResultCollectors::add,
Accumulator::append,
Accumulator::finishBiasOK
);
}

static <OK, ERR> Collector<Result<OK, ERR>, ?, Both<OK, ERR>> both() {
return Collector.<Result<OK, ERR>, Accumulator<OK,ERR>, Both<OK,ERR>>of(
/**
* Unbiased Result collector.
* <p>
* This will return <b>both</b> OK and Err values
* </p>
*
* @param <OK> OK type
* @param <ERR> Err type
* @return Tuple containing OK and Err values
*/
static <OK, ERR> Collector<Result<OK, ERR>, ?, OKsAndErrs<OK, ERR>> both() {
return Collector.<Result<OK, ERR>, Accumulator<OK, ERR>, OKsAndErrs<OK, ERR>>of(
Accumulator::new,
ResultCollectors::add,
Accumulator::append,
accum -> new Both<>( accum.okList, accum.errList )
accum -> new OKsAndErrs<>( accum.okList, accum.errList )

);
}



static private <OK, ERR> void add(Accumulator<OK,ERR> listBox, Result<OK,ERR> result) {
static private <OK, ERR> void add(Accumulator<OK, ERR> listBox, Result<OK, ERR> result) {
switch (result) {
case Result.OK<OK, ERR> ok -> listBox.okList.add( ok.get() );
case Result.Err<OK, ERR> err -> listBox.errList.add( err.get() );
}
case Result.OK<OK, ERR> ok -> listBox.okList.add( ok.get() );
case Result.Err<OK, ERR> err -> listBox.errList.add( err.get() );
}
}




record Both<OK, ERR>(List<OK> oks, List<ERR> errs) {

public Both {
Objects.requireNonNull( oks );
Objects.requireNonNull( errs );
/**
* Tuple containing lists of OK and Err values
* <p>
* Contained lists are immutable and never null, but may be empty.
* </p>
*
* @param oks OK values
* @param errs Err values
* @param <OK> OK type
* @param <ERR> Err type
*/
record OKsAndErrs<OK, ERR>(@NotNull List<OK> oks, @NotNull List<ERR> 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 );
}

}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/**
* Customized Stream Collectors for Either and Result types.
*/
package net.xyzsd.dichotomy.collectors;
108 changes: 108 additions & 0 deletions src/test/java/net/xyzsd/dichotomy/collectors/EitherCollectorsTest.java
Original file line number Diff line number Diff line change
@@ -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<Either<Integer,String>> RESLIST = List.of(
Either.ofRight("First"),
Either.ofRight("Second"),
Either.ofLeft(-3),
Either.ofRight("Fourth"),
Either.ofRight("Fifth"),
Either.ofLeft( -6)
);

List<Either<Integer,String>> RESLIST_EMPTY = List.of();
List<Either<Integer,String>> RESLIST_ONLY_SUCCESS = List.of(
Either.ofRight("First"),
Either.ofRight("Second")
);

List<Either<Integer,String>> 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() )
);
}


}
Loading

0 comments on commit 815fbbc

Please sign in to comment.