com.aol.cyclops.trycatch.Try Maven / Gradle / Ivy
package com.aol.cyclops.trycatch;
import java.io.Closeable;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;
import lombok.AllArgsConstructor;
import lombok.val;
import com.aol.cyclops.invokedynamic.ExceptionSoftener;
import com.aol.cyclops.monad.AnyM;
import com.aol.cyclops.sequence.streamable.ToStream;
import com.aol.cyclops.value.ValueObject;
/**
* Light weight Try Monad
*
* Fail fast behaviour with more explicit declararions required compared to
* with scala.util.Try and javaslang.monad.Try. This is probably closer
* to how most Java devs currently handle exceptions.
*
* Goals / features
*
* Support for init block / try block / finally block
* Try with resources
* Try with multiple resources
* Does not automatically catch exceptions in combinators
* Can target specific exception types
* Exception types to be caught can be specified in xxxWithCatch methods
* Handle exceptions conciously, not coding bugs
* Fluent step builders
* Fail fast
*
* @author johnmcclean
*
* @param Return type (success)
* @param Base Error type
*/
public interface Try extends Supplier, ValueObject, ToStream {
/**
* @return This monad, wrapped as AnyM of Success
*/
public AnyM anyM();
/**
* @return This monad, wrapped as AnyM of Failure
*/
public AnyM anyMFailure();
/**
* @return This monad, wrapped as AnyM of Success
*/
public AnyM anyMSuccess();
/**
* @return Successful value or will throw Throwable (X) if Failire
*/
public T get();
/**
* Throw exception if Failure, do nothing if success
*/
public void throwException();
/**
* @param value Return value supplied if Failure, otherwise return Success value
* @return Success value or supplied value
*/
public T orElse(T value);
/**
*
* @param value from supplied Supplier if Failure otherwise return Success value
* @return Success value
*/
public T orElseGet(Supplier value);
/**
* @param fn Map success value from T to R. Do nothing if Failure (return this)
* @return New Try with mapped value (Success) or this (Failure)
*/
public Try map(Function fn);
/**
* @param fn FlatMap success value or Do nothing if Failure (return this)
* @return Try returned from FlatMap fn
*/
public Try flatMap(Function> fn);
/**
* @param p Convert a Success to a Failure (with a null value for Exception) if predicate does not hold.
* Do nothing to a Failure
* @return this if Success and Predicate holds, or if Failure. New Failure if Success and Predicate fails
*/
public Optional filter(Predicate p);
/**
* @param consumer Accept Exception if present (Failure)
* @return this
*/
public Try onFail(Consumer consumer);
/**
* @param t Class type of match Exception against
* @param consumer Accept Exception if present (Failure) and if class types match
* @return this
*/
public Try onFail(Class extends X> t,Consumer consumer);
/**
* @param fn Recovery function - map from a failure to a Success.
* @return new Success
*/
public Success recover(Function fn);
/**
* flatMap recovery
*
* @param fn Recovery FlatMap function. Map from a failure to a Success
* @return Success from recovery function
*/
public Success recoverWith(Function> fn);
/**
* Recover if exception is of specified type
* @param t Type of exception to match against
* @param fn Recovery function
* @return New Success if failure and types match / otherwise this
*/
public Try recoverFor(Class extends X> t,Function fn);
/**
*
* FlatMap recovery function if exception is of specified type
*
* @param t Type of exception to match against
* @param fn Recovery FlatMap function. Map from a failure to a Success
* @return Success from recovery function or this and types match or if already Success
*/
public Try recoverWithFor(Class extends X> t,Function> fn);
/**
* Flatten a nested Try Structure
* @return Lowest nested Try
*/
public Try flatten();
/**
* @return Optional present if Success, Optional empty if failure
*/
public Optional toOptional();
/**
* @return Stream with value if Sucess, Empty Stream if failure
*/
public Stream stream();
/**
* @return Optional present if Failure (with Exception), Optional empty if Success
*/
public Optional toFailedOptional();
/**
* @return Stream with error if Failure, Empty Stream if success
*/
public Stream toFailedStream();
/**
* @return true if Success / false if Failure
*/
public boolean isSuccess();
/**
* @return True if Failure / false if Success
*/
public boolean isFailure();
/**
* @param consumer Accept value if Success / not called on Failure
*/
public void foreach(Consumer consumer);
/**
* @param consumer Accept value if Failure / not called on Failure
*/
public void foreachFailed(Consumer consumer);
/**
* @param consumer Accept value if Success
* @return this
*/
default Try peek(Consumer consumer){
foreach(consumer);
return this;
}
/**
* @param consumer Accept Exception if Failure
* @return this
*/
default Try peekFailed(Consumer consumer){
foreachFailed(consumer);
return this;
}
/**
* Return a Try that will catch specified exceptions when map / flatMap called
* For use with liftM / liftM2 and For Comprehensions (when Try is at the top level)
*
* @param value Initial value
* @param classes Exceptions to catch during map / flatMap
* @return Try instance
*/
@SafeVarargs
public static Try of(T value,Class extends Throwable>... classes){
return new Success<>(value,classes);
}
/**
* Try to execute supplied Supplier and will Catch specified Excpetions or java.lang.Exception
* if none specified.
*
* @param cf CheckedSupplier to attempt to execute
* @param classes Exception types to catch (or java.lang.Exception if none specified)
* @return New Try
*/
@SafeVarargs
public static Try withCatch(CheckedSupplier cf,
Class extends X>...classes){
Objects.requireNonNull(cf);
try{
return Success.of(cf.get());
}catch(Throwable t){
if(classes.length==0)
return Failure.of((X)t);
val error = Stream.of(classes).filter(c -> c.isAssignableFrom(t.getClass())).findFirst();
if(error.isPresent())
return Failure.of((X)t);
else
throw ExceptionSoftener.throwSoftenedException(t);
}
}
/**
* Try to execute supplied Runnable and will Catch specified Excpetions or java.lang.Exception
* if none specified.
*
* @param cf CheckedRunnable to attempt to execute
* @param classes Exception types to catch (or java.lang.Exception if none specified)
* @return New Try
*/
@SafeVarargs
public static Try runWithCatch(CheckedRunnable cf,Class extends X>...classes){
Objects.requireNonNull(cf);
try{
cf.run();
return Success.of(null);
}catch(Throwable t){
if(classes.length==0)
return Failure.of((X)t);
val error = Stream.of(classes).filter(c -> c.isAssignableFrom(t.getClass())).findFirst();
if(error.isPresent())
return Failure.of((X)t);
else
throw ExceptionSoftener.throwSoftenedException(t);
}
}
/**
* Fluent step builder for Try / Catch / Finally and Try with resources equivalents.
* Start with Exception types to catch.
*
* @param classes Exception types to catch
* @return Next step in the fluent Step Builder
*/
@SafeVarargs
public static Init catchExceptions(Class extends X>...classes){
return new MyInit((Class[])classes);
}
@AllArgsConstructor
static class MyInit implements Init{
private final Class[] classes;
/*
* @param input
* @return
* @see com.aol.cyclops.trycatch.Try.Init#init(com.aol.cyclops.trycatch.Try.CheckedSupplier)
*/
@Override
public TryCatch init(CheckedSupplier input) {
return new MyTryCatch(classes,input);
}
@Override
public Try run(CheckedRunnable input) {
return runWithCatch(input,classes);
}
@Override
public Try tryThis(CheckedSupplier input) {
return withCatch(input,classes);
}
}
@AllArgsConstructor
static class MyTryCatch implements TryCatch{
private final Class[] classes;
private final CheckedSupplier inputSupplier;
@Override
public AndFinally tryThis(CheckedFunction catchBlock) {
return new MyFinallyBlock(classes,inputSupplier,catchBlock);
}
@Override
public Try tryWithResources(CheckedFunction catchBlock) {
return new MyFinallyBlock(classes,inputSupplier,catchBlock).close();
}
}
@AllArgsConstructor
public static class MyFinallyBlock implements AndFinally{
private final Class[] classes;
private final CheckedSupplier inputSupplier;
private final CheckedFunction catchBlock;
private void invokeClose(Object in) {
if(in instanceof Iterable)
invokeClose((Iterable)in);
invokeClose((Closeable)in);
}
private void invokeClose(Iterable in){
for(Closeable next : in)
invokeClose(next);
}
private void invokeClose(Closeable in){
Try.withCatch(()->in.getClass().getMethod("close")).filter(m->m!=null)
.flatMap(m->Try.withCatch(()->m.invoke(in))
.filter(o->o!=null));
}
public Try close(){
return andFinally(in -> invokeClose(in) );
}
@Override
public Try andFinally(CheckedConsumer finallyBlock) {
val input = Try.withCatch(() ->inputSupplier.get(),classes);
Try result = null;
try{
result =input.flatMap(in -> withCatch(()->catchBlock.apply(in),classes) );
}finally{
Try finalResult = result.flatMap(i-> Try.runWithCatch(() ->finallyBlock.accept(input.get()),classes));
if(finalResult instanceof Failure)
return finalResult;
}
return result;
}
}
public static interface Init{
/**
* Initialise a try / catch / finally block
* Define the variables to be used within the block.
* A Tuple or Iterable can be returned to defined multiple values.
* Closeables (either individually or within an iterable) will be closed
* via tryWithResources.
*
*
*
* Try.catchExceptions(FileNotFoundException.class,IOException.class)
* .init(()->new BufferedReader(new FileReader("file.txt")))
* .tryWithResources(this::read);
*
*
*
* or
*
*
*
* Try t2 = Try.catchExceptions(FileNotFoundException.class,IOException.class)
* .init(()->Tuple.tuple(new BufferedReader(new FileReader("file.txt")),new FileReader("hello")))
* .tryWithResources(this::read2);
*
* private String read2(Tuple2<BufferedReader,FileReader> res) throws IOException{
* String line = res.v1.readLine();
*
*
*
* @param input Supplier that provides input values to be used in the Try / Catch
* @return
*/
TryCatch init(CheckedSupplier input);
/**
* Run the supplied CheckedRunnable and trap any Exceptions
* Return type is Void
*
* @param input CheckedRunnable
* @return Try that traps any errors (no return type)
*/
Try run(CheckedRunnable input);
/**
* Run the supplied CheckedSupplier and trap the return value or an Exception
* inside a Try
*
* @param input CheckedSupplier to run
* @return new Try
*/
Try tryThis(CheckedSupplier input);
}
public static interface TryCatch {
/**
* Will execute and run the CheckedFunction supplied and will automatically
* safely close any Closeables supplied during init (either individually or inside an iterable)
*
* @param catchBlock CheckedFunction to Try
* @return New Try capturing return data or Exception
*/
Try tryWithResources(CheckedFunction catchBlock);
/**
* Build another stage in try / catch / finally block
* This defines the CheckedFunction that will be run in the main body of the catch block
* Next step can define the finally block
*
* @param catchBlock To Try
* @return Next stage in the fluent step builder (finally block)
*/
AndFinally tryThis(CheckedFunction catchBlock);
}
public static interface AndFinally {
/**
* Define the finally block and execute the Try
*
* @param finallyBlock to execute
* @return New Try capturing return data or Exception
*/
Try andFinally(CheckedConsumer finallyBlock);
/**
* Create a finally block that auto-closes any Closeables specified during init
* including those inside an Iterable
*
* @return New Try capturing return data or Exception
*/
Try close();
}
public static interface CheckedFunction{
public R apply(T t) throws X;
}
public static interface CheckedSupplier{
public T get() throws X;
}
public static interface CheckedConsumer{
public void accept(T t) throws X;
}
public static interface CheckedRunnable{
public void run() throws X;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy