
io.meat.Try Maven / Gradle / Ivy
package io.meat;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* A completed attempt to compute a result that either succeeded or failed.
*
* Try is especially useful for monadic composition of tasks that may either
* succeed or fail, without having to use a series of nested try/catch
* statements. You can form a Try using {@link #attempt(Supplier)} or
* {@link #attemptApply(Function, Object)}:
*
* {@code
* Try value = Try.attempt(() -> keyValueStore.fetch("someKey"));
* Try value = Try.attemptApply(keyValueStore::fetch, "someKey");
* }
*
* If the function has checked exceptions, you can use
* {@link #attemptChecked(CheckedSupplier)} or
* {@link #attemptApplyChecked(CheckedFunction, Object)} to wrap any exception
* in a RuntimeException.
*
* You can also manually make a successful or failed Try using
* {@link #succeed(Object)} and {@link #fail(Throwable)}:
*
* {@code
* Try value = Try.succeed("someValue");
* Try failure = Try.fail(new IllegalStateException("Something broke"));
* }
*
* Once you have a Try, you can transform the value inside it using
* {@link #map(Function)}:
*
* {@code
* Try hex = Try.succeed(123).map(Integer::toHexString);
* assert hex.equals(Try.succeed("7b"));
* }
*
* If your function returns a Try, you can use {@link #flatMap(Function)} to
* prevent nesting:
*
* {@code
* Try number = Try.succeed(123);
* Try nextValue = number.flatMap(num -> {
* return Try.succeed("a string");
* });
* assert nextValue.equals(Try.succeed("a string"));
* }
*
* To get values back out of a Try, there are Optionals available as
* {@link #getResult()} and {@link #getFailure()} for safe retrieval of either
* state:
*
* {@code
* Try number = Try.succeed(123);
* assert number.getResult().equals(Optional.of(123));
* assert !number.getFailure().isPresent();
* }
*
* If you've checked that a Try is successful, or you don't mind an (unchecked)
* exception being raised, use {@link #get()}:
*
* {@code
* Try number = Try.succeed(123);
* assert number.get() == 123;
* }
*
* In the case of a failed Try, get() will wrap the exception in a
* RuntimeException; its {@link Throwable#getCause()} will be the
* original exception.
*
* @param the type of result, if successful.
*/
public final class Try {
private final Result result;
private final Throwable failure;
private Try(Result result, Throwable failure) {
assert (result == null) ^ (failure == null)
: "Exactly one of failure or result must be null";
this.result = result;
this.failure = failure;
}
/**
* Build a successful Try from a non-null result.
*/
public static Try succeed(Result result) {
if (result == null) {
throw new IllegalArgumentException("Try.succeed result may not be null");
}
return new Try<>(result, null);
}
/**
* Build a failed Try from an exception or other Throwable.
*/
public static Try fail(Throwable failure) {
if (failure == null) {
throw new IllegalArgumentException("Try.fail failure may not be null");
}
return new Try<>(null, failure);
}
/**
* Wrap the result of a {@link Supplier} as a Try, catching exceptions.
*
* @param func a Supplier which returns a Result
* @param the type of result returned by func
* @return a Try containing either the Supplier's result, or any exception
* thrown by {@link Supplier#get()}
*/
public static Try attempt(Supplier func) {
return attemptChecked(func::get);
}
/**
* Similar to {@link #attempt(Supplier)}, but handles checked exceptions.
* @param func a CheckedSupplier which returns a Result or throws Exception
* @param the type of result returned by func
* @return a Try containing either the CheckedSupplier's result, or any
* exception thrown by {@link CheckedSupplier#get()}
*/
public static Try attemptChecked(CheckedSupplier func) {
Result result;
try {
result = func.get();
} catch (Exception e) {
return Try.fail(e);
}
return Try.succeed(result);
}
/**
* Wrap the result of a {@link Function} as a Try, catching exceptions.
*
* @param func a Function from the input to the result type of the Try
* @param input a single argument for func
* @param the input type of the function
* @param the result type of the function
* @return a Try of type Result
*/
public static Try attemptApply(
Function func,
Input input) {
return attemptApplyChecked(func::apply, input);
}
/**
* Similar to {@link #attemptApply(Function, Object)}, handling checked exceptions.
*
* @param func a Function from the input to the result type of the Try
* @param input a single argument for func
* @param the input type of the function
* @param the result type of the function
* @return a Try of type Result
*/
public static Try attemptApplyChecked(
CheckedFunction func,
Input input) {
Result result;
try {
result = func.apply(input);
} catch (Exception e) {
return Try.fail(e);
}
return Try.succeed(result);
}
/**
* Get the successful result of this Try, or {@link Optional#empty()} if it failed.
*
* @return an Optional result
*/
public Optional getResult() {
return Optional.ofNullable(result);
}
/**
* Get the Throwable that caused this Try to fail, or {@link Optional#empty()} if it was successful.
*
* @return an Optional Throwable
*/
public Optional getFailure() {
return Optional.ofNullable(failure);
}
/**
* Get this Try's result, or throw its failure as an unchecked exception.
*
* @return the successful result of this Try
* @throws RuntimeException wrapping the failure as an unchecked exception
*/
public Result get() {
if (result == null) {
throw new RuntimeException(failure);
}
return result;
}
/**
* Transform the result of this Try.
*
* If this Try is successful, the provided function will be applied to
* the current result and a new Try of the destination type will be
* returned.
* If this Try is a failure, a new Try of the destination result type
* containing the existing failure will be returned.
* If this Try is successful but the mapping function throws an
* exception, a failed Try of the destination result type will be
* returned, containing that exception.
*
* @param func A function mapping this Try's result type to a new one
* @param the output type of the transformation function
* @return a new Try of either the transformed result or existing failure
*/
public Try map(Function func) {
if (result == null) { return Try.fail(this.failure); }
return Try.attemptApply(func, result);
}
/**
* Transform the result of this Try into a new Try, returning that.
*
* The behavior of this function is similar to {@link #map(Function)},
* except that func should return a Try, and this will be returned without
* being wrapped further.
* Any uncaught exceptions thrown by func will themselves be captured in
* a Try.
*
* @param func a function mapping this Try's result type to another Try
* @param the result type of the Try produced by func
* @return a Try of the destination result type
*/
public Try flatMap(Function> func) {
if (result == null) { return Try.fail(this.failure); }
// This is kind of like Try.attemptApply but we don't wrap the result
// of func.apply in Try.succeed.
try {
return func.apply(result);
} catch (Exception e) {
return Try.fail(e);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Try aTry = (Try) o;
return Objects.equals(result, aTry.result) &&
Objects.equals(failure, aTry.failure);
}
@Override
public int hashCode() {
return Objects.hash(result, failure);
}
@Override
public String toString() {
if (result != null) {
return "Try{result=" + result + "}";
} else {
return "Try{failure=" + failure + "}";
}
}
}