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

io.strati.functional.Try Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2016 WalmartLabs
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.strati.functional;

import io.strati.functional.exception.WrappedCheckedException;
import io.strati.functional.function.*;

import java.util.Collections;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author WalmartLabs
 * @author Georgi Khomeriki [[email protected]]
 */

/**
 * Implementation of the Try error-handling abstraction, based on the implementation in the Scala Standard Library
 * which in turn is based on Twitter's original implementation in com.twitter.util.
 * 

* The {@code Try} type represents a computation that may either result in an exception, or return a * successfully computed value. It's similar to, but semantically different from the {@code java.util.Optional} type. *

* Instances of {@code Try}, are either an instance of {@code Success} or {@code Failure}. *

* For example, {@code Try} can be used to perform code that might throw Exceptions, without the need to do explicit * exception-handling in all of the places that an exception might occur. *

* An important property of {@code Try} is its ability to ''pipeline'', or chain, operations, catching exceptions * along the way. For example, the {@code flatMap} and {@code map} combinators each essentially pass off either their * successfully completed value, wrapped in the `Success` type for it to be further operated * upon by the next combinator in the chain, or the exception wrapped in the {@code Failure} type usually to be simply * passed on down the chain. Combinators such as {@code recover} and {@code recoverWith} are designed to provide some * type of default behavior in the case of failure. */ public abstract class Try { /** * Factory method to wrap values in a Success. * * @param t the value to wrap * @return {@code Try} */ public static Try success(final T t) { return new Success<>(t); } /** * Factory method to wrap Throwable's in a Failure. * * @param t the throwable to wrap * @return {@code Try} */ public static Try failure(final Throwable t) { return new Failure<>(t); } /** * If the given Try is a Failure its type parameter will be cast to T, otherwise a ClassCastException will be thrown. */ public static Try failure(final Try t) { return (Failure) t; } /** * Factory method to create a Try out of a block of (possibly) throwing code. * * @param supplier supplier of the value for the Try which possibly throws * @return {@code Success} if supplier returns a value, if it throws then a {@code Failure} */ public static Try ofFailable(final TrySupplier supplier) { try { return success(supplier.get()); } catch (final Exception e) { return failure(e); } } /** * Factory method to create a Try out of a block of (possibly) throwing code. * * @param runnable action to perform which possibly throws * @return {@code Success} if runnable succeeds, if it throws then a {@code Failure} */ public static Try ofFailable(final TryRunnable runnable) { try { runnable.run(); return success((Void) null); } catch (final Exception e) { return failure(e); } } /** * Stream Collector that transforms a {@code Stream>} into a {@code Try>}. * Iff all {@code Try's} in the input stream are a {@code Success} their values will be collected and * returned as a {@code Success>}. If the input {@code Stream} contains a {@code Failure}, the output * will be a {@code Failure} as well. */ public static Collector, AtomicReference>>, Try>> listCollector() { return new TryCollector>() { @Override public Supplier>> collector() { return Collectors::toList; } }; } /** * Stream Collector that transforms a {@code Stream>} into a {@code Try>} * Iff all {@code Try's} in the input stream are a {@code Success} their values will be collected and * returned as a {@code Success>}. If the input {@code Stream} contains a {@code Failure}, the output * will be a {@code Failure} as well. */ public static Collector, AtomicReference>>, Try>> setCollector() { return new TryCollector>() { @Override public Supplier>> collector() { return Collectors::toSet; } }; } /** * @return {@code true} if the {@code Try} is a {@code Failure}, {@code false} otherwise. */ public abstract boolean isFailure(); /** * @return {@code true} if the {@code Try} is a {@code Success}, {@code false} otherwise. */ public abstract boolean isSuccess(); /** * Returns the value from this {@code Success} or the given {@code defaultValue} if this is a {@code Failure}. * Note: This will throw an exception if it is not a success and {@code defaultSupplier} throws an exception. * * @param defaultValue value to return of this is a {@code Failure} * @return T */ public abstract T getOrElse(final T defaultValue); /** * Returns this {@code Try} if it's a {@code Success} or the given {@code defaultValue} if this is a {@code Failure}. * * @param defaultTry default {@code Try} value, to be returned if this is {@code Failure} * @return {@code Try} */ public abstract Try orElse(final Try defaultTry); /** * Returns the value from this {@code Success} or throws the exception if this is a {@code Failure}. * * @return T */ public abstract T get(); /** * Applies the given {@code consumer} function iff this is a {@code Success}. * * @param consumer the function to apply * @return the original {@code Try} */ public abstract Try ifSuccess(final TryConsumer consumer); /** * Executes the given {@code Runnable} if this is a {@code Success} and returns this, otherwise directly returns this. * * @param runnable the runnable action that performs side-effects * @return the original {@code Try} */ public abstract Try ifSuccess(final TryRunnable runnable); /** * Applies the given {@code consumer} function iff this is a {@code Failure}. * * @param consumer the function to apply * @return the original {@code Try} */ public abstract Try ifFailure(final TryConsumer consumer); /** * Applies the given {@code consumer} function iff this is a {@code Failure} and the Exception is an instance * of the given class {@code e}. * * @param consumer the function to apply * @param e the type of the Exception for which we call the given {@code consumer} * @return the original {@code Try} */ public abstract Try ifFailure(final Class e, final TryConsumer consumer); /** * Returns the given function applied to the value from this {@code Success} or returns this if this is a {@code Failure}. * * @param f the mapping function * @return {@code Try} */ public abstract Try flatMap(final TryFunction> f); /** * Runs the given supplier to transform this {@code Try} if this is a {@code Success} or returns this if this is a {@code Failure}. * Note that this method is intended to flatMap functions that discard the value stored in the current {@code Success}. * * @param f the mapping function * @return {@code Try} */ public abstract Try flatMap(final TrySupplier> f); /** * Maps the given function to the value from this {@code Success} or returns this if this is a {@code Failure}. * * @param f the mapping function * @return {@code Try} */ public abstract Try map(final TryFunction f); /** * Runs the given supplier to transform the payload if this is a {@code Success} or returns this if this is a {@code Failure}. * Note that this method is intended to map functions that discard the value stored in the current {@code Success}. * * @param f the mapping function * @return {@code Try} */ public abstract Try map(final TrySupplier f); /** * Converts this to a {@code Failure} (containing a {@code NoSuchElementException}) if the predicate is not satisfied. * * @param predicate used to test the payload * @return {@code Try} */ public abstract Try filter(final TryPredicate predicate); /** * Converts this to a {@code Failure} (containing the given {@code Throwable}) if the predicate is not satisfied. * * @param predicate used to test the payload * @return {@code Try} */ public abstract Try filter(final TryPredicate predicate, final Throwable t); /** * Converts this using the {@code orElse} {@code Function} if the predicate is not satisfied. * * @param predicate used to test the payload * @param orElse the function which is used if the predicate isn't satisfied * @return {@code Try} */ public abstract Try filter(final TryPredicate predicate, final TryFunction> orElse); /** * Converts this using the {@code orElse} {@code Supplier} if the predicate is not satisfied. * * @param predicate used to test the payload * @param orElse the supplier which is used if the predicate isn't satisfied * @return {@code Try} */ public abstract Try filter(final TryPredicate predicate, final TrySupplier> orElse); /** * Applies the given function {@code f} if this is a {@code Failure}, otherwise returns this if this is a {@code Success}. * {@code recoverWith} is like {@code flatMap} for the {@code Failure} case. * * @param f the function to apply if this is a {@code Failure} * @return {@code Try} */ public abstract Try recoverWith(final TryFunction> f); /** * Applies the given function {@code f} if this is a {@code Failure} and the Exception is an instance of the given * class {@code e}, otherwise returns this if this is a {@code Success}. * {@code recoverWith} is like {@code flatMap} for the {@code Failure} case. * * @param f the function to apply if this is a {@code Failure} * @param e the type of the Exception to recover from * @return {@code Try} */ public abstract Try recoverWith(final Class e, final TryFunction> f); /** * Applies the given function {@code f} if this is a {@code Failure}, otherwise returns this if this is a {@code Success}. * {@code recover} is like {@code map} for the {@code Failure} case. * * @param f the function to apply if this is a {@code Failure} * @return {@code Try} */ public abstract Try recover(final TryFunction f); /** * Applies the given function {@code f} if this is a {@code Failure} and the Exception is an instance of the given class {@code e}, * otherwise returns this if this is a {@code Success}. * {@code recover} is like {@code map} for the {@code Failure} case. * * @param f the function to apply if this is a {@code Failure} * @param e the type of the Exception to recover from * @return {@code Try} */ public abstract Try recover(final Class e, final TryFunction f); /** * Convert to {@code Optional} * * @return {@code Optional.empty} if this is a {@code Failure} or an {@code Optional.ofNullable()} containing the * value if this is a {@code Success}. */ public abstract Optional toOptional(); /** * Compose sets of common operations on {@code Try}. * {@code apply} facilitates re-use of common patterns on {@code Try} without breaking the expression chain. * In essence this method is simply wrapping 'function application' to allow a more functional style with chained expressions. * * @param transformer function that performs the transformation * @param type of the resulting transformation * @return result of applying the {@code transformer}. If the {@code transformer} throws, a {@code Failure} will be returned. */ public Try apply(final TryFunction, Try> transformer) { try { return transformer.apply(this); } catch (final Exception e) { return failure(e); } } } final class Success extends Try { private T value; protected Success(final T value) { this.value = value; } @Override public boolean isFailure() { return false; } @Override public boolean isSuccess() { return true; } @Override public T getOrElse(final T defaultValue) { return value; } @Override public Try orElse(final Try defaultTry) { return this; } @Override public T get() { return value; } @Override public Try ifSuccess(final TryConsumer consumer) { try { consumer.accept(value); return this; } catch (final Exception e) { return failure(e); } } @Override public Try ifSuccess(final TryRunnable runnable) { try { runnable.run(); return this; } catch (final Exception e) { return failure(e); } } @Override public Try ifFailure(final TryConsumer consumer) { return this; } @Override public Try ifFailure(final Class e, final TryConsumer consumer) { return this; } @Override public Try flatMap(TryFunction> f) { try { return (Try) f.apply(value); } catch (final Exception e) { return failure(e); } } @Override public Try flatMap(final TrySupplier> f) { try { return (Try) f.get(); } catch (final Exception e) { return failure(e); } } @Override public Try map(final TryFunction f) { try { return success(f.apply(value)); } catch (final Exception e) { return failure(e); } } @Override public Try map(final TrySupplier f) { try { return success(f.get()); } catch (final Exception e) { return failure(e); } } @Override public Try filter(final TryPredicate predicate) { try { return predicate.test(value) ? this : failure(new NoSuchElementException("Predicate does not hold for " + value)); } catch (final Exception e) { return failure(e); } } @Override public Try filter(final TryPredicate predicate, final Throwable t) { try { return predicate.test(value) ? this : failure(t); } catch (final Exception e) { return failure(e); } } @Override public Try filter(final TryPredicate predicate, final TryFunction> orElse) { try { return predicate.test(value) ? this : (Try) orElse.apply(value); } catch (final Exception e) { return failure(e); } } @Override public Try filter(final TryPredicate predicate, final TrySupplier> orElse) { try { return predicate.test(value) ? this : (Try) orElse.get(); } catch (final Exception e) { return failure(e); } } @Override public Try recoverWith(final TryFunction> f) { return this; } @Override public Try recoverWith(final Class e, final TryFunction> f) { return this; } @Override public Try recover(final TryFunction f) { return this; } @Override public Try recover(final Class e, final TryFunction f) { return this; } @Override public Optional toOptional() { return Optional.ofNullable(value); } } final class Failure extends Try { private Throwable error; protected Failure(final Throwable error) { this.error = error; } @Override public boolean isFailure() { return true; } @Override public boolean isSuccess() { return false; } @Override public T getOrElse(final T defaultValue) { return defaultValue; } @Override public Try orElse(final Try defaultTry) { return (Try) defaultTry; } @Override public T get() { throw RuntimeException.class.isInstance(error) ? RuntimeException.class.cast(error) : new WrappedCheckedException(error); } @Override public Try ifSuccess(final TryConsumer consumer) { return this; } @Override public Try ifSuccess(TryRunnable runnable) { return this; } @Override public Try ifFailure(final TryConsumer consumer) { try { consumer.accept(error); return this; } catch (final Exception e) { return failure(e); } } @Override public Try ifFailure(final Class e, final TryConsumer consumer) { try { if (e.isInstance(error)) { consumer.accept((E) error); } return this; } catch (final Exception ex) { return failure(ex); } } @Override public Try flatMap(final TryFunction> f) { return (Try) this; } @Override public Try flatMap(TrySupplier> f) { return (Try) this; } @Override public Try map(final TryFunction f) { return (Try) this; } @Override public Try map(TrySupplier f) { return (Try) this; } @Override public Try filter(final TryPredicate predicate) { return this; } @Override public Try filter(final TryPredicate predicate, final Throwable t) { return this; } @Override public Try filter(final TryPredicate predicate, final TryFunction> orElse) { return this; } @Override public Try filter(final TryPredicate predicate, final TrySupplier> orElse) { return this; } @Override public Try recoverWith(final TryFunction> f) { try { return (Try) f.apply(error); } catch (final Exception e) { return failure(e); } } @Override public Try recoverWith(final Class e, final TryFunction> f) { try { return e.isInstance(error) ? (Try) f.apply((E) error) : this; } catch (final Exception ex) { return failure(ex); } } @Override public Try recover(final TryFunction f) { try { return success(f.apply(error)); } catch (final Exception e) { return failure(e); } } @Override public Try recover(final Class e, final TryFunction f) { try { return e.isInstance(error) ? success(f.apply((E) error)) : this; } catch (final Exception ex) { return failure(ex); } } @Override public Optional toOptional() { return Optional.empty(); } } abstract class TryCollector implements Collector, AtomicReference>>, Try> { /** * @return A function that, when called, returns a reference to a {@code Success}. * The {@code StreamBuilder} will be used to build up the {@code Stream}. The {@code AtomicReference} is needed * because the {@code Try} needs to be mutated in certain cases while collecting the {@code Stream} (see {@code accumulator}). */ @Override public Supplier>>> supplier() { return () -> new AtomicReference<>(Try.success(Stream.builder())); } /** * @return A function that takes a reference to the {@code Try} and the "next" {@code Try}, * iff both are a {@code Success} we add the payload of "next" to the builder, otherwise we mutate the * reference to the {@code StreamBuilder} to a {@code Failure}. */ @Override public BiConsumer>>, Try> accumulator() { return (builder, next) -> builder.get().ifSuccess(b -> next.ifSuccess(b::accept) .ifFailure(e -> builder.set(Try.failure(e))) ); } /** * @return A function that takes two references to {@code Try} and returns a new one in which * both input streams are merged. Iff both input streams are a {@code Success}, we add all items from "right" * to "left" and return the reference to "left". */ @Override public BinaryOperator>>> combiner() { return (leftRef, rightRef) -> { final Try> left = leftRef.get(); final Try> right = rightRef.get(); leftRef.set(right.flatMap(rb -> left.ifSuccess(lb -> rb.build().forEach(lb::accept)))); return leftRef; }; } /** * @return A function that takes a reference to a {@code Try} and returns a {@code Try} * iff the input is a {@code Success}. The concrete type of {@code U} is dependent on the {@code Collector} that is * supplied by the abstract method {@code collector()}. */ @Override public Function>>, Try> finisher() { return builder -> builder.get().map(b -> b.build().collect(collector().get())); } @Override public Set characteristics() { return Collections.emptySet(); } /** * @return A function that supplies the {@code Collector} to be used in the {@code finisher}. */ public abstract Supplier> collector(); }