package com.zenika.util; import java.util.*; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collector; import java.util.stream.Collectors; /** * A instance of a Try represents an attempt to compute a value. A Try is either a success, either a failure. * @param <E> */ public abstract class Try<E> { /** * Enumerate the different types of a Try */ public enum Type { SUCCESS, FAILURE } /** * Convert the given ThrowingFunction<I, O> instance as a Function<I, Try<O>> instance. * @param function * @param <I> * @param <O> * @return */ public static <I, O> Function<I, Try<O>> of(ThrowingFunction<I, O> function) { return input -> { try { O result = function.apply(input); return new Success<>(result); } catch (RuntimeException e) { throw e; // we don't want to wrap runtime exceptions } catch (Exception e) { return new Failure<>(e); } }; } /** * Convert the given ThrowingFunction<I, O> instance as a Function<I, Supplier<Try<O>>> instance. * @param function * @param <I> * @param <O> * @return */ public static <I, O> Function<I, Supplier<Try<O>>> lazyOf(ThrowingFunction<I, O> function) { Function<I, Try<O>> of = of(function); return input -> { return () -> of.apply(input); }; } /** * Return a map collector partitioning tries by type (Type.SUCCESS / Type.FAILURE). * @param <E> * @return */ public static <E> Collector<Try<E>, ?, Map<Type, List<Try<E>>>> groupingBySuccess() { return Collectors.groupingBy(t -> t.getType()); } /** * A collector for consuming stream of Try suppliers until all values have been retrieved or a failure has been detected. * If all values are success, return the list wrapped in an instance of Success * Otherwise return the first failure detected. * @param <E> * @return */ public static <E> TryCollector<E> collect() { return new TryCollector<>(); } /** * Return the type associated to the current instance. * @return an reference to Type.SUCCESS or Type.FAILURE */ public abstract Type getType(); /** * Return true if the current instance represents a success. * @return */ public boolean isSuccess() { return getType() == Type.SUCCESS; } /** * Return true if the current instance represents a failure. * @return */ public boolean isFailure() { return !isSuccess(); } /** * If the current instance represents a success, transform its result and return it as a Try. * Otherwise return the current instance. * @param mapper the function used to transform the result * @param <F> * @return */ public <F> Try<F> map(Function<E, F> mapper) { return flatMap(of(result -> mapper.apply(result))); } /** * If the current instance represents a success, transform its result and return it as a Try. * Otherwise return the current instance. * @param mapper the function used to transform the result * @param <O> * @return */ public <O> Try<O> flatMap(Function<E, Try<O>> mapper) { return isSuccess() ? mapper.apply(asSuccess().getResult()) : (Try<O>) this; } /** * If the current instance represents a success, wrap its result as an option. * Otherwise return Optional.empty(). * @return */ public Optional<E> toOption() { return isSuccess() ? Optional.ofNullable(asSuccess().getResult()) : Optional.<E>empty(); } /** * If the current instance represents a success, return the wrapped value. * Otherwise throw the wrapped exception. * @return * @throws Exception */ public E getOrThrow() throws Exception { if(isSuccess()) { return asSuccess().getResult(); } else { throw asFailure().getException(); } } /** * Execute the given consumer if the current instance represents a success * @param consumer */ public void ifPresent(Consumer<E> consumer) { if(isSuccess()) { consumer.accept(asSuccess().getResult()); } } /** * Force this try as an instance of Success * @return */ public Success<E> asSuccess() { return (Success<E>) this; } /** * Force this try as an instance of Failure * @return */ public Failure<E> asFailure() { return (Failure<E>) this; } }