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

no.finn.lambdacompanion.Either Maven / Gradle / Ivy

There is a newer version: 0.27
Show newest version
package no.finn.lambdacompanion;

import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

/**
 * Represents a value of one of two possible types (a disjoint union.) Instances of Either are either an instance of Left or Right.
 * A common use of Either is as an alternative to Optional for dealing with possible missing values. In this usage, Optional.Absent is
 * replaced with a Left which can contain useful information. Right takes the place of Optional.Present. Convention dictates that Left is
 * used for failure and Right is used for success.
 *
 * @param  type of the left side
 * @param  type of the right side
 */
public abstract class Either {

    /**
     * @return true if this is a Left, false otherwise.
     */
    public abstract boolean isLeft();

    /**
     * @return true if this is a Right, false otherwise.
     */
    public boolean isRight() {
        return !isLeft();
    }

    public LeftProjection left() {
        return new LeftProjection<>(this);
    }

    public RightProjection right() {
        return new RightProjection<>(this);
    }

    /**
     * Returns true if this and object are both either a Left or a Right, and both their
     * containing values are equal as determined by their equal methods.
     * Note that instances of differing parameterized types can be equal.
     */
    @Override
    public abstract boolean equals(final Object object);

    @Override
    public abstract int hashCode();

    @Override
    public abstract String toString();

    /**
     * Applies leftMapper if this is a Left or rightMapper if this is a Right.
     *
     * @param leftMapper  the function to apply if this is a Left
     * @param rightMapper the function to apply if this is a Right
     * @param          the resulting type of applying the function
     * @return the results of applying the function
     */
    public abstract  X fold(final Function leftMapper, final Function rightMapper);

    /**
     * Joins an Either through Left.
     * This method requires that the left side of this Either is itself an Either type. That is, this must be some type like:
     * Either[Either[X, R], R]
     * (which respects the type parameter bounds, shown below.)
     * If this instance is a Left[Either[X, R]] then the contained Either[X, R] will be returned, otherwise this value will be returned
     * unmodified.
     *
     * @param leftJoiner the function to apply if this is a Left
     * @param         the resulting type of applying the function
     * @return the results of applying the function
     */
    public abstract  Either joinLeft(final Function> leftJoiner);

    /**
     * Joins an Either through Right.
     * This method requires that the right side of this Either is itself an Either type. That is, this must be some type like:
     * Either[L, Either[L, X]]
     * (which respects the type parameter bounds, shown below.)
     * If this instance is a Right[Either[L, X]] then the contained Either[L, X] will be returned, otherwise this value will be returned
     * unmodified.
     *
     * @param rightJoiner the function to apply if this is a Right
     * @param          the resulting type of applying the function
     * @return the results of applying the function
     */
    public abstract  Either joinRight(final Function> rightJoiner);

    /**
     * @param value the value for the left side
     * @param    the type of the left side of the resulting Left[L,R]
     * @param    the type of the right side of the resulting Left[L,R]
     * @return a Left of the given value
     */
    public static  Either left(final L value) {
        return new Left<>(value);
    }

    /**
     * @param value the value for the right side
     * @param    the type of the left side of the resulting Right[L,R]
     * @param    the type of the right side of the resulting Right[L,R]
     * @return a Right of the given value
     */
    public static  Either right(final R value) {
        return new Right<>(value);
    }

    /**
     * Projects an Either into a Left.
     *
     * @param  the type of the left side of the resulting Right[L,R]
     * @param  the type of the right side of the resulting Right[L,R]
     */
    public static final class LeftProjection {

        private final Either either;

        LeftProjection(final Either either) {
            this.either = either;
        }

        /**
         * The given function is applied if this is a Left.
         *
         * @param mapper the function to apply if this is a Left
         * @param     the resulting type of applying the function
         * @return the results of applying the function
         */
        public  Either map(final Function mapper) {
            return either.joinLeft(left -> Either.left(mapper.apply(left)));
        }

        /**
         * @param predicate the predicate
         * @return Optional.Absent if this is a Right or if the given predicate p does not hold for the left value, otherwise, returns a Left.
         */
        public Optional> filter(final Predicate predicate) {
            return either.fold(left -> predicate.test(left) ? Optional.of(either) : Optional.>empty(),
                               right -> Optional.>empty());
        }

        /**
         * Executes the given side-effecting function if this is a Left and returns this Either
         *
         * @param consumer the consumer
         * @return the Either within this projection
         */
        public Either peek(final Consumer consumer) {
            forEach(consumer);
            return either;
        }

        /**
         * Executes the given side-effecting function if this is a Left
         *
         * @param consumer the consumer
         */
        public void forEach(final Consumer consumer) {
            toOptional().ifPresent(consumer);
        }

        /**
         * @return an Optional containing the Left value if it exists or an Optional.Absent if this is a Right.
         */
        public Optional toOptional() {
            return either.fold(Optional::ofNullable, right -> Optional.empty());
        }

        /**
         * @param other other
         * @return the value from this Left or the given argument if this is a Right.
         */
        public L orElse(final L other) {
            return either.fold(Function.identity(), right -> other);
        }

        /**
         * @param supplier supplier
         * @return the value from this Left or the supplied argument if this is a Right.
         */
        public L orElseGet(final Supplier supplier) {
            return either.fold(Function.identity(), right -> supplier.get());
        }

        /**
         * @param                Type of the exception to be thrown
         * @param exceptionMapper The function which will return the exception to be thrown
         * @return the value from this Left or throw an exception to be created by the provided function if this is a Right.
         * @throws X                    if this is a Right
         * @throws NullPointerException if no value is present and {@code exceptionSupplier} is null
         */
        public  L orElseThrow(Function exceptionMapper) throws X {
            return either.left().toOptional().orElseThrow(() -> exceptionMapper.apply(either.right().toOptional().get()));
        }

    }

    /**
     * Projects an Either into a Right.
     *
     * @param  the type of the left side of the resulting Right[L,R]
     * @param  the type of the right side of the resulting Right[L,R]
     */
    public static final class RightProjection {

        private final Either either;

        RightProjection(final Either either) {
            this.either = either;
        }

        /**
         * The given function is applied if this is a Right.
         *
         * @param mapper the function to apply if this is a Right
         * @param     the resulting type of applying the function
         * @return the results of applying the function
         */
        public  Either map(final Function mapper) {
            return either.joinRight(right -> Either.right(mapper.apply(right)));
        }

        /**
         * @param predicate predicate
         * @return Optional.Absent if this is a Left or if the given predicate p does not hold for the right value, otherwise, returns a Right.
         */
        public Optional> filter(final Predicate predicate) {
            return either.fold(left -> Optional.>empty(),
                               right -> predicate.test(right) ? Optional.of(either) : Optional.>empty());
        }

        /**
         * Executes the given side-effecting function if this is a Right and returns this Either
         *
         * @param consumer consumer
         * @return the Either within this projection
         */
        public Either peek(final Consumer consumer) {
            forEach(consumer);
            return either;
        }

        /**
         * Executes the given side-effecting function if this is a Right
         *
         * @param consumer consumer
         */
        public void forEach(final Consumer consumer) {
            toOptional().ifPresent(consumer);
        }

        /**
         * @return an Optional containing the Right value if it exists or an Optional.Absent if this is a Left.
         */
        public Optional toOptional() {
            return either.fold(left -> Optional.empty(), Optional::ofNullable);
        }

        /**
         * @param other other
         * @return the value from this Right or the given argument if this is a Left.
         */
        public R orElse(final R other) {
            return either.fold(left -> other, Function.identity());
        }

        /**
         * @param supplier supplier
         * @return the value from this Right or the supplied argument if this is a Left.
         */
        public R orElseGet(final Supplier supplier) {
            return either.fold(left -> supplier.get(), Function.identity());
        }

        /**
         * @param                Type of the exception to be thrown
         * @param exceptionMapper The function which will return the exception to be thrown
         * @return the value from this Right or throw an exception to be created by the provided function if this is a Left.
         * @throws X                    if this is a left
         * @throws NullPointerException if no value is present and {@code exceptionSupplier} is null
         */
        public  R orElseThrow(Function exceptionMapper) throws X {
            return either.right().toOptional().orElseThrow(() -> exceptionMapper.apply(either.left().toOptional().get()));
        }
    }

    /**
     * The left side of the disjoint union, as opposed to the Right side.
     *
     * @param  type of the left side
     * @param  type of the right side
     */
    private static final class Left extends Either {

        private final L value;

        Left(final L value) {
            this.value = value;
        }

        @Override
        public boolean isLeft() {
            return true;
        }

        @Override
        public  X fold(final Function leftMapper, final Function rightMapper) {
            return leftMapper.apply(value);
        }

        @Override
        public  Either joinLeft(final Function> leftJoiner) {
            return leftJoiner.apply(value);
        }

        @Override
        public  Either joinRight(final Function> rightJoiner) {
            return Either.left(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;
            if (value != null ? !value.equals(left.value) : left.value != null) {
                return false;
            }
            return true;
        }

        @Override
        public int hashCode() {
            return value != null ? value.hashCode() : 0;
        }

        @Override
        public String toString() {
            return "Left(" + value + ")";
        }

    }

    /**
     * The right side of the disjoint union, as opposed to the Left side.
     *
     * @param  type of the left side
     * @param  type of the right side
     */
    private static final class Right extends Either {

        private final R value;

        @Override
        public boolean isLeft() {
            return false;
        }

        @Override
        public  X fold(final Function leftMapper, final Function rightMapper) {
            return rightMapper.apply(value);
        }

        @Override
        public  Either joinLeft(final Function> leftJoiner) {
            return Either.right(value);
        }

        @Override
        public  Either joinRight(final Function> rightJoiner) {
            return rightJoiner.apply(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;
            if (value != null ? !value.equals(right.value) : right.value != null) {
                return false;
            }
            return true;
        }

        @Override
        public int hashCode() {
            return value != null ? value.hashCode() : 0;
        }

        @Override
        public String toString() {
            return "Right(" + value + ")";
        }

        Right(final R value) {
            this.value = value;
        }

    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy