All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.camunda.tasklist.util.Either Maven / Gradle / Ivy

There is a newer version: 8.7.0-alpha2
Show newest version
/*
 * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH under
 * one or more contributor license agreements. See the NOTICE file distributed
 * with this work for additional information regarding copyright ownership.
 * Licensed under the Camunda License 1.0. You may not use this file
 * except in compliance with the Camunda License 1.0.
 */
package io.camunda.tasklist.util;

import java.util.ArrayList;
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.stream.Collector;

public interface Either {

  /**
   * Returns a {@link Right} describing the given value.
   *
   * @param right the value to describe
   * @param  the type of the left value
   * @param  the type of the right value
   * @return a {@link Right} of the value
   */
  static  Either right(final R right) {
    return new Right<>(right);
  }

  /**
   * Returns a {@link Left} describing the given value.
   *
   * @param left the value to describe
   * @param  the type of the left value
   * @param  the type of the right value
   * @return a {@link Left} of the value
   */
  static  Either left(final L left) {
    return new Left<>(left);
  }

  /**
   * Convenience method to convert an {@link Optional} of R to an {@code Either}, using an
   * intermediary representation of the Optional in the form of {@link EitherOptional}.
   *
   * 

Example: * *

{@code
   * Either.ofOptional(Optional.of(1))
   *   .orElse("left value")
   *   .ifRightOrLeft(
   *     right -> System.out.println("If Optional is present, right is the contained value"),
   *     left -> System.out.println("If Optional is empty, left is the value provided by orElse")
   *   );
   * }
* * @param right The optional that may contain the right value * @param the type of the right value * @return An intermediary representation {@link EitherOptional} */ @SuppressWarnings("OptionalUsedAsFieldOrParameterType") static EitherOptional ofOptional(final Optional right) { return new EitherOptional<>(right); } /** * Returns a collector for {@code Either} that collects them into {@code * Either,List} and favors {@link Left} over {@link Right}. * *

This is commonly used to collect a stream of either objects where a right is considered a * success and a left is considered an error. If any error has occurred, we're often only * interested in the errors and not in the successes. Otherwise, we'd like to collect all success * values. * *

This collector groups all the lefts into one {@link List} and all the rights into another. * When all elements of the stream have been collected and any lefts were encountered, it outputs * a left of the list of encountered left values. Otherwise, it outputs a right of all right * values it encountered. * *

Examples: * *

{@code
   * Stream.of(Either.right(1), Either.right(2), Either.right(3))
   *   .collect(Either.collector()) // => a Right
   *   .get(); // => List.of(1,2,3)
   *
   * Stream.of(Either.right(1), Either.left("oops"), Either.right(3))
   *   .collect(Either.collector()) // => a Left
   *   .getLeft(); // => List.of("oops")
   * }
* * @param the type of the left values * @param the type of the right values * @return a collector that favors left over right */ static Collector, Tuple, List>, Either, List>> collector() { return Collector.of( () -> new Tuple<>(new ArrayList<>(), new ArrayList<>()), (acc, next) -> next.ifRightOrLeft(right -> acc.getRight().add(right), left -> acc.getLeft().add(left)), (a, b) -> { a.getLeft().addAll(b.getLeft()); a.getRight().addAll(b.getRight()); return a; }, acc -> { if (!acc.getLeft().isEmpty()) { return left(acc.getLeft()); } else { return right(acc.getRight()); } }); } /** * Returns a collector for {@code Either} that collects them into {@code Either} * and favors {@link Left} over {@link Right}. While collecting the rights, it folds the left into * the first encountered. * *

This is commonly used to collect a stream of either objects where a right is considered a * success and a left is considered an error. If any error has occurred, we're often only * interested in the first error and not in the successes. Otherwise, we'd like to collect all * success values. * *

This collector looks for a left while it groups all the rights into one {@link List}. When * all elements of the stream have been collected and a left was encountered, it outputs the * encountered left. Otherwise, it outputs a right of all right values it encountered. * *

Examples: * *

{@code
   * * Stream.of(Either.right(1), Either.right(2), Either.right(3))
   * *   .collect(Either.collector()) // => a Right
   * *   .get(); // => List.of(1,2,3)
   * *
   * * Stream.of(Either.right(1), Either.left("oops"), Either.left("another oops"))
   * *   .collect(Either.collector()) // => a Left
   * *   .getLeft(); // => "oops"
   * *
   * }
* * @param the type of the left values * @param the type of the right values * @return a collector that favors left over right */ static Collector, Tuple, List>, Either>> collectorFoldingLeft() { return Collector.of( () -> new Tuple<>(Optional.empty(), new ArrayList<>()), (acc, next) -> next.ifRightOrLeft( right -> acc.getRight().add(right), left -> acc.setLeft(acc.getLeft().or(() -> Optional.of(left)))), (a, b) -> { if (a.getLeft().isEmpty() && b.getLeft().isPresent()) { a.setLeft(b.getLeft()); } a.getRight().addAll(b.getRight()); return a; }, acc -> acc.getLeft().map(Either::>left).orElse(Either.right(acc.getRight()))); } /** * Returns true if this Either is a {@link Right}. * * @return true if right, false if left */ boolean isRight(); /** * Returns true if this Either is a {@link Left}. * * @return true if left, false if right */ boolean isLeft(); /** * Returns the right value, if this is a {@link Right}. * * @return the right value * @throws NoSuchElementException if this is a {@link Left} */ R get(); /** * Returns the right value, or a default value if this is a {@link Left}. * * @param defaultValue the default value * @return the right value, or the default value if this is a {@link Left} */ R getOrElse(R defaultValue); /** * Returns the left value, if this is a {@link Left}. * * @return the left value * @throws NoSuchElementException if this is a {@link Right} */ L getLeft(); /** * Maps the right value, if this is a {@link Right}. * * @param right the mapping function for the right value * @param the type of the resulting right value * @return a mapped {@link Right} or the same {@link Left} */ Either map(Function right); /** * Maps the left value, if this is a {@link Left}. * * @param left the mapping function for the left value * @param the type of the resulting left value * @return a mapped {@link Left} or the same {@link Right} */ Either mapLeft(Function left); /** * Flatmaps the right value into a new Either, if this is a {@link Right}. * *

A common use case is to map a right value to a new right, unless some error occurs in which * case the value can be mapped to a new left. Note that this flatMap does not allow to alter the * type of the left side. Example: * *

{@code
   * Either.right(0) // => Right(0)
   *   .flatMap(x -> Either.right(x + 1)) // => Right(1)
   *   .flatMap(x -> Either.left("an error occurred")) // => Left("an error occurred")
   *   .getLeft(); // => "an error occurred"
   * }
* * @param right the flatmapping function for the right value * @param the type of the right side of the resulting either * @return either a mapped {@link Right} or a new {@link Left} if this is a right; otherwise the * same left, but cast to consider the new type of the right. */ Either flatMap(Function> right); /** * Performs the given action with the value if this is a {@link Right}, otherwise does nothing. * * @param action the consuming function for the right value */ void ifRight(Consumer action); /** * Performs the given action with the value if this is a {@link Left}, otherwise does nothing. * * @param action the consuming function for the left value */ void ifLeft(Consumer action); /** * Performs the given right action with the value if this is a {@link Right}, otherwise performs * the given left action with the value. * * @param rightAction the consuming function for the right value * @param leftAction the consuming function for the left value */ void ifRightOrLeft(Consumer rightAction, Consumer leftAction); /** * A right for either a left or right. By convention, right is used for success and left for * error. * * @param The left type * @param The right type */ @SuppressWarnings("java:S2972") final class Right implements Either { private final R value; private Right(final R value) { this.value = value; } @Override public boolean isRight() { return true; } @Override public boolean isLeft() { return false; } @Override public R get() { return value; } @Override public R getOrElse(final R defaultValue) { return value; } @Override public L getLeft() { throw new NoSuchElementException("Expected a left, but this is right"); } @Override public Either map(final Function right) { return Either.right(right.apply(value)); } @Override @SuppressWarnings("unchecked") public Either mapLeft(final Function left) { return (Either) this; } @Override public Either flatMap(final Function> right) { return right.apply(value); } @Override public void ifRight(final Consumer right) { right.accept(value); } @Override public void ifLeft(final Consumer action) { // do nothing } @Override public void ifRightOrLeft(final Consumer rightAction, final Consumer leftAction) { rightAction.accept(value); } @Override public int hashCode() { return Objects.hash(value); } @Override public boolean equals(final Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } final Right right = (Right) o; return Objects.equals(value, right.value); } @Override public String toString() { return "Right[" + value + ']'; } } /** * A left for either a left or right. By convention, right is used for success and left for error. * * @param The left type * @param The right type */ @SuppressWarnings("java:S2972") final class Left implements Either { private final L value; private Left(final L value) { this.value = value; } @Override public boolean isRight() { return false; } @Override public boolean isLeft() { return true; } @Override public R get() { throw new NoSuchElementException("Expected a right, but this is left"); } @Override public R getOrElse(final R defaultValue) { return defaultValue; } @Override public L getLeft() { return value; } @Override @SuppressWarnings("unchecked") public Either map(final Function right) { return (Either) this; } @Override public Either mapLeft(final Function left) { return Either.left(left.apply(value)); } @Override @SuppressWarnings("unchecked") public Either flatMap(final Function> right) { return (Either) this; } @Override public void ifRight(final Consumer right) { // do nothing } @Override public void ifLeft(final Consumer action) { action.accept(value); } @Override public void ifRightOrLeft(final Consumer rightAction, final Consumer leftAction) { leftAction.accept(value); } @Override public int hashCode() { return Objects.hash(value); } @Override public boolean equals(final Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } final Left left = (Left) o; return Objects.equals(value, left.value); } @Override public String toString() { return "Left[" + value + ']'; } } @SuppressWarnings("OptionalUsedAsFieldOrParameterType") final class EitherOptional { private final Optional right; private EitherOptional(final Optional right) { this.right = right; } public Either orElse(final L left) { return right.>map(Either::right).orElse(Either.left(left)); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy