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

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

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

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

/**
 * Try is a right-biased datatype for wrapping function calls that might fail with an Throwable.
 *
 * A Try is alwyas Success or Failure. Success holds a value, and Failure holds an Throwable.
 *
 * Being right biased, you can map and flatMap on it successivly, delaying handling of failure to
 * the very end of your chain.
 *
 * the recover-method usually comes at the end of a call-chain and forces the handling of both
 * success and failure.
 *
 * @param  t
 */
public abstract class Try {

    /**
     * Applies a function on a value of type Success. Returns self if Failure.
     * @param mapper Function from T to U with an Throwable in the signature
     * @param  Type of the return value of the function
     * @return a new Try
     */
    public abstract  Try map(ThrowingFunction mapper);

    /**
     * Applies a function on two values. Returns Success of the resulting value, or Returns self if Failure.
     * Same as map() but needs a function ending in a Try
     * @param mapper Function from T to Try<U> with an Throwable in the signature
     * @param  Type of the value to be wrapped in a Try of the function
     * @return a new Try
     */
    public abstract  Try flatMap(ThrowingFunction, ? extends Throwable> mapper);

    /**
     * Applies a filter, where a match returns Success and Failure otherwise.
     * @param predicate Predicate function to determine Success or Failure
     * @return the same or new try
     */
    public abstract Optional> filter(Predicate predicate);

    /**
     * Accepts a consuming function and applies it to the value if it is a Success. Does nothing if Failure.
     * @param consumer Consuming function with an Throwable in the signature
     */
    public abstract void forEach(ThrowingConsumer consumer);

    /**
     * Same as forEach but returns the Try for further chaining
     * @param consumer Consuming function with an Throwable in the signature
     * @return the same Try
     */
    public abstract Try peek(ThrowingConsumer consumer);

    /**
     * Does nothing on Success, but accepts a consumer on Failure
     * @param consumer Consuming function with a failure
     * @return the same Try
     */
    public abstract Try peekFailure(Consumer> consumer);

    /**
     * Returns the value of the Success, or a default value of the same type if this is a Failure.
     * Note that the argument is always evaluated, and this is not lazy (as opposed to orElseGet)
     * @param defaultValue default fallback value
     * @return value
     */
    public abstract T orElse(T defaultValue);

    /**
     * Returns the value of the Success, or lazily falls back to a supplied value of the same type
     * @param defaultValue lazy default supplier of fallback value
     * @return value
     */
    public abstract T orElseGet(Supplier defaultValue);

    /**
     * Accepts two functions, the first applied if Success - returning the value,
     * the other executed if Failure, returning a fallback value. Note that
     * - the first function cannot have an Throwable in its signature.
     * - the fallback function must end in a value
     * @param successFunc Function handling the Success case
     * @param failureFunc Function handling the Failure case
     * @param  Type of the value
     * @return a value of type U
     */
    public abstract  U recover(Function successFunc,
                                    Function failureFunc);

    /**
     * Creates an Optional wrapping the value if Success. Creates an empty Optional if Failure.
     * @return An Optional
     */
    public abstract Optional toOptional();

    /**
     * Creates an Either where the Left is the Failure and Right is the Success from this Try
     * @param  throwable
     * @return an Either
     */
    public abstract  Either toEither();


    /**
     * Escapes the Try and enters a regular try-catch flow
     * @param throwableMapper Function to transform the Throwable if this is a Failure
     * @param  any Throwable
     * @param  any Throwable
     * @return Value or a transformed Throwable
     * @throws Y any Throwable
     */
    public abstract  T orElseThrow(Function throwableMapper) throws Y;

    /**
     * Escapes the Try and enters a regular try-catch flow by rethowing the caught exception when a Failure
     * @param  any Throwable
     * @return Value or a transformed Throwable
     * @throws E any Throwable
     */
    public abstract  T orElseRethrow() throws E;


    /**
     * Starting point to the Try structure. Create a try from a function that throws an Throwable
     * and an argument to this function
     * @param func Function to be attempted, e.g. URL::new
     * @param v Argument for the function
     * @param  Type of the function return value
     * @param  Type of the function argument
     * @return a Try
     */
    public static  Try of(ThrowingFunction func, V v) {
        try {
            return new Success<>(func.apply(v));
        } catch (Throwable e) {
            return new Failure<>(e);
        }
    }

    /**
     * Starting point to the Try structure. Create a try from a function that throws an Throwable
     * and two arguments to this function
     * @param func Function to be attempted, e.g. (a,b) -> a / b
     * @param v First argument for the function
     * @param w Second argument for the function
     * @param  Type of the function return value
     * @param  Type of the first function argument
     * @param  Type of the second function argument
     * @return a Try
     */
    public static  Try of(ThrowingBiFunction func, V v, W w) {
        try {
            return new Success<>(func.apply(v, w));
        } catch (Throwable e) {
            return new Failure<>(e);
        }
    }

    /**
     * Starting point to the Try structure. Create a try from a Supplier that throws an Throwable
     * @param supplier The supplier function
     * @param  Type of the supplied object from the supplier function
     * @return a Try
     */
    public static  Try of(ThrowingSupplier supplier) {
        try {
            return new Success<>(supplier.get());
        } catch (Throwable e) {
            return new Failure<>(e);
        }
    }

    public static  Try failure(Throwable throwable) {
        return new Failure<>(throwable);
    }

    /**
     * Creates one Try from a list of tries containing the same type, or the _first_ failure in the given list
     * @param tries List of tries
     * @param  the type
     * @return One Try containing a list of Ts
     */
    public static  Try> sequence(List> tries) {
        if (tries.size() == 0) {
            return Try.failure(new IllegalArgumentException("Cannot sequence an empty list"));
        }
        Try> head = Functions.head(tries).map(Collections::singletonList);
        if (tries.size() == 1 || !head.toOptional().isPresent()) {
            return head;
        }
        return concat(head, head.flatMap(t -> sequence(Functions.tail(tries))));
    }

    private static  Try> concat(Try> head, Try> tail) {
        return head.flatMap(l -> tail.map(k -> concat(l, k)));
    }

    private static  List concat(List l, List k) {
        ArrayList retVal = new ArrayList<>(l);
        retVal.addAll(k);
        return retVal;
    }

}