/* * esodata - data structures and other things, of varying utility * Copyright 2022, Ben Culkin * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package bjc.data; import java.util.*; import java.util.function.*; import bjc.functypes.ID; /** * Represents a pair where only one side has a value. * * @author ben * * @param The type that could be on the left. * * @param The type that could be on the right. * */ public class Either { /** * Create a new either with the left value occupied. * * @param The type of the left value. * * @param The type of the empty right value. * * @param left The value to put on the left. * * @return An either with the left side occupied. */ public static Either left(final LeftType left) { return new Either<>(left, null); } /** * Create a new either with the right value occupied. * * @param The type of the empty left value. * * @param The type of the right value. * * @param right The value to put on the right. * * @return An either with the right side occupied. */ public static Either right(final RightType right) { return new Either<>(null, right); } /* The left value of the either. */ private LeftType leftVal; /* The right value of the either. */ private RightType rightVal; /* Whether the left value is the one filled out. */ private boolean isLeft; /* Create a new either with specifed values. */ private Either(final LeftType left, final RightType right) { if (left == null) { rightVal = right; } else { leftVal = left; isLeft = true; } } /** * Perform a mapping over this either. * * @param The new left type. * @param The new right type. * * @param leftFunc The function to apply if this is a left either. * @param rightFunc The function to apply if this is a right either. * * @return A new either, containing a value transformed by the appropriate * function. */ public Either map(Function leftFunc, Function rightFunc) { return isLeft ? left(leftFunc.apply(leftVal)) : right(rightFunc.apply(rightVal)); } /** * Extract the value from this Either. * * @param The common type to extract. * * @param leftHandler The function to handle left-values. * @param rightHandler The function to handle right-values. * * @return The result of applying the proper function. */ public Common extract(Function leftHandler, Function rightHandler) { return isLeft ? leftHandler.apply(leftVal) : rightHandler.apply(rightVal); } /** * Perform an action on this either. * * @param leftHandler The handler of left values. * @param rightHandler The handler of right values. */ public void pick(Consumer leftHandler, Consumer rightHandler) { if (isLeft) leftHandler.accept(leftVal); else rightHandler.accept(rightVal); } /** * Check if this either is left-aligned (has the left value filled, not the * right value). * * @return Whether this either is left-aligned. */ public boolean isLeft() { return isLeft; } /** * Get the left value of this either if there is one. * * @return An optional containing the left value, if there is one. */ public Optional getLeft() { return Optional.ofNullable(leftVal); } /** * Get the left value of this either, or get a {@link NoSuchElementException} if * there isn't one. * * @return The left value of this either. * * @throws NoSuchElementException If this either doesn't have a left value. */ public LeftType forceLeft() { if (isLeft) { return leftVal; } throw new NoSuchElementException("Either has no left value, is right value"); } /** * Get the right value of this either if there is one. * * @return An optional containing the right value, if there is one. */ public Optional getRight() { return Optional.ofNullable(rightVal); } /** * Get the right value of this either, or get a {@link NoSuchElementException} * if there isn't one. * * @return The right value of this either. * * @throws NoSuchElementException If this either doesn't have a right value. */ public RightType forceRight() { if (isLeft) { throw new NoSuchElementException("Either has no right value, has left value"); } return rightVal; } @SuppressWarnings("unchecked") public Either newRight() { if (isLeft) return (Either) this; throw new NoSuchElementException("Can't replace right type on right Either"); } @SuppressWarnings("unchecked") public Either newLeft() { if (isLeft) throw new NoSuchElementException("Can't replace left type on left Either"); return (Either) this; } public static T collapse(Either eth) { return eth.extract(ID.id(), ID.id()); } // Misc. overrides @Override public int hashCode() { return Objects.hash(isLeft, leftVal, rightVal); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Either other = (Either) obj; return isLeft == other.isLeft && Objects.equals(leftVal, other.leftVal) && Objects.equals(rightVal, other.rightVal); } @Override public String toString() { return String.format("Either [leftVal='%s', rightVal='%s', isLeft=%s]", leftVal, rightVal, isLeft); } }