001package com.identityworksllc.iiq.common;
002
003import java.util.NoSuchElementException;
004import java.util.Objects;
005import java.util.StringJoiner;
006
007/**
008 * A functional programming concept that will contain either the left object or
009 * the right object, but never both. This can be used in a variety of functional
010 * data flows.
011 *
012 * @param <L> The left object type
013 * @param <R> The right object type
014 */
015public final class Either<L, R> {
016
017    /**
018     * Creates an Either object containing the given object on the left. The type of
019     * the right object is undefined.
020     *
021     * @param l The non-null left object
022     * @param <A> The left object type
023     * @return The Either object
024     */
025    public static <A> Either<A, ?> left(A l) {
026        if (l == null) {
027            throw new IllegalArgumentException("Input to Either.left() must not be null");
028        }
029        return new Either<>(l, null);
030    }
031
032    /**
033     * Creates an Either object containing the given object on the right. The type of
034     * the left object is undefined.
035     *
036     * @param r The non-null right object
037     * @param <B> The right object type
038     * @return The Either object
039     */
040    public static <B> Either<?, B> right(B r) {
041        if (r == null) {
042            throw new IllegalArgumentException("Input to Either.right() must not be null");
043        }
044        return new Either<>(null, r);
045    }
046
047    private final L left;
048    private final R right;
049
050    protected Either(L inputLeft, R inputRight) {
051        left = inputLeft;
052        right = inputRight;
053    }
054
055    /**
056     * @see Object#equals(Object)
057     */
058    @Override
059    public boolean equals(Object o) {
060        if (this == o) return true;
061        if (!(o instanceof Either)) return false;
062        Either<?, ?> either = (Either<?, ?>) o;
063        return Objects.equals(left, either.left) &&
064                Objects.equals(right, either.right);
065    }
066
067    /**
068     * Returns the left object or throws a {@link NoSuchElementException} if this Either contains a right object
069     * @return The left object
070     * @throws NoSuchElementException if this is a right Either
071     */
072    public L getLeft() {
073        if (left == null) {
074            throw new NoSuchElementException("left");
075        }
076        return left;
077    }
078
079    /**
080     * Returns the right object or throws a {@link NoSuchElementException} if this Either contains a left object
081     * @return The right object
082     * @throws NoSuchElementException if this is a left Either
083     */
084    public R getRight() {
085        if (right == null) {
086            throw new NoSuchElementException("right");
087        }
088        return right;
089    }
090
091    /**
092     * @return True if this Either has a left object
093     */
094    public boolean hasLeft() {
095        return left != null;
096    }
097
098    /**
099     * @return True if this Either has a right object
100     */
101    public boolean hasRight() {
102        return right != null;
103    }
104
105    /**
106     * @see Object#hashCode()
107     */
108    @Override
109    public int hashCode() {
110        return Objects.hash(left, right);
111    }
112
113    /**
114     * @see Object#toString()
115     */
116    @Override
117    public String toString() {
118        return new StringJoiner(", ", Either.class.getSimpleName() + "[", "]")
119                .add("left=" + left)
120                .add("right=" + right)
121                .toString();
122    }
123}