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

com.spencerwi.either.Result Maven / Gradle / Ivy

Go to download

A right-biased implementation of "Either a b" for Java, using Java 8 for mapping/folding and type inference.

There is a newer version: 2.9.0
Show newest version
package com.spencerwi.either;

import java.util.NoSuchElementException;
import java.util.function.Function;
import java.util.function.Consumer;

/**
 * A useful class to wrap operations that may throw exceptions and convert 
 *  exception-handling try-catch code into value-handling code, similar to
 *  "railway-oriented programming". This is especially useful when dealing
 *  with Java Stream methods, which accept the various java.util.function
 *  "functional interfaces", none of which make allowances for checked 
 *  exceptions.
 */
public abstract class Result {

	/**
	 * The "factory function" entry point for using `Result`; effectively acts
	 *  like a try-catch block. If the `resultSupplier` method throws an 
	 *  exception, an `Err` will be returned containing that exception. 
	 *  Otherwise, an `Ok` will be returned containing the return value
	 *  of `resultSupplier`.
	 * @param resultSupplier
	 * @return an `Err` if an exception was thrown; otherwise, an `Ok`
	 */
    public static  Result attempt(ExceptionThrowingSupplier resultSupplier){
        try {
            R resultValue = resultSupplier.get();
            return Result.ok(resultValue);
        } catch (Exception e){
            return Result.err(e);
        }
    }

	/**
	 * Factory method for directly creating an `Err` from an exception.
	 * @param e the exception to wrap in an `Err`
	 */
    public static  Result err(Exception e){ return new Err<>(e); }
	/**
	 * Factory method for directly creating an `Ok` from a value.
	 * @param result the result value to wrap in an `Ok`
	 */
    public static  Result ok(R result){ return new Ok<>(result); }

	/**
	 * @return the wrapped exception if this is an `Err`; otherwise, throws a NoSuchElementException (ironically).
	 * @throws NoSuchElementException if this is an `Ok` 
	 */
    public abstract Exception getException();

	/**
	 * @return the wrapped value if this is an `Ok`; otherwise, throws a NoSuchElementException.
	 * @throws NoSuchElementException if this is an `Err` 
	 */
    public abstract R getResult();

    public abstract boolean isErr();
    public abstract boolean isOk();

	/**
	 * A convenience method for taking an exception and a value and transforming
	 * both to a common type `T`, returning the result of whichever 
	 * transformation is applicable based on whether this is an `Err` or an `Ok`.
	 * @param transformException a method that takes an exception and returns a value of type `T`, useful for handling "fallback" cases.
	 * @param transformValue a method that takes the wrapped value and returns a value of type `T`
	 * @return the return value of `transformValue` if this is an `Ok`, otherwise, the return value of `transformException`
	 */
    public abstract  T fold(Function transformException, Function transformValue);
	/**
	 * Applies `transformValue` to the wrapped value and wraps the result in an 
	 * `Ok` for further operations *if* this is an `Ok`; otherwise returns 
	 * the same `Err`, but with the appropriate type so as not to disrupt a 
	 * "chain" of operations.
	 * @param transformValue
	 * @return an `Ok` containing the result of applying transformValue to the wrapped result value if this is an `Ok`, otherwise, the same `Err` but with a friendly type signature.
	 */
    public abstract  Result map(ExceptionThrowingFunction transformValue);
	/**
	 * Applies a `Result`-returning function to the wrapped value and returns 
	 * that if this is an `Ok`; otherwise returns the same `Err`, but with 
	 * the appropriate type so as not to disrupt a "chain" of operations.
	 * @param transformValue
	 * @return the return value from transformValue if this is an `Ok`, otherwise, the same `Err` but with a friendly type signature.
	 */
    public abstract  Result flatMap(ExceptionThrowingFunction> transformValue);

	/**
	 * Runs the acceptsOkValue function if this is an `Ok` instead of an `Err`,
	 * passing in the value that `Ok` wraps; otherwise, does nothing. Does not 
	 * return a value. If you need to transform a `Result`, use {@link #map}
	 * or {@link #flatMap}.
	 */
	public abstract void ifOk(Consumer acceptsOkValue);

	/**
	 * Runs the `errorHandler` function with the wrapped exception if this is an
	 * `Err`, or else runs the `okHandler` function with the wrapped value if 
	 * this is an `Ok`.  Does not return a value. If you need to extract a value 
	 * using a pair of "extractors", see {@link #fold}.
	 */
	public abstract void run(Consumer errorHandler, Consumer okHandler);

    public static class Err extends Result {
        private Exception ex;
        private Err(Exception e) {
            this.ex = e;
        }

        @Override
        public Exception getException() { return this.ex; }
        @Override
        public R getResult() { throw new NoSuchElementException("Tried to getResult from an Err"); }

        @Override
        public boolean isErr() { return true; }
        @Override
        public boolean isOk() { return false; }

        @Override
        public  T fold(Function transformException, Function transformValue) {
            return transformException.apply(this.ex);
        }

        @Override
        public  Result map(ExceptionThrowingFunction transformRight) {
            return Result.err(this.ex);
        }
        @Override
        public  Result flatMap(ExceptionThrowingFunction> transformValue) {
            return Result.err(this.ex);
        }
		@Override
		public void ifOk(Consumer acceptsOkValue) { /* no-op */ }
		@Override
		public void run(Consumer errorHandler, Consumer okHandler) {
			errorHandler.accept(this.ex);
		}

        @Override
        public int hashCode(){ return this.ex.hashCode(); }

		/**
		 * An `Err` object is equal to another object if that other object is
		 *  another `Err` instance containing an exception that is equal to this 
		 *  instance's exception.
		 * */
        @Override
        public boolean equals(Object other){
            if (other instanceof Err){
                final Err otherAsErr = (Err)other;
                return this.ex.equals(otherAsErr.ex);
            } else {
                return false;
            }
        }

    }
    public static class Ok extends Result {
        private R resultValue;
        private Ok(R value) {
            this.resultValue = value;
        }

        @Override
        public Exception getException() { throw new NoSuchElementException("Tried to getException from an Ok"); }
        @Override
        public R getResult() { return resultValue; }

        @Override
        public boolean isErr() { return false; }
        @Override
        public boolean isOk() { return true; }

        @Override
        public  T fold(Function transformException, Function transformValue) {
            return transformValue.apply(this.resultValue);
        }
        @Override
        public  Result map(ExceptionThrowingFunction transformValue) {
            return Result.attempt(() -> transformValue.apply(this.resultValue));
        }
        @Override
        public  Result flatMap(ExceptionThrowingFunction> transformValue) {
            try {
                return transformValue.apply(this.resultValue);
            } catch(Exception e) {
                return new Err(e);
            }
        }
		@Override
		public void ifOk(Consumer acceptsOkValue) {
			acceptsOkValue.accept(this.resultValue);
		}
		@Override
		public void run(Consumer errorHandler, Consumer okHandler) {
			okHandler.accept(this.resultValue);
		}

        @Override
        public int hashCode(){ return this.resultValue.hashCode(); }
		/**
		 * An `Ok` object is equal to another object if that other object is
		 *  another `Ok` instance containing a value that is equal to this 
		 *  instance's value.
		 * */
        @Override
        public boolean equals(Object other){
            if (other instanceof Ok){
                final Ok otherAsOk = (Ok)other;
                return this.resultValue.equals(otherAsOk.resultValue);
            } else {
                return false;
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy